Novidades na diretiva NgOptimizedImage do Angular

Alex Castle
Alex Castle

Há pouco mais de um ano, a equipe do Chrome Aurora lançou a diretiva NgOptimizedImage do Angular. A diretiva tem como foco principal melhorar o desempenho, conforme medido pelas métricas das Core Web Vitals. Ela agrupa otimizações de imagem comuns e práticas recomendadas em uma API voltada para o usuário que não é muito mais complicada do que um elemento <img> padrão.

Em 2023, melhoramos a diretiva com novos recursos. Esta postagem descreve os mais importantes desses novos recursos, com ênfase em por que escolhemos priorizar cada recurso e como eles podem ajudar a melhorar o desempenho dos aplicativos Angular.

Novos recursos

O NgOptimizedImage melhorou bastante ao longo do tempo, incluindo os novos recursos a seguir.

Modo de preenchimento

Dimensionar suas imagens fornecendo os atributos width e height é uma otimização extremamente importante para reduzir a mudança de layout, porque os navegadores precisam saber a proporção da imagem para economizar espaço. No entanto, o dimensionamento das imagens é um trabalho adicional para desenvolvedores de aplicativos e não faz sentido em alguns casos de uso de imagens.

Para ajudar a resolver essa tensão, o modo de preenchimento é o primeiro recurso importante adicionado à prévia do componente de imagem após o desenvolvedor. Essa é uma maneira dos desenvolvedores incluirem imagens sem dimensioná-las explicitamente e sem incorrer em mudanças de layout.

Com o modo de preenchimento, o requisito de dimensionamento da imagem é desativado, e a imagem é estilizada automaticamente para preencher o elemento que a contém. Isso dissocia a proporção de uma imagem do espaço que ela ocupa na página e oferece maior controle sobre como as imagens se encaixam no layout da página.

O modo de preenchimento usa NgOptimizedImage como uma alternativa de melhor desempenho à propriedade CSS background-image. Coloque uma imagem dentro do <div> ou outro elemento que teria o estilo background-image e ative o modo de preenchimento, conforme demonstrado no exemplo de código anterior. Use as propriedades CSS object-fit e object-position no <div> para controlar como a imagem é posicionada em segundo plano.

// Height and width are required
<img ngSrc="example.com" height="300" width="400">

// Unless you use fill mode!
<div style="width: 100vw; height: 50em; position: relative">
  <img ngSrc="example.com" fill>
</div>

Geração de Srcset

Uma das técnicas de otimização de imagens mais eficazes é o uso do atributo srcset, que garante o download de imagens com o tamanho adequado em qualquer dispositivo que acesse seu app. O uso do srcset em todo o app pode evitar o desperdício de largura de banda e melhorar significativamente as Core Web Vitals da LCP.

A desvantagem do atributo srcset é que a implementação dele pode ser complicada. Gravar manualmente os valores de srcset significa adicionar várias linhas de marcação a cada elemento de imagem no app, com vários URLs personalizados para cada srcset. Você também precisa decidir sobre um conjunto de pontos de interrupção, o que é complicado, porque eles podem representar as densidades de tela e os tamanhos da janela de visualização de dispositivos comuns.

É por isso que adicionar a geração automatizada de srcset à diretiva NgOptimizedImage foi um marco importante pós-lançamento. Com essa adição, qualquer aplicativo que use uma CDN com suporte ao redimensionamento de imagem pode ter srcsets completos e personalizáveis adicionados automaticamente a cada imagem gerada com a diretiva NgOptimizedImage.

Incluímos uma API simplificada para definir a propriedade sizes, que é usada para garantir que cada imagem receba o tipo correto de srcset. Se você não incluir um atributo sizes, saberemos que a imagem deve ter tamanho fixo e receber um srcset dependente de densidade, como no exemplo a seguir:

<img src="www.example.com/image.png" srcset="www.example.com/image.png?w=400 1x, www.example.com/image.png?w=800 2x" >

Esse tipo de srcset garante que as imagens sejam exibidas em um tamanho que leve em consideração a densidade de pixels do dispositivo do usuário.

Por outro lado, se você incluir a propriedade sizes, o NgOptimizedImage vai gerar um srcset responsivo que inclui pontos de interrupção para muitos tamanhos comuns de dispositivos e imagens, usando esta lista padrão de pontos de interrupção:

[16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840]

Geração de pré-conexão

Para melhorar a LCP, é importante reduzir o tempo que os usuários gastam fazendo o download da imagem da LCP. Na seção anterior, você viu como o srcset pode ajudar na transferência de arquivos de imagem menores. No entanto, uma otimização igualmente importante é iniciar a transferência o mais rápido possível. Uma maneira de fazer isso é usando tags link rel="preconnect" para iniciar a conexão com o domínio da imagem.

Desde o início, a NgOptimizedImage avisa se você não se pré-conecta ao domínio da sua imagem da LCP, mas esse alerta não é a solução ideal. Preferimos corrigir o problema para você. É exatamente isso que a NgOptimizedImage faz com a geração automatizada de pré-conexão.

Para oferecer suporte a esse recurso, usamos a análise de código estático para tentar detectar domínios de imagem nos carregadores do NgOptimizedImage e gerar automaticamente tags de link de pré-conexão para esses domínios. Em alguns casos, os links de pré-conexão manual são necessários. No entanto, para a maioria dos usuários, a pré-conexão automática significa uma etapa a menos necessária para que a imagem tenha um bom desempenho.

Suporte aprimorado para carregadores personalizados

Um elemento-chave da NgOptimizedImage é a arquitetura do carregador, que permite que a diretiva gere automaticamente URLs adaptados ao CDN de imagem do aplicativo. Um conjunto de carregadores integrados está incluído para CDNs amplamente utilizadas. Também oferecemos o uso de carregadores personalizados, que permitem integrar o NgOptimizedImage a praticamente qualquer solução de hospedagem de imagens.

No lançamento, esses carregadores personalizados tinham escopo limitado e só podiam ler o atributo width do elemento de imagem. Em resposta ao feedback dos usuários, adicionamos suporte a uma estrutura de dados loaderParams personalizável, que permite que dados arbitrários sejam transmitidos do elemento de imagem para o carregador personalizado. Com a expansão, os carregadores personalizados podem ser tão simples ou complexos quanto for exigido pela infraestrutura de imagem de um aplicativo.

O exemplo a seguir mostra como um carregador personalizado simples pode usar a API loaderParams para selecionar entre dois domínios de imagem alternativos:

const myCustomLoader = (config: ImageLoaderConfig) => {
  if (config.loaderParams?.alternateDomain) {
    return `https://alternate.domain.com/images/${config.src}`
  }
  return `https://primary.domain.com/images/${config.src}`;
};

Um exemplo de um carregador personalizado mais complexo está disponível na documentação do Angular.

Orientações ampliadas sobre o desempenho da imagem

Até agora, todos os alertas de desempenho de imagem que adicionamos ao Angular faz parte da diretiva NgOptimizedImage. Se você não estiver usando a diretiva no app, não vai receber orientações sobre problemas de desempenho da imagem.

No Angular 17, estamos expandindo o escopo da orientação de desempenho de imagem para incluir todos os apps do Angular. Agora, se detectarmos padrões de imagem que sabemos que prejudicam o desempenho, como o carregamento lento da imagem LCP ou o download de um arquivo grande demais para a página, avisaremos mesmo que você não esteja usando a NgOptimizedImage.

O desempenho das imagens é importante para todos os apps, e estamos animados em continuar criando proteções para ajudar a evitar erros comuns nos apps do Angular.

Para o futuro

Já estamos trabalhando duro no desenvolvimento do próximo conjunto de recursos para a NgOptimizedImage. Embora o desempenho da imagem continue sendo nossa principal preocupação, também gostaríamos de adicionar recursos que melhorassem a experiência do desenvolvedor, para garantir que o NgOptimizedImage continue sendo uma opção atraente para incluir imagens em aplicativos Angular.

Um recurso que é uma prioridade para nós são os marcadores de posição de imagem. Eles geralmente são usados para melhorar a aparência do carregamento de imagens em aplicativos da Web, mas podem prejudicar o desempenho se implementados incorretamente. Esperamos criar um sistema de marcador de posição de imagem que priorize o desempenho no NgOptimizedImage. Fique de olho no nosso blog para ficar por dentro das novidades.