I’ve built a simple photo gallery page which shows thumbnails of the photos in a grid. When the user clicks on a photo it presents a modal that shows the full-size image. The user can then swipe left or right to see the next images using .
My problem is that when the page containing the <ion-slides>
is opened, it loads all of the images present in each <ion-slide>
. This is not a problem with a handful of photos, however I could potentially have hundreds of photographs loading at the same time in no particular order resulting in a bad user experience.
Is there anyway I can change the behaviour so that only the current photo is loaded?
Modal markup:
<ion-header>
<ion-navbar>
<ion-title>{{ title | translate }}</ion-title>
<ion-buttons end>
<button ion-button (click)="dismiss()">{{ 'close' | translate }}</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-slides pager="true" [initialSlide]="initialSlide" paginationType="progress" zoom="true">
<ion-slide *ngFor="let medium of media">
<div class="swiper-zoom-container">
<img [src]="medium.largeImage()">
</div>
</ion-slide>
</ion-slides>
</ion-content>
Modal TS:
import { Component } from '@angular/core';
import { IonicPage, ViewController, NavParams } from 'ionic-angular';
// custom
import { Medium } from '../../models/medium';
@IonicPage()
@Component({
selector: 'page-media-viewer',
templateUrl: 'media-viewer.html',
})
export class MediaViewerPage {
title: string;
media: Medium[] = [];
initialSlide: number;
constructor(
private viewCtrl: ViewController,
private navParams: NavParams) {
this.title = this.navParams.get('title');
this.media = <Medium[]>this.navParams.get('media');
let selected = <Medium>this.navParams.get('selected');
this.setInitialSlide(selected);
}
dismiss() {
this.viewCtrl.dismiss();
}
private setInitialSlide(medium: Medium) {
let index = this.media.indexOf(medium);
this.initialSlide = index;
}
}
1 Like
Listen to slideChanged(). When the slide changes, get the current active index. Load the Observable that corresponds to your image at that index.
Thanks @AaronSterling for pointers on how to accomplish this.
For anyone that’s interested, i ended up with the below solution. Not a particularly elegant solution, but does what I need it to do.
Markup:
<ion-header>
<ion-navbar>
<ion-title>{{ title | translate }}</ion-title>
<ion-buttons end>
<button ion-button navPop>{{ 'close' | translate }}</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-slides pager="true" [initialSlide]="initialSlide" paginationType="progress" zoom="true" (ionSlideReachStart)="firstSlide()" (ionSlideNextStart)="slideNextStart()" (ionSlidePrevStart)="slidePrevStart()">
<ion-slide *ngFor="let medium of media">
<div class="swiper-zoom-container" *ngIf="medium.viewed()">
<img [src]="medium.largeImage()">
</div>
</ion-slide>
</ion-slides>
</ion-content>
Modal code:
import { Component, ViewChild } from '@angular/core';
import { IonicPage, NavParams, Slides } from 'ionic-angular';
// Medium model
import { Medium } from '../../models/medium';
@IonicPage()
@Component({
selector: 'page-media-viewer',
templateUrl: 'media-viewer.html',
})
export class MediaViewerPage {
private static readonly PRELOAD_SLIDES = 2;
@ViewChild(Slides) slides: Slides;
title: string;
media: Medium[] = [];
initialSlide: number;
constructor(
private navParams: NavParams) {
this.title = this.navParams.get('title');
this.media = <Medium[]>this.navParams.get('media');
let selected = <Medium>this.navParams.get('selected');
this.setInitialSlide(selected);
}
// Pre-load first n images if initial side index === 0
firstSlide() {
this.markViewed(0, true);
}
// Pre-load next n images if sliding forwards
// Called when slides first initialized - but oddly, not if initial slide index === 0
slideNextStart() {
let currentIndex = this.slides.getActiveIndex();
this.markViewed(currentIndex, true);
}
// Pre-load next n images if sliding backwards
slidePrevStart() {
let currentIndex = this.slides.getActiveIndex();
this.markViewed(currentIndex, false);
}
// Set initial slide
private setInitialSlide(medium: Medium) {
let index = this.media.indexOf(medium);
this.initialSlide = index;
}
// Mark next n images as viewed
private markViewed(currentIndex: number, forward: boolean) {
if (forward) {
let offset = Math.min(currentIndex + MediaViewerPage.PRELOAD_SLIDES + 1, this.media.length);
for(let i = currentIndex; i < offset; i++) {
this.media[i].markViewed();
}
} else {
let offset = Math.max(currentIndex - MediaViewerPage.PRELOAD_SLIDES - 1, 0);
for(let i = currentIndex; i >= offset; i--) {
this.media[i].markViewed();
}
}
}
}
Medium model:
import { Deserializable } from './deserializable';
import * as AppConstants from '../app/app.constants';
export class Medium implements Deserializable<Medium> {
public static readonly DEFAULT_IMAGE = AppConstants.processingAsset;
id: number;
small: string;
medium: string;
large: string;
kind: string;
video: string;
duration: string;
_viewed: boolean;
constructor() {
}
deserialize(input: any): Medium {
Object.assign(this, input);
return this;
}
smallImage() {
return this.small || Medium.DEFAULT_IMAGE;
}
mediumImage() {
return this.medium || Medium.DEFAULT_IMAGE;
}
largeImage() {
return this.large || Medium.DEFAULT_IMAGE;
}
canShowVideo() {
return this.kind === 'video' && this.video;
}
canShowPhoto() {
return this.kind === 'photo' || (this.kind === 'video' && this.video === undefined)
}
viewed() {
return this._viewed || false;
}
markViewed() {
if (!this.viewed()) {
this._viewed = true;
}
}
}
1 Like
Another pretty smooth method using ng-lazyload-image
:
<ion-slides pager class="item-images" #imageSlides>
<ion-slide *ngFor="let imageUrl of item.imageUrls; let i = index">
<img src="{{ imageUrl.url }}" (load)="item.imageLoaded=true" [width]="screenWidth" [ngClass]="{ 'item-img-default-size': !item.imageLoaded }"
*ngIf="i == 0">
<img class="subitem-image"
[src]="imagePlaceholder"
[lazyLoad]="imageUrl.url"
[scrollObservable]="imageSlides.ionSlideDrag"
[width]="screenWidth"
*ngIf="i > 0" />
</ion-slide>
</ion-slides>
NOTE: needed to load first image “normally” 'cause imageSlides.ionSlideDrag
doesn’t fire until sliding begins.
3 Likes
This works out of the box if you use ion-img (tested both in list views and on ion-slide).
In Ionic 5 it doesn’t even seem to be necessary to use the ion-img in an ion-virtual-scroll, as is documented in earlier Ionic releases.