Performance

Otimização de Imagens

Técnicas para reduzir tamanho de imagens sem perder qualidade.

Por que Otimizar Imagens?

Imagens são frequentemente o maior componente de uma página web:

  • 60-80% do tamanho total: Maior parte do peso da página
  • Impacto visual direto: Afeta LCP (Largest Contentful Paint)
  • Custo de banda: Especialmente em dispositivos móveis
  • Experiência do usuário: Imagens lentas frustram usuários

Formatos de Imagem Modernos

WebP

Formato desenvolvido pelo Google, oferece:

  • 25-35% menor que JPEG
  • 26% menor que PNG
  • Suporte a transparência
  • Suporte amplo em navegadores modernos
<picture>
  <source srcset="imagem.webp" type="image/webp">
  <img src="imagem.jpg" alt="Descrição">
</picture>

AVIF

Formato mais moderno e eficiente:

  • 50% menor que JPEG
  • Melhor qualidade em tamanhos menores
  • Suporte crescente (Chrome, Firefox, Safari)
<picture>
  <source srcset="imagem.avif" type="image/avif">
  <source srcset="imagem.webp" type="image/webp">
  <img src="imagem.jpg" alt="Descrição">
</picture>

Técnicas de Otimização

1. Compressão

Compressão com Perda (Lossy)

Para fotografias e imagens complexas:

# Usando imagemagick
convert imagem.jpg -quality 85 -strip imagem-otimizada.jpg

# Usando sharp (Node.js)
const sharp = require('sharp');
sharp('imagem.jpg')
  .jpeg({ quality: 85, mozjpeg: true })
  .toFile('imagem-otimizada.jpg');

Compressão sem Perda (Lossless)

Para gráficos, logos, ícones:

# PNG sem perda
pngquant --quality=65-80 imagem.png

# SVG otimizado
svgo imagem.svg

2. Redimensionamento

Nunca use imagens maiores que o necessário:

<!-- ❌ Ruim - imagem 2000px usada em 400px -->
<img src="foto-2000px.jpg" width="400" alt="Foto">

<!-- ✅ Bom - imagem redimensionada -->
<img src="foto-400px.jpg" width="400" alt="Foto">
// Redimensionar no servidor (Node.js com sharp)
sharp('imagem-original.jpg')
  .resize(400, 400, {
    fit: 'cover',
    position: 'center'
  })
  .jpeg({ quality: 85 })
  .toFile('imagem-400px.jpg');

3. Responsive Images

Use srcset e sizes para diferentes tamanhos de tela:

<img 
  srcset="
    imagem-400px.jpg 400w,
    imagem-800px.jpg 800w,
    imagem-1200px.jpg 1200w
  "
  sizes="(max-width: 600px) 400px,
         (max-width: 1200px) 800px,
         1200px"
  src="imagem-800px.jpg"
  alt="Descrição">

4. Lazy Loading

Carregue imagens apenas quando necessário:

<!-- Lazy loading nativo -->
<img 
  src="imagem.jpg" 
  loading="lazy"
  alt="Descrição">

<!-- Com Intersection Observer (fallback) -->
<img 
  data-src="imagem.jpg" 
  class="lazy"
  alt="Descrição">
// Intersection Observer para lazy loading
const lazyImages = document.querySelectorAll('img.lazy');

const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.classList.remove('lazy');
      observer.unobserve(img);
    }
  });
});

lazyImages.forEach(img => imageObserver.observe(img));

Imagens de Fundo

Otimização de Background Images

/* ❌ Ruim - imagem grande */
.hero {
  background-image: url('hero-2000px.jpg');
}

/* ✅ Bom - imagem otimizada e responsiva */
.hero {
  background-image: image-set(
    url('hero-400px.webp') 1x,
    url('hero-800px.webp') 2x
  );
  background-image: url('hero-800px.jpg'); /* fallback */
}

CSS com Lazy Loading

.hero {
  background-image: url('hero-placeholder.jpg');
}

.hero.loaded {
  background-image: url('hero-full.jpg');
}
// Carregar imagem de fundo quando visível
const hero = document.querySelector('.hero');
const imageObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('loaded');
      imageObserver.unobserve(entry.target);
    }
  });
});
imageObserver.observe(hero);

Sprites e Ícones

SVG Sprites

Para ícones, use SVG sprites:

<svg class="icon">
  <use href="#icon-home"></use>
</svg>

<svg style="display: none;">
  <symbol id="icon-home" viewBox="0 0 24 24">
    <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
  </symbol>
</svg>

Icon Fonts vs SVG

Prefira SVG para ícones:

  • Menor tamanho: Apenas ícones usados
  • Melhor qualidade: Escalável sem perda
  • Mais controle: Cores e estilos via CSS
  • Acessibilidade: Melhor para screen readers

Preload de Imagens Críticas

Preload LCP Image

<!-- Preload da imagem LCP -->
<link rel="preload" as="image" href="hero-image.jpg" fetchpriority="high">

Prefetch de Imagens

<!-- Prefetch de imagens provavelmente necessárias -->
<link rel="prefetch" as="image" href="next-page-hero.jpg">

Ferramentas de Otimização

Ferramentas Online

Ferramentas de Build

// Webpack com imagemin
const ImageminPlugin = require('imagemin-webpack-plugin').default;

module.exports = {
  plugins: [
    new ImageminPlugin({
      pngquant: { quality: [0.65, 0.8] },
      mozjpeg: { quality: 85 }
    })
  ]
};
// Vite com vite-imagetools
import { imagetools } from 'vite-imagetools';

export default {
  plugins: [
    imagetools({
      defaultDirectives: (url) => {
        if (url.searchParams.has('webp')) {
          url.searchParams.set('format', 'webp');
        }
        return new URLSearchParams({
          quality: '85'
        });
      }
    })
  ]
};

Checklist de Otimização

  • Imagens comprimidas (qualidade 80-85 para JPEG)
  • Formatos modernos (WebP/AVIF) com fallback
  • Tamanhos apropriados (não maiores que necessário)
  • Responsive images (srcset e sizes)
  • Lazy loading para imagens abaixo da dobra
  • Preload para imagem LCP
  • SVG para ícones e gráficos simples
  • Sprites para múltiplos ícones pequenos

Exemplo Completo

<picture>
  <!-- Formatos modernos primeiro -->
  <source 
    srcset="
      hero-400px.avif 400w,
      hero-800px.avif 800w,
      hero-1200px.avif 1200w
    "
    type="image/avif"
    sizes="(max-width: 600px) 400px,
           (max-width: 1200px) 800px,
           1200px">
  <source 
    srcset="
      hero-400px.webp 400w,
      hero-800px.webp 800w,
      hero-1200px.webp 1200w
    "
    type="image/webp"
    sizes="(max-width: 600px) 400px,
           (max-width: 1200px) 800px,
           1200px">
  <!-- Fallback -->
  <img 
    srcset="
      hero-400px.jpg 400w,
      hero-800px.jpg 800w,
      hero-1200px.jpg 1200w
    "
    sizes="(max-width: 600px) 400px,
           (max-width: 1200px) 800px,
           1200px"
    src="hero-800px.jpg"
    alt="Hero image"
    loading="eager"
    fetchpriority="high"
    width="1200"
    height="600">
</picture>
Sempre teste a qualidade visual após compressão. O objetivo é encontrar o equilíbrio entre tamanho de arquivo e qualidade visual aceitável.