Перейти к содержимому

13. Оптимизация изображений

Qwik предоставляет встроенные инструменты для оптимизации изображений через пакет @unpic/qwik или интеграцию с Qwik City. Правильная работа с изображениями критически важна для производительности.

Нативный <img> в HTML уже можно оптимизировать с помощью атрибутов:

// Базовая оптимизация без библиотек
<img
src="/images/hero.jpg"
alt="Hero image"
width={800}
height={400}
loading="lazy" // Ленивая загрузка
decoding="async" // Асинхронное декодирование
fetchpriority="high" // Для LCP изображений
/>
import { Image } from '@unpic/qwik';
export const HeroImage = component$(() => {
return (
<Image
src="https://example.com/hero.jpg"
alt="Hero"
width={800}
height={400}
priority // Загрузить с высоким приоритетом
layout="fullWidth" // Ширина во всю страницу
/>
);
});
vite.config.ts
import { qwikVite } from '@builder.io/qwik/optimizer';
import { qwikCity } from '@builder.io/qwik-city/vite';
export default defineConfig(() => ({
plugins: [
qwikCity(),
qwikVite(),
// Автоматическая оптимизация в dev-режиме
],
}));
// Импорт изображений с метаданными
import heroImage from './hero.jpg?w=800&h=400&format=webp';
export const Hero = component$(() => (
<img
src={heroImage.src}
width={heroImage.width}
height={heroImage.height}
/>
));
// 1. loading="lazy" — браузерная ленивая загрузка
<img src="..." loading="lazy" alt="..." />
// 2. Intersection Observer — более гибкий контроль
export const LazyImage = component$<{ src: string; alt: string }>((props) => {
const imgRef = useSignal<HTMLImageElement>();
const loaded = useSignal(false);
useVisibleTask$(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
loaded.value = true;
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (imgRef.value) observer.observe(imgRef.value);
return () => observer.disconnect();
});
return (
<div ref={imgRef} style={{ minHeight: 200 }}>
{loaded.value && (
<img src={props.src} alt={props.alt} />
)}
</div>
);
});
export const ResponsiveImage = component$<{ src: string; alt: string }>((props) => {
return (
<picture>
{/* WebP для современных браузеров */}
<source
type="image/webp"
srcset={\`
\${props.src}?w=400&format=webp 400w,
\${props.src}?w=800&format=webp 800w,
\${props.src}?w=1200&format=webp 1200w
\`}
/>
{/* Fallback JPEG */}
<img
src={props.src}
alt={props.alt}
sizes="(max-width: 400px) 100vw, (max-width: 800px) 50vw, 400px"
loading="lazy"
/>
</picture>
);
});
export const BlurImage = component$<{
src: string;
placeholder: string; // Base64 или низкоразрешённое изображение
alt: string;
}>((props) => {
const isLoaded = useSignal(false);
return (
<div style="position: relative; overflow: hidden;">
{/* Placeholder (blur) */}
<img
src={props.placeholder}
alt=""
aria-hidden="true"
style={{
position: 'absolute',
inset: 0,
filter: 'blur(20px)',
transform: 'scale(1.1)',
opacity: isLoaded.value ? 0 : 1,
transition: 'opacity 0.3s',
}}
/>
{/* Основное изображение */}
<img
src={props.src}
alt={props.alt}
loading="lazy"
onLoad$={() => { isLoaded.value = true; }}
style={{ opacity: isLoaded.value ? 1 : 0, transition: 'opacity 0.3s' }}
/>
</div>
);
});