Chargement Différé d'Images
Native Chargement Différé
<!-- Simple lazy loading -->
<img
src="image.jpg"
loading="lazy"
alt="Description">
<!-- With dimensions (prevents layout shift) -->
<img
src="image.jpg"
loading="lazy"
width="400"
height="300"
alt="Description">
<!-- Eager loading (for above-the-fold images) -->
<img
src="hero.jpg"
loading="eager"
alt="Hero image">
NgOptimizedImage
import { NgOptimizedImage } from '@angular/common';
@Component({
standalone: true,
imports: [NgOptimizedImage],
template: `
<!-- Basic usage -->
<img ngSrc="hero.jpg" width="400" height="300" alt="Hero">
<!-- Priority (above-the-fold) -->
<img ngSrc="hero.jpg" width="400" height="300" priority alt="Hero">
<!-- With fill (for backgrounds) -->
<div style="position: relative; height: 400px">
<img ngSrc="bg.jpg" fill alt="Background">
</div>
<!-- Responsive -->
<img
ngSrc="responsive.jpg"
width="400"
height="300"
sizes="(max-width: 768px) 100vw, 50vw"
alt="Responsive">
`
})
export class ImageComponent {}
Intersection Observer Directive
import { Directive, ElementRef, output, AfterViewInit, inject } from '@angular/core';
@Directive({
selector: '[appLazyLoad]',
standalone: true
})
export class LazyLoadDirective implements AfterViewInit {
private el = inject(ElementRef);
// Basé sur signal output
lazyLoad = output<void>();
ngAfterViewInit() {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.lazyLoad.emit();
observer.unobserve(this.el.nativeElement);
}
});
},
{ threshold: 0.1 }
);
observer.observe(this.el.nativeElement);
}
}
// Usage
@Component({
template: `
<img
[src]="imageSrc"
appLazyLoad
(lazyLoad)="loadImage()"
alt="Lazy loaded">
`
})
export class ImageComponent {
imageSrc = 'placeholder.jpg';
loadImage() {
this.imageSrc = 'actual-image.jpg';
}
}
Lazy Load with Placeholder
import { Component, input } from '@angular/core';
@Component({
selector: 'app-lazy-image',
standalone: true,
template: `
<img
[src]="isLoaded ? actualSrc() : placeholder"
(load)="onLoad()"
[class.loaded]="isLoaded"
alt="Image">
`,
styles: [`
img {
opacity: 0;
transition: opacity 0.3s;
}
img.loaded {
opacity: 1;
}
`]
})
export class LazyImageComponent {
actualSrc = input.required<string>();
placeholder = 'data:image/svg+xml,...'; // Tiny placeholder
isLoaded = false;
onLoad() {
this.isLoaded = true;
}
}
Progressive Image Loading
import { Component, input } from '@angular/core';
@Component({
selector: 'app-progressive-image',
standalone: true,
template: `
<div class="image-container">
<img
[src]="lowResSrc()"
class="low-res"
[class.hidden]="highResLoaded"
alt="Low resolution">
<img
[src]="highResSrc()"
class="high-res"
(load)="highResLoaded = true"
[class.visible]="highResLoaded"
loading="lazy"
alt="High resolution">
</div>
`,
styles: [`
.image-container {
position: relative;
}
img {
position: absolute;
width: 100%;
height: 100%;
}
.low-res {
filter: blur(10px);
transition: opacity 0.3s;
}
.low-res.hidden {
opacity: 0;
}
.high-res {
opacity: 0;
transition: opacity 0.3s;
}
.high-res.visible {
opacity: 1;
}
`]
})
export class ProgressiveImageComponent {
lowResSrc = input.required<string>();
highResSrc = input.required<string>();
highResLoaded = false;
}
Responsive Images
<!-- srcset for different resolutions -->
<img
src="image-400.jpg"
srcset="
image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w
"
sizes="(max-width: 600px) 100vw, 50vw"
loading="lazy"
alt="Responsive image">
<!-- picture element for art direction -->
<picture>
<source media="(max-width: 600px)" srcset="mobile.jpg">
<source media="(max-width: 1200px)" srcset="tablet.jpg">
<img src="desktop.jpg" loading="lazy" alt="Responsive">
</picture>
Virtual Scrolling with Images
import { ScrollingModule } from '@angular/cdk/scrolling';
@Component({
standalone: true,
imports: [ScrollingModule],
template: `
<cdk-virtual-scroll-viewport
itemSize="200"
style="height: 600px">
<div *cdkVirtualFor="let image of images" class="item">
<img
[src]="image.url"
loading="lazy"
width="200"
height="200"
[alt]="image.alt">
</div>
</cdk-virtual-scroll-viewport>
`
})
export class ImageGalleryComponent {
images = Array.from({ length: 1000 }, (_, i) => ({
url: `image-${i}.jpg`,
alt: `Image ${i}`
}));
}
Image CDN Integration
@Pipe({
name: 'imageCdn',
standalone: true
})
export class ImageCdnPipe implements PipeTransform {
transform(url: string, width?: number, height?: number): string {
const baseUrl = 'https://cdn.example.com';
const params = [];
if (width) params.push(`w=${width}`);
if (height) params.push(`h=${height}`);
const query = params.length > 0 ? `?${params.join('&')}` : '';
return `${baseUrl}/${url}${query}`;
}
}
// Usage
<img [src]="'image.jpg' | imageCdn:400:300" alt="Optimized">
Background Image Chargement Différé
import { Directive, ElementRef, input, AfterViewInit, inject } from '@angular/core';
@Directive({
selector: '[appLazyBg]',
standalone: true
})
export class LazyBackgroundDirective implements AfterViewInit {
private el = inject(ElementRef);
appLazyBg = input.required<string>();
ngAfterViewInit() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.el.nativeElement.style.backgroundImage = `url(${this.appLazyBg()})`;
observer.unobserve(this.el.nativeElement);
}
});
});
observer.observe(this.el.nativeElement);
}
}
// Usage
<div
[appLazyBg]="'background.jpg'"
style="height: 400px">
Content
</div>
Preload Critical Images
@Component({
template: `
<!-- Preload critical images -->
<link rel="preload" as="image" href="hero.jpg">
<img ngSrc="hero.jpg" width="1200" height="600" priority alt="Hero">
`
})
Bonnes Pratiques
- Utiliser
loading="lazy"for below-the-fold images - Utiliser
loading="eager"orpriorityfor above-the-fold - Always specify
widthandheight(prevent layout shift) - Utiliser
NgOptimizedImagefor automatique optimization - Utiliser responsive images (
srcset,sizes) - Utiliser CDN for image optimization
- Utiliser placeholders pour une meilleure UX
- Virtual scroll for image galleries
- Preload critical images
- Utiliser WebP format when possible
Image Formats
<!-- Utiliser modern formats with fallback -->
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" loading="lazy" alt="Image">
</picture>