如何在 Ionic 2 或 3 应用程序中加载实际图像之前显示占位符图像

Posted

技术标签:

【中文标题】如何在 Ionic 2 或 3 应用程序中加载实际图像之前显示占位符图像【英文标题】:How to show a placeholder image before the actual image loads in an Ionic 2 or 3 app 【发布时间】:2018-03-11 06:01:53 【问题描述】:

早安,

我正在开发一个 Ionic 应用程序,我正在从 JSON 提要动态加载图像。我希望当从占位符图像显示在其位置的外部 URL 中提取图像时,占位符图像应在加载后被“真实”图像替换。

我目前情况的一个例子是:

<ion-card *ngFor="let item of items"...
 <img [src]="item.picture" />

感谢和问候。

【问题讨论】:

你可以给item.picture(你的占位符图像src)一个默认值,当图像准备好时用实际值替换它。 @Kaddath 说起来容易做起来难 :) 你能证明你的方法吗,所以我能完全理解吗?谢谢! 你可以看看 sampath 的回答,我在之前的项目中做了几乎相同的事情,除了我有一个控制器函数作为 src 值(这个函数和他做的一样,检查图片是否有一个值,如果不是,则返回默认 src)。在我的情况下,我正在下载图片,并且异步响应只是在文件路径已知时设置值。当值改变时显示应该自动更新 你能解决这个@TCRoberts 吗? @sebaferreras 还没有。我没有测试这些方法。我很快就会在我的项目中进入这一部分。我不得不在我的项目中退后几步 【参考方案1】:

您能否修改您的 JSON 以具有 loaded 布尔属性?如果可以,这将很容易,请这样做:

<ion-card *ngFor="let item of items">
  <img [src]="item.picture" (load)="item.loaded = true" [hidden]="!item.loaded" />
  <img src="../path/to/your/placeholer/image.jpg" [hidden]="item.loaded">
</ion-card>

您将拥有一个始终初始化为 false 的加载属性,当图像加载时,(load) 事件将被触发并将图像加载属性更改为 true,隐藏占位符图像并显示加载的图像。

如果您无法更改此 JSON(或者如果它太大而无法编辑),您可以使用上面的代码,但在您的页面中执行以下操作.ts

public functionWhoseIsLoadingYourJson()
  // HTTP REQUEST FOR JSON
  .subscribe(response => 
    response.forEach(element => 
      element.loaded = false;
    );
    this.items = response;
  );

只需迭代您的响应,并在每个对象中将加载的属性设置为 false。如果这会引发一些错误,您可以将您的 &lt;ion-card&gt; 放在带有 *ngIf 的 div 中

<div *ngIf="this.items != null">
  <!-- ion card -->
</div>

编辑 - 更新 JSON

由于它来自 Firebase,因此可以通过三个选项来更新您的 JSON。第一个是一个函数,您只需执行一次即可更新数据库,第二个仍然是我提供的代码(遍历结果并推送布尔值),但由于您使用的是 Firebase,所以它会有所不同。

第一个解决方案:

在你的代码中的某个地方,它可以是任何页面,你只需要执行一次:

firebase.database().ref('myDataNode').on('value', snapshot => 
  for(let key in snapshot.val())
    firebase.database().ref('myDataNode/' + key + '/loaded').set(false);
  
);

看到我正在遍历您的myDataNode 中的所有结果,并使用它们的密钥将loaded 属性设置为false。但是在使用set 时要小心,因为您可以覆盖所有节点,在执行此方法之前请记住导出您的 Firebase 节点以进行备份。如果您要从代码中的任何位置将图像推送到 Firebase,请记住将其更改为一起设置 loaded 属性。

如前所述,第二个是我在此答案中所做的,但现在在您的 Firebase 调用中使用它并使用快照随附的 forEach 方法:

firebase.database().ref('myDataNode').on('value', snapshot => 
  snapshot.forEach(element =>
    this.myDataNode.push(
      picture: element.val(), // maybe it needs to be element.val().nameOfYourProperty
      loaded: false
    )
  );
);

这将使用图片 url 和加载的属性将一个新元素推送到您的数组。如果它无法识别.push() 方法,只需将变量类型声明为数组即可:

public myDataNode: any[];

第三个只是在本地更新,遍历您的 firebase 快照,将结果保存在本地变量中,创建一个新属性并将其推送到您的数组:

firebase.database().ref('myDataNode').on('value', snapshot => 
  for (let key in snapshot.val())
    snapshot.forEach(item =>  // iterate snapshot
      let record = item.val(); // save the snapshot item value in a local variable
      record.loaded = true; // add the property
      this.newArray.push(record); // push it to your array that'll  be used in ng-for
      return true; // return true to foreach
    );
  
);

希望这会有所帮助。

【讨论】:

是的,我可以修改我的 JSON 文件。这是我当前的 JSON 结构,如何修改它以具有 loaded 布尔属性? "picture" : "my/picture/url" @TCRoberts 您有两个选择:您可以在 API/数据库中修改 JSON 的每个值,也可以按照我的代码进行操作。当您从 http 调用中获得响应时,您可以使用 forEach 循环对其进行迭代,并在每个元素中插入加载的属性,并将其设置为 false。 @TCRoberts 你如何得到你的 JSON?是http调用吗?它在服务中?在资产文件中?如果您发布如何获取 JSON,我可以更新我的答案并向您展示如何修改。 您好,我正在通过 Firebase 获取我的 JSON 数据。在我的ts file 我有我的导入import firebase from 'firebase'; 然后在我的constructor 我有:firebase.database().ref('myDataNode').on('value', snapshot =&gt; this.myDataNode = snapshot.val(); ); 并且我在我的页面export class 下有myDataNode = []; 的属性myDataNode = []; - 你帮助我进行了检索不久前来自 Firebase 的数据(如果你还记得的话),所以我仍在关注你关于检索 JSON 数据的方法。那里一切都很好。 哦,对不起,我不记得帮助过你,但我很高兴我帮助过你。更新了我的答案。【参考方案2】:

大部分答案都是关于在同一视图中添加已加载检查的逻辑,但似乎不合适。相反,您可以创建自己的指令来处理它。您可以查看 this amazing article 了解如何操作。

首先将this GIF复制到您的src/assets文件夹中,并将其命名为preloader.gif

然后添加这个自定义指令。代码几乎是不言自明的:

// An image directive based on http://blog.teamtreehouse.com/learn-asynchronous-image-loading-javascript
import Directive, Input, OnInit from '@angular/core';

// Define the Directive meta data
@Directive(
  selector: '[img-preloader]', //E.g <img mg-img-preloader="http://some_remote_image_url"
  host: 
    '[attr.src]': 'finalImage'    //the attribute of the host element we want to update. in this case, <img 'src' />
  
)

//Class must implement OnInit for @Input()
export class ImagePreloader implements OnInit 
  @Input('img-preloader') targetSource: string;

  downloadingImage : any; // In class holder of remote image
  finalImage: any; //property bound to our host attribute.

  // Set an input so the directive can set a default image.
  @Input() defaultImage : string = 'assets/preloader.gif';

  //ngOnInit is needed to access the @inputs() variables. these aren't available on constructor()
  ngOnInit() 
    //First set the final image to some default image while we prepare our preloader:
    this.finalImage = this.defaultImage;

    this.downloadingImage = new Image();  // create image object
    this.downloadingImage.onload = () =>  //Once image is completed, console.log confirmation and switch our host attribute
      console.log('image downloaded');
      this.finalImage = this.targetSource;  //do the switch ?
    
    // Assign the src to that of some_remote_image_url. Since its an Image Object the
    // on assignment from this.targetSource download would start immediately in the background
    // and trigger the onload()
    this.downloadingImage.src = this.targetSource;
  


然后将新指令添加到您的应用模块中,如下所示:

import  NgModule, ErrorHandler  from '@angular/core';
import  IonicApp, IonicModule, IonicErrorHandler  from 'ionic-angular';
import  MyApp  from './app.component';
import  ImagePreloader  from '../components/img-preload/img-preload';

@NgModule(
  declarations: [
    MyApp,
    ImagePreloader, // <------- Here!
    // ...
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    // ...
  ],
  providers: [provide: ErrorHandler, useClass: IonicErrorHandler]
)
export class AppModule 

这是您可以使用新自定义指令的方式:

<img img-preloader="https://images.unsplash.com/photo-1413781892741-08a142b23dfe" >

这就是它的样子:

然后您可以设置图像容器的高度/宽度,因此预加载器 gif 和最终图像具有相同的尺寸。

【讨论】:

如何将第二个参数传递给该指令?例如像下面这样,你想在 onError 事件的情况下添加另一个图像:&lt;img img-preloader="https://images.unsplash.com/photo-1413781892741-08a142b23dfe" [onErrorImage]="assets/error.png" alt=""&gt; @Ari 你只需要在指令中再添加一个@Input('onErrorImage') errorImgSource: string;,然后在出现错误时使用:this.downloadingImage.onerror = () =&gt; this.finalImage = this.errorImgSource; 哥们你太棒了! 谢谢一百万!这很简洁,太有用了! 我们可以为 Ionic 1 也这样做吗?【参考方案3】:

据我所知,没有 Ionic 方法可以做到这一点。但是你仍然可以通过 javascript 和 css 来实现。

CSS方式:

创建一个 div 覆盖您的图像并将占位符图像设置为该 div 的background-image

<div class="image">
    <img src="your_main_image"/>
</div>
.image
    background-image: url('your_placeholder_image');

Javascript方式:

您可以在***中轻松找到几种javascript方法,因此我不会在这里重新回答。 This one 就是一个例子

【讨论】:

【参考方案4】:

试试这个:

.ts

export class MyPage 

  placeHolder:string = './assets/img/placeholder.png';

  constructor()

.html

   <img [src]=item.image ? item.image : placeHolder>

【讨论】:

【参考方案5】:

希望您可以尝试如下所示。您可以将占位符图像存储在assets/images 文件夹中。

.html

<ion-card *ngFor="let item of items">
 <img [src]="item?.picture!= null ? item.picture : myImgUrl" />
</ion-card>

.ts

export class MyPage
   myImgUrl:stirng='./assets/images/myimage.png;

   constructor()

【讨论】:

这会按预期工作吗?因为(我认为)总会有一个图像item?.picture 将永远是!= null。然后它会获取图像 url 并开始加载,并且永远不会显示占位符图像。

以上是关于如何在 Ionic 2 或 3 应用程序中加载实际图像之前显示占位符图像的主要内容,如果未能解决你的问题,请参考以下文章

自定义组件 sccs 未在 ionic 5 中加载

在 Ionic 应用程序中加载 Cloudfront 图像时出错 - iOS 9

Ionic v1 - 谷歌地图最初没有在 iOS 中加载

在 Ionic4 中加载页面时播放声音

如何通过 WKWebVIew 或 JSC 在 iOS 应用程序中加载 WebAssembly

UITableViewCell 如何知道从数组或字典中加载啥 JSON