Automatizar a compactação e a codificação

Torne a geração de origens de imagens de alto desempenho uma parte perfeita do seu processo de desenvolvimento.

Todas as sintaxes deste curso, desde a codificação de dados de imagem até a marcação densa de informações que alimenta as imagens responsivas, são métodos para as máquinas se comunicarem com as máquinas. Você descobriu várias maneiras de um navegador cliente comunicar as próprias necessidades a um servidor e a um servidor responder de maneira semelhante. As marcações de imagem responsivas (srcset e sizes, em especial) descrevem uma quantidade chocante de informações em relativamente poucos caracteres. Para o bem ou para o mal, essa concisão é intencional: deixar essas sintaxes menos concisas e, assim, mais fáceis de serem analisadas pelos desenvolvedores, poderia ter sido mais difícil para um navegador de análise. Quanto maior a complexidade adicionada a uma string, maior o potencial de erros de analisador ou de diferenças não intencionais no comportamento de um navegador para outro.

Janela de codificação automática de imagens.

No entanto, a mesma característica que pode fazer esses assuntos parecerem tão intimidantes também pode oferecer soluções: uma sintaxe lida facilmente por máquinas é uma sintaxe escrita por elas mais facilmente. Você provavelmente já encontrou muitos exemplos de codificação e compactação automatizadas de imagens como usuário da Web: qualquer imagem enviada para a Web por meio de plataformas de mídia social, sistemas de gerenciamento de conteúdo (CMS) e até mesmo clientes de e-mail quase sempre passam por um sistema que as redimensiona, recodifica e compacta.

Da mesma forma, seja com plug-ins, bibliotecas externas, ferramentas de processo de build autônomas ou uso responsável de scripts do lado do cliente, a marcação de imagem responsiva é pronta para ser usada na automação.

Essas são as duas principais preocupações com relação à automatização do desempenho de imagens: gerenciar a criação de imagens (as codificações, compactações e as fontes alternativas que você usará para preencher um atributo srcset) e gerar nossa marcação voltada para o usuário. Neste módulo, você aprenderá sobre algumas abordagens comuns para gerenciar imagens como parte de um fluxo de trabalho moderno, seja como uma fase automatizada do processo de desenvolvimento, por meio da estrutura ou do sistema de gerenciamento de conteúdo que alimenta seu site, ou quase totalmente abstraído por uma rede de fornecimento de conteúdo dedicada.

Automatizar a compactação e a codificação

É improvável que você se depare com um tempo para determinar manualmente a codificação e o nível de compactação ideais para cada imagem destinada a uso em um projeto, nem você gostaria de fazer isso. Tão importante quanto é manter o menor tamanho de transferência de imagens possível , ajustar as configurações de compactação e salvar novamente fontes alternativas de cada recurso de imagem destinado a um site de produção criaria um grande gargalo no seu trabalho diário.

Como você aprendeu ao ler sobre os vários formatos de imagem e tipos de compactação, a codificação mais eficiente para uma imagem sempre será ditada pelo conteúdo dela e, como você aprendeu em Imagens responsivas, os tamanhos alternativos necessários para suas origens de imagem serão definidos pela posição que essas imagens ocupam no layout da página. Em um fluxo de trabalho moderno, você vai abordar essas decisões de forma abrangente, e não individualmente, determinando um conjunto de padrões razoáveis para imagens, de acordo com os contextos em que elas devem ser usadas.

Ao escolher codificações para um diretório de imagens fotográficas, o AVIF é claramente o vencedor em termos de qualidade e tamanho da transferência, mas tem suporte limitado, o WebP oferece um substituto moderno e otimizado, e JPEG é o padrão mais confiável. Os tamanhos alternativos que precisamos produzir para imagens que ocupam uma barra lateral no layout de uma página variam muito em relação às imagens que ocupam toda a janela de visualização do navegador nos pontos de interrupção mais altos. As configurações de compactação exigem uma atenção nos artefatos de desfoque e compactação em vários arquivos resultantes, liberando menos espaço para extrair todos os bytes possíveis de cada imagem em troca de um fluxo de trabalho mais flexível e confiável. Em suma, você vai seguir o mesmo processo de tomada de decisão que conheceu neste curso, por escrito.

Quanto ao processamento em si, há um grande número de bibliotecas de processamento de imagens de código aberto que fornecem métodos de conversão, modificação e edição de imagens em lotes, competindo em velocidade, eficiência e confiabilidade. Essas bibliotecas de processamento permitem que você aplique configurações de codificação e compactação a diretórios inteiros de imagens de uma só vez, sem precisar abrir um software de edição de imagens e de uma maneira que preserve as origens de imagens originais caso essas configurações precisem ser ajustadas. Eles foram criados para execução em diversos contextos, do ambiente de desenvolvimento local ao próprio servidor da Web. Por exemplo, o ImageMin, voltado à compactação, para Node.js, pode ser estendido para atender a aplicativos específicos com uma variedade de plug-ins. Já o ImageMagick multiplataforma e o Sharp baseado em Node.js vêm com um número impressionante de recursos.

Essas bibliotecas de processamento de imagem possibilitam que os desenvolvedores criem ferramentas dedicadas à otimização contínua de imagens como parte dos processos de desenvolvimento padrão, garantindo que seu projeto sempre faça referência a origens de imagem prontas para produção com o mínimo de sobrecarga possível.

Fluxos de trabalho e ferramentas de desenvolvimento local

Executores de tarefas e bundlers como Grunt, Gulp ou Webpack podem ser usados para otimizar recursos de imagem com outras tarefas comuns relacionadas ao desempenho, como a minificação de CSS e JavaScript. Para ilustrar, vamos considerar um caso de uso relativamente simples: um diretório no seu projeto contém uma dúzia de imagens fotográficas que precisam ser usadas em um site público.

Primeiro, você precisa garantir uma codificação consistente e eficiente para essas imagens. Como você aprendeu nos módulos anteriores, o WebP é um padrão eficiente para imagens fotográficas em termos de qualidade e tamanho de arquivo. O formato WebP é bem compatível, mas não é universalmente. Por isso, também convém incluir um substituto na forma de um JPEG progressivo. Em seguida, para usar o atributo srcset e entregar esses recursos com eficiência, você vai precisar produzir vários tamanhos alternativos para cada codificação.

Embora isso fosse uma tarefa repetitiva e demorada se fosse feita com um software de edição de imagens, tarefas como o Gulp foram criados para automatizar exatamente esse tipo de repetição. O plug-in gulp-responsive, que usa o Sharp, é uma opção entre muitas que seguem um padrão semelhante: coletar todos os arquivos em um diretório de origem, recodificá-los e compactá-los com base na mesma abreviação padronizada de "qualidade" que você aprendeu em Formatos de imagem e compactação. Os arquivos resultantes são enviados a um caminho definido por você, prontos para serem referenciados nos atributos src dos elementos img voltados ao usuário, deixando os arquivos originais intactos.

const { src, dest } = require('gulp');
const respimg = require('gulp-responsive');

exports.webp = function() {
  return src('./src-img/*')
    .pipe(respimg({
      '*': [{
        quality: 70,
        format: ['webp', 'jpeg'],
        progressive: true
      }]
  }))
  .pipe(dest('./img/'));
}

Com um processo como esse, não seria causado nenhum dano ao ambiente de produção se alguém do projeto adicionasse acidentalmente uma foto codificada como PNG truecolor grande ao diretório que contém as origens de imagens originais. Independentemente da codificação da imagem original, essa tarefa produzirá um WebP eficiente e um substituto de JPEG progressivo confiável e um nível de compactação que pode ser facilmente ajustado de maneira imediata. Obviamente, esse processo também garante que os arquivos de imagem originais sejam mantidos no ambiente de desenvolvimento do projeto, o que significa que essas configurações podem ser ajustadas a qualquer momento com apenas a saída automatizada substituída.

Para gerar vários arquivos, você transmite vários objetos de configuração, todos iguais, exceto pela adição de uma chave width e um valor em pixels:

const { src, dest } = require('gulp');
const respimg = require('gulp-responsive');

exports.default = function() {
  return src('./src-img/*')
    .pipe(respimg({
    '*': [{
            width: 1000,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-1000' }
            },
            {
            width: 800,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-800' }
            },
            {
            width: 400,
            format: ['jpeg', 'webp'],
            progressive: true,
            rename: { suffix: '-400' },
        }]
        })
    )
    .pipe(dest('./img/'));
}

No caso do exemplo acima, a imagem original (monarch.png) tinha mais de 3,3 MB. O maior arquivo gerado por esta tarefa (monarch-1000.jpeg) tem aproximadamente 150 KB. A menor, o monarch-400.web, tem apenas 32 KB.

[10:30:54] Starting 'default'...
[10:30:54] gulp-responsive: monarch.png -> monarch-400.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-800.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-1000.jpeg
[10:30:54] gulp-responsive: monarch.png -> monarch-400.webp
[10:30:54] gulp-responsive: monarch.png -> monarch-800.webp
[10:30:54] gulp-responsive: monarch.png -> monarch-1000.webp
[10:30:54] gulp-responsive: Created 6 images (matched 1 of 1 image)
[10:30:54] Finished 'default' after 374 ms

Obviamente, convém examinar cuidadosamente os resultados em busca de artefatos de compactação visíveis ou possivelmente aumentar a compactação para economizar mais. Como essa tarefa não é destrutiva, essas configurações podem ser facilmente alteradas.

No geral, em troca dos poucos kilobytes que poderia extrair com microotimização manual, você ganha um processo que não é apenas eficiente, mas resiliente, uma ferramenta que aplica perfeitamente seu conhecimento sobre recursos de imagem de alto desempenho a um projeto inteiro, sem qualquer intervenção manual.

Marcação de imagem responsiva na prática

Normalmente, o preenchimento de atributos srcset é um processo manual simples, porque o atributo captura apenas informações sobre a configuração já feita ao gerar suas origens. Nas tarefas acima, estabelecemos os nomes dos arquivos e as informações de largura que serão seguidas pelo nosso atributo:

srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w"

Lembre-se de que o conteúdo do atributo srcset é descritivo, não prescritivo. Não há problema em sobrecarregar um atributo srcset, desde que a proporção de todas as fontes seja consistente. Um atributo srcset pode conter o URI e a largura de cada corte alternativo gerado pelo servidor sem causar solicitações desnecessárias, e quanto mais origens candidatas forem fornecidas a uma imagem renderizada, mais eficiente será a capacidade do navegador de adaptar as solicitações.

Como você aprendeu em Imagens responsivas, use o elemento <picture> para processar o padrão substituto WebP ou JPEG. Nesse caso, você vai usar o atributo type em conjunto com srcset.

<picture>
  <source type="image/webp" srcset="filename-1000.webp 1000w, filename-800.webp 800w, filename-400.webp 400w">
  <img srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w" sizes="…" alt="…">
</picture>

Como você aprendeu, os navegadores compatíveis com WebP vão reconhecer o conteúdo do atributo type e selecionar o atributo srcset do elemento <source> como a lista de imagens candidatas. Navegadores que não reconhecem image/webp como um tipo de mídia válido vão ignorar esse <source> e usar o atributo srcset do elemento interno <img>.

Há mais uma consideração em termos de compatibilidade com navegadores: navegadores sem suporte para qualquer marcação de imagem responsiva ainda precisarão de um substituto, ou podemos correr o risco de uma imagem corrompida em contextos de navegação especialmente antigos. Como <picture>, <source> e srcset são ignorados nesses navegadores, queremos especificar uma origem padrão no atributo src interno do <img>.

Como o escalonamento de uma imagem para baixo é visual integrado e a codificação JPEG tem suporte universal, o maior JPEG é uma escolha sensata.

<picture>
  <source type="image/webp" srcset="filename-1000.webp 1000w, filename-800.webp 800w, filename-400.webp 400w">
  <img src="filename-1000.jpg" srcset="filename-1000.jpg 1000w, filename-800.jpg 800w, filename-400.jpg 400w" sizes="…" alt="…">
</picture>

Pode ser um pouco mais difícil lidar com sizes. Como você aprendeu, sizes é necessariamente contextual. Não é possível preencher o atributo sem saber a quantidade de espaço que a imagem ocupará no layout renderizado. Para as solicitações mais eficientes possíveis, um atributo sizes preciso precisa estar na nossa marcação quando essas solicitações forem feitas pelo usuário final, muito antes dos estilos que regem o layout da página serem solicitados. Omitir completamente sizes não é apenas uma violação da especificação HTML, mas resulta em um comportamento padrão equivalente a sizes="100vw", informando ao navegador que essa imagem é restrita apenas pela própria janela de visualização, resultando na seleção das maiores fontes candidatas possíveis.

Como acontece com qualquer tarefa de desenvolvimento da Web particularmente pesada, várias ferramentas foram criadas para abstrair o processo de escrita manual de atributos sizes. respImageLint é um snippet de código absolutamente essencial destinado a verificar a precisão dos atributos sizes e fornecer sugestões de melhoria. Ele é executado como um marcador de página, uma ferramenta executada no navegador, apontando para a página totalmente renderizada que contém os elementos da imagem. Em um contexto em que o navegador entenda totalmente o layout da página, ele também terá uma percepção quase perfeita do espaço que uma imagem ocupará nesse layout em todos os tamanhos possíveis da janela de visualização.

Relatório de imagem responsiva mostrando a incompatibilidade de tamanho/largura.

Uma ferramenta para inspecionar os atributos sizes é certamente útil, mas tem ainda mais valor como ferramenta para gerá-los por atacado. Como você sabe, as sintaxes srcset e sizes têm como objetivo otimizar solicitações de recursos de imagem de maneira visualmente harmoniosa. Embora não seja algo que deva ser usado na produção, um valor padrão de marcador de posição sizes de 100vw é perfeitamente razoável ao trabalhar no layout de uma página no seu ambiente de desenvolvimento local. Quando os estilos de layout estiverem no lugar, executar respImageLint fornecerá atributos sizes personalizados que você pode copiar e colar na sua marcação, em um nível de detalhe muito maior do que um escrito à mão:

Relatório de imagem responsiva com dimensões sugeridas.

Embora as solicitações de imagem iniciadas pela marcação renderizada pelo servidor aconteçam muito rápido para que o JavaScript gere um atributo sizes do lado do cliente, o mesmo raciocínio não se aplica se essas solicitações forem iniciadas no lado do cliente. O projeto Lazysizes, por exemplo, permite adiar totalmente as solicitações de imagem até que o layout seja estabelecido, permitindo que o JavaScript gere nossos valores de sizes. Isso é uma grande conveniência para você e uma garantia de solicitações mais eficientes possíveis para seus usuários. No entanto, essa abordagem exige sacrificar a confiabilidade da marcação renderizada pelo servidor e as otimizações de velocidade integradas aos navegadores e iniciar essas solicitações somente após a renderização da página terá um grande impacto negativo na sua pontuação de LCP.

Obviamente, se você já está dependendo de um framework de renderização do lado do cliente, como React ou Vue, essa é uma dívida que você já vai estar sofrendo. Nesses casos, usar o Lazysizes significa que os atributos sizes podem ser quase completamente abstraídos. Melhor ainda: à medida que sizes="auto" em imagens de carregamento lento ganhar consenso e implementações nativas, o Lazysizes se tornará um polyfill para esse comportamento de navegador recém-padronizado.