Aller au contenu principal

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" or priority for above-the-fold
  • Always specify width and height (prevent layout shift)
  • Utiliser NgOptimizedImage for 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>