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

TC *_*rts 11 typescript ionic-framework ionic2 ionic3 angular

美好的一天,

我正在研究Ionic应用程序,我正在从JSON提要中动态加载图像.我希望当从外部URL拉入图像时,占位符图像显示在其位置,此后,占位符图像应该在加载后由"真实"图像替换.

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

<ion-card *ngFor="let item of items"...
 <img [src]="item.picture" />
Run Code Online (Sandbox Code Playgroud)

感谢和问候.

seb*_*ras 20

大多数答案都是关于在同一视图中添加加载的检查的逻辑,但似乎不合适.相反,您可以创建自己的指令来处理它.您可以查看这篇精彩的文章,看看它是如何完成的.

首先将此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;
  }

}
Run Code Online (Sandbox Code Playgroud)

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

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 {}
Run Code Online (Sandbox Code Playgroud)

这是您使用新的自定义指令的方法:

<img img-preloader="https://images.unsplash.com/photo-1413781892741-08a142b23dfe" alt="">
Run Code Online (Sandbox Code Playgroud)

这就是它的样子:

结果

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


Gab*_*eto 2

您可以修改 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>
Run Code Online (Sandbox Code Playgroud)

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

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

public functionWhoseIsLoadingYourJson(){
  // HTTP REQUEST FOR JSON
  .subscribe(response => {
    response.forEach(element => {
      element.loaded = false;
    });
    this.items = response;
  });
}
Run Code Online (Sandbox Code Playgroud)

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

<div *ngIf="this.items != null">
  <!-- ion card -->
</div>
Run Code Online (Sandbox Code Playgroud)

编辑-更新 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);
  }
});
Run Code Online (Sandbox Code Playgroud)

请注意,我正在循环遍历您的所有结果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
    })
  });
});
Run Code Online (Sandbox Code Playgroud)

这会将带有图片 url 和已加载属性的新元素推送到数组中。如果它无法识别该.push()方法,只需将变量声明为数组:

public myDataNode: any[];
Run Code Online (Sandbox Code Playgroud)

第三个只是在本地更新,迭代您的 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
    });
  }
});
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助。