Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(module:image): support lazy loading for image component #8157

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions components/image/doc/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { NzImageModule } from 'ng-zorro-antd/image';
| nzCloseOnNavigation | Whether to close the image preview when the user goes backwards/forwards in history. Note that this usually doesn't include clicking on links (unless the user is using the HashLocationStrategy). | `boolean` | `false` | ✅ |
| nzDirection | Text directionality | `Direction` | `'ltr'` | ✅ |
| nzScaleStep | `1 + nzScaleStep` is the step to increase or decrease the scale | `number` | 0.5 | ✅ |
| nzLoading | Image element's loading property. reference [loading property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/loading) | `eager \| lazy` | `eager` | - |

Other attributes [<img\>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes)

Expand Down
1 change: 1 addition & 0 deletions components/image/doc/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { NzImageModule } from 'ng-zorro-antd/image';
| nzCloseOnNavigation | 当用户在历史中前进/后退时是否关闭预览。注意,这通常不包括点击链接(除非用户使用 HashLocationStrategy)。 | `boolean` | `false` | ✅ |
| nzDirection | 文字方向 | `Direction` | `'ltr'` | ✅ |
| nzScaleStep | `1 + nzScaleStep` 为缩放放大的每步倍数 | `number` | 0.5 | ✅ |
| nzLoading | 图片元素的加载属性。 参见 [HTMLImageElement.loading](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLImageElement/loading) | `eager \| lazy` | `eager` | - |

其他属性见 [<img\>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#Attributes)

Expand Down
39 changes: 33 additions & 6 deletions components/image/image.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import { NzImageService } from './image.service';

const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'image';
const INTERSECTION_THRESHOLD = 0.1;

export type ImageStatusType = 'error' | 'loading' | 'normal';
export type NzImageUrl = string;
Expand All @@ -47,6 +48,7 @@

@Input() nzSrc = '';
@Input() nzSrcset = '';
@Input() nzLoading: 'eager' | 'lazy' = 'eager';
@Input({ transform: booleanAttribute }) @WithConfig() nzDisablePreview: boolean = false;
@Input() @WithConfig() nzFallback: string | null = null;
@Input() @WithConfig() nzPlaceholder: string | null = null;
Expand Down Expand Up @@ -133,7 +135,9 @@
ngOnChanges(changes: SimpleChanges): void {
const { nzSrc } = changes;
if (nzSrc) {
this.getElement().nativeElement.src = nzSrc.currentValue;
if (this.nzLoading === 'eager') {
this.getElement().nativeElement.src = nzSrc.currentValue;
}
this.backLoad();
}
}
Expand All @@ -145,18 +149,35 @@
*/
private backLoad(): void {
this.backLoadImage = this.document.createElement('img');
this.backLoadImage.src = this.nzSrc;
this.backLoadImage.srcset = this.nzSrcset;

if (this.nzLoading === 'eager') {
this.backLoadImage.src = this.nzSrc;
this.backLoadImage.srcset = this.nzSrcset;
}
this.status = 'loading';

// unsubscribe last backLoad
this.backLoadDestroy$.next();
this.backLoadDestroy$.complete();
this.backLoadDestroy$ = new Subject();

if (this.backLoadImage.complete) {
this.status = 'normal';
this.getElement().nativeElement.src = this.nzSrc;
this.getElement().nativeElement.srcset = this.nzSrcset;
if (this.nzLoading === 'lazy') {
const observer = new IntersectionObserver(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could trigger a memory leak, we need to call observer.disconnect() in ngOnDestroy. @ParsaArvanehPA

entries => {
entries.forEach(entry => {

Check warning on line 168 in components/image/image.directive.ts

View check run for this annotation

Codecov / codecov/patch

components/image/image.directive.ts#L166-L168

Added lines #L166 - L168 were not covered by tests
if (entry.isIntersecting) {
this.backLoadCompleteStateHandler();
observer.disconnect();

Check warning on line 171 in components/image/image.directive.ts

View check run for this annotation

Codecov / codecov/patch

components/image/image.directive.ts#L170-L171

Added lines #L170 - L171 were not covered by tests
}
});
},
{ threshold: INTERSECTION_THRESHOLD }
);
observer.observe(this.getElement().nativeElement);

Check warning on line 177 in components/image/image.directive.ts

View check run for this annotation

Codecov / codecov/patch

components/image/image.directive.ts#L177

Added line #L177 was not covered by tests
} else {
this.backLoadCompleteStateHandler();
}
} else {
if (this.nzPlaceholder) {
this.getElement().nativeElement.src = this.nzPlaceholder;
Expand Down Expand Up @@ -187,4 +208,10 @@
});
}
}

private backLoadCompleteStateHandler(): void {
this.status = 'normal';
this.getElement().nativeElement.src = this.nzSrc;
this.getElement().nativeElement.srcset = this.nzSrcset;
}
}
10 changes: 9 additions & 1 deletion components/image/image.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ describe('Basics', () => {
fixture.detectChanges();
expect(image.src).toBe(SECOND_SRC);
}));

it('should the loading attribute be auto at default', fakeAsync(() => {
const image = debugElement.nativeElement.querySelector('img');
context.nzImage.backLoadImage.dispatchEvent(new Event('load'));
fixture.detectChanges();
expect(image.loading).toBe('auto');
}));
});

describe('Placeholder', () => {
Expand Down Expand Up @@ -715,12 +722,13 @@ describe('Preview', () => {
});

@Component({
template: ` <img nz-image [nzSrc]="src" [nzPlaceholder]="placeholder" /> `
template: ` <img nz-image [nzSrc]="src" [nzPlaceholder]="placeholder" [nzLoading]="loading" /> `
})
export class TestImageBasicsComponent {
@ViewChild(NzImageDirective) nzImage!: NzImageDirective;
src = '';
placeholder: string | null = '';
loading: 'eager' | 'lazy' = 'eager';
}

@Component({
Expand Down