A criação para a Web oferece um alcance incomparável. Seu aplicativo da Web está a um clique de distância e está disponível em quase todos os dispositivos conectados: smartphones, tablets, laptops e computadores, TVs e muito mais, independentemente da marca ou da plataforma. Para oferecer a melhor experiência, você criou um site responsivo que adapta a apresentação e a funcionalidade para cada formato e agora está analisando sua lista de verificação de desempenho para garantir que o aplicativo seja carregado o mais rápido possível: você otimizou seu caminho crítico de renderização, compactou e armazenau em cache os recursos de texto e agora está analisando a maioria dos recursos de imagem, que geralmente representam a maioria dos bytes transferidos. O problema é que a otimização de imagens é difícil:
- Determine o formato apropriado (vetor vs. rasterizado)
- Determinar os formatos de codificação ideais (jpeg, webp etc.)
- Determine as configurações de compactação corretas (com e sem perdas)
- Determine quais metadados devem ser mantidos ou removidos
- Faça diversas variantes de cada uma para cada tela + resolução DPR
- ...
- Considere o tipo de rede, a velocidade e as preferências do usuário
Individualmente, esses são problemas bem compreendidos. Coletivamente, eles criam um grande espaço de otimização que nós (os desenvolvedores) muitas vezes negligenciamos. Os humanos fazem um trabalho ruim ao explorar repetidamente o mesmo espaço de pesquisa, especialmente quando há muitas etapas envolvidas. Os computadores, por outro lado, se destacam nesses tipos de tarefas.
A resposta para uma estratégia de otimização boa e sustentável para imagens e outros recursos com propriedades semelhantes é simples: automação. Se estiver ajustando seus recursos manualmente, você está fazendo errado: você vai esquecer, vai ficar preguiçoso ou outra pessoa vai cometer esse erro por você, é garantido.
A saga da desenvolvedora com foco no desempenho
A pesquisa pelo espaço de otimização de imagens tem duas fases distintas: tempo de build e ambiente de execução.
- Algumas otimizações são intrínsecas ao recurso em si, por exemplo, selecionar o formato e o tipo de codificação adequados, ajustar as configurações de compactação para cada codificador, remover metadados desnecessários e assim por diante. Essas etapas podem ser executadas no "tempo de build".
- Outras otimizações são determinadas pelo tipo e pelas propriedades do cliente que as solicitam e precisam ser realizadas no "tempo de execução": selecionar o recurso adequado para a DPR do cliente e a largura de exibição pretendida, considerar a velocidade de rede do cliente, as preferências do usuário e do aplicativo e assim por diante.
As ferramentas de tempo de build existem, mas poderiam ser melhoradas. Por exemplo, há muita economia a ser feita ao ajustar dinamicamente a configuração de "qualidade" para cada imagem e cada formato de imagem, mas ainda não vi alguém realmente usá-la fora da pesquisa. Essa é uma área pronta para inovação, mas, para os propósitos desta postagem, deixarei nela. Vamos nos concentrar na parte do ambiente de execução da história.
<img src="/image/thing" sizes="50vw"
alt="image thing displayed at 50% of viewport width">
A intent do app é muito simples: buscar e mostrar a imagem em 50% da janela de visualização do usuário. É aqui que a maioria dos designers lava as mãos e a cabeça no bar. Enquanto isso, o desenvolvedor da equipe que se preocupa com o desempenho vai ter uma longa noite:
- Para ter a melhor compactação, ela quer usar o formato de imagem ideal para cada cliente: WebP para Chrome, JPEG XR para Edge e JPEG para o restante.
- Para ter a melhor qualidade visual, ela precisa gerar diversas variantes de cada imagem em diferentes resoluções: 1x, 1,5x, 2x, 2,5x, 3x e, talvez, um pouco mais intermediárias.
- Para evitar a exibição de pixels desnecessários, ela precisa entender o que "50% da janela de visualização do usuário realmente significa". Existem muitas larguras de janela de visualização diferentes.
- O ideal é também proporcionar uma experiência resiliente em que os usuários em redes mais lentas busquem automaticamente uma resolução mais baixa. Afinal, é hora de fazer o vidro.
- O aplicativo também expõe alguns controles de usuário que afetam qual recurso de imagem precisa ser buscado. Portanto, também é preciso considerar isso.
Então, o designer percebe que precisa exibir uma imagem diferente
com 100% de largura se o tamanho da janela de visualização for pequeno para otimizar a legibilidade. Isso significa que agora precisamos repetir o mesmo processo para mais um recurso e tornar a busca condicional ao tamanho da janela de visualização. Eu disse que isso é difícil? Bem,
ok, vamos lá. O elemento picture
nos levará longe:
<picture>
<!-- serve WebP to Chrome and Opera -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
/image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
/image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
type="image/webp">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
/image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
/image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
type="image/webp">
<!-- serve JPEGXR to Edge -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
/image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
/image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
type="image/vnd.ms-photo">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
/image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
/image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
type="image/vnd.ms-photo">
<!-- serve JPEG to others -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
/image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
/image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
/image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
/image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
<!-- fallback for browsers that don't support picture -->
<img src="/image/thing.jpg" width="50%">
</picture>
Cuidamos da direção de arte e da seleção de formato e fornecemos seis variantes de cada imagem para considerar a variabilidade na DPR e na largura da janela de visualização do dispositivo do cliente. Impressionante!
Infelizmente, o elemento picture
não permite definir regras de comportamento com base no tipo ou na velocidade da conexão do cliente. Dito isso, o
algoritmo de processamento dele permite que o user agent ajuste qual recurso busca em alguns casos (consulte a etapa 5). Vamos só esperar que o user agent
seja inteligente o bastante. Observação: nenhuma das implementações atuais são. Da mesma forma, não há
hooks no elemento picture
para permitir lógica específica do app que considere as preferências
do app ou do usuário. Para conseguir esses dois últimos bits, teríamos que mover toda a lógica acima
para JavaScript, mas isso perde as otimizações do scanner de pré-carregamento oferecidas por
picture
. Humm…
Fora essas limitações, ele funciona. Pelo menos para este recurso em particular. O desafio real e de longo prazo aqui é que não podemos esperar que o designer ou o desenvolvedor criem manualmente códigos como esse para cada recurso. É um quebra-cabeça divertido na primeira tentativa, mas perde o interesse logo depois. Precisamos de automação. Talvez o ambiente de desenvolvimento integrado ou outras ferramentas de transformação de conteúdo possam nos salvar e gerar automaticamente o código boilerplate acima.
Como automatizar a seleção de recursos com dicas de cliente
Respire fundo, suspenda a descrença e agora considere o exemplo abaixo:
<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
<source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
<img sizes="100vw" src="/image/thing-crop">
</picture>
Acredite ou não, o exemplo acima é suficiente para oferecer os mesmos
recursos que a marcação de imagem muito mais longa acima. Além disso, como veremos, ele
permite controle total do desenvolvedor sobre como, qual e quando os recursos de imagem são
buscados. A "mágica" está na primeira linha que
ativa a geração de relatórios de dicas do cliente
e instrui o navegador a anunciar a proporção de pixels do dispositivo (DPR
), a largura
da janela de visualização do layout (Viewport-Width
) e a largura de exibição pretendida (Width
) dos
recursos para o servidor.
Com as dicas do cliente ativadas, a marcação resultante do lado do cliente mantém apenas os requisitos de apresentação. O designer não precisa se preocupar com tipos de imagem, resoluções do cliente, pontos de interrupção ideais para reduzir os bytes entregues ou outros critérios de seleção de recursos. Sejamos francos, eles nunca fizeram e não deveriam. Melhor ainda, o desenvolvedor também não precisa reescrever e expandir a marcação acima, porque a seleção real de recursos é negociada pelo cliente e pelo servidor.
O Chrome 46 oferece suporte nativo para as dicas DPR
, Width
e
Viewport-Width
. As dicas são desativadas por padrão, e o <meta http-equiv="Accept-CH" content="...">
acima serve como um indicador de ativação que instrui o Chrome a anexar os cabeçalhos especificados às
solicitações de saída. Com isso pronto, vamos examinar os cabeçalhos de solicitação e resposta para uma solicitação de imagem de amostra:
O Chrome anuncia a compatibilidade com o formato WebP pelo cabeçalho de solicitação Accept. O novo navegador Edge também anuncia compatibilidade com JPEG XR pelo cabeçalho Accept.
Os próximos três cabeçalhos de solicitação são os cabeçalhos de dica do cliente que anunciam a
proporção de pixels do dispositivo do cliente (3x), a largura da janela de visualização do layout
(460 px) e a largura de exibição pretendida do recurso (230 px). Isso fornece todas as informações necessárias para que o servidor selecione a variante de imagem ideal com base no próprio conjunto de políticas: disponibilidade de recursos pré-gerados, custo de recodificação ou redimensionamento, popularidade de um recurso, carga atual do servidor e assim por diante. Nesse caso específico, o servidor usa as dicas DPR
e
Width
e retorna um recurso WebP, conforme indicado pelos cabeçalhos Content-Type
,
Content-DPR
e Vary
.
Não há mágica aqui. Movemos a seleção de recursos da marcação HTML para a negociação de solicitação/resposta entre o cliente e o servidor. Como resultado, o HTML se preocupa apenas com os requisitos de apresentação e é algo em que podemos confiar em qualquer designer e desenvolvedor para escrever, enquanto a pesquisa no espaço de otimização de imagens é deferida para computadores e agora é facilmente automatizada em escala. Você se lembra do nosso desenvolvedor focado em desempenho? Agora, o trabalho dela é criar um serviço de imagem que aproveite as dicas fornecidas e retorne a resposta apropriada: ela pode usar a linguagem ou o servidor que quiser ou deixar que um serviço de terceiros ou uma CDN faça isso por ela.
<img src="/image/thing" sizes="50vw"
alt="image thing displayed at 50% of viewport width">
Além disso, lembra deste cara acima? Com as dicas do cliente, a simples tag de imagem agora reconhece DPR-, janela de visualização e largura sem nenhuma marcação adicional. Se você precisar adicionar direção de arte, poderá usar a tag picture
, conforme ilustrado acima. Caso contrário, todas as tags de imagem existentes ficarão muito mais inteligentes. As dicas do cliente melhoram os elementos img
e picture
atuais.
Assumir o controle sobre a seleção de recursos com o service worker
O ServiceWorker é, na verdade, um proxy do lado do cliente em execução no seu navegador. Ele intercepta todas as solicitações enviadas e permite inspecionar, reescrever, armazenar em cache e até mesmo sintetizar respostas. As imagens não são diferentes e, com as dicas do cliente ativadas, o ServiceWorker ativo pode identificar as solicitações de imagem, inspecionar as dicas fornecidas pelo cliente e definir a própria lógica de processamento.
self.onfetch = function(event) {
var req = event.request.clone();
console.log("SW received request for: " + req.url)
for (var entry of req.headers.entries()) {
console.log("\t" + entry[0] +": " + entry[1])
}
...
}
O ServiceWorker oferece controle total do lado do cliente sobre a seleção de recursos. Isso é fundamental. Pense nisso, porque as possibilidades são quase infinitas:
- É possível reescrever os valores do cabeçalho de dicas do cliente definidos pelo user agent.
- Você pode anexar novos valores de cabeçalhos de dicas do cliente à solicitação.
- É possível reescrever o URL e apontar a solicitação de imagem para um servidor alternativo (por exemplo, CDN).
- Você pode até mesmo mover os valores de dica dos cabeçalhos para o próprio URL, se isso facilitar a implantação na sua infraestrutura.
- É possível armazenar as respostas em cache e definir a própria lógica para que os recursos sejam exibidos.
- É possível adaptar sua resposta com base na conectividade dos usuários.
- É possível usar a API NetInfo para consultar e anunciar suas preferências para o servidor.
- É possível retornar uma resposta alternativa se a busca for lenta.
- Você pode considerar as substituições de preferência do usuário e do aplicativo.
- Você pode... fazer tudo o que o seu coração desejar, na verdade.
O elemento picture
fornece o controle de direção de arte necessário na marcação HTML.
As dicas do cliente fornecem anotações sobre as solicitações de imagem resultantes que permitem
a automação da seleção de recursos. O ServiceWorker oferece recursos de gerenciamento
de solicitações e respostas no cliente. Esta é a Web extensível em ação.
Perguntas frequentes sobre dicas do cliente
Onde as dicas do cliente estão disponíveis? Enviado no Chrome 46. Em consideração no Firefox e no Edge.
Por que as dicas do cliente são ativadas? Queremos minimizar a sobrecarga para sites que não usam dicas de clientes. Para ativar as dicas do cliente, o site precisa fornecer o cabeçalho
Accept-CH
ou uma diretiva<meta http-equiv>
equivalente na marcação da página. Com qualquer um desses presentes, o user agent vai anexar as dicas apropriadas a todas as solicitações de sub-recursos. No futuro, poderemos oferecer outro mecanismo para manter essa preferência por uma origem específica, o que permitirá que as mesmas dicas sejam entregues em solicitações de navegação.Por que precisamos de dicas de cliente se temos o ServiceWorker? O ServiceWorker não tem acesso às informações de layout, recurso e largura da janela de visualização. Pelo menos não sem introduzir idas e voltas caras e atrasar significativamente a solicitação de imagem, por exemplo, quando uma solicitação de imagem é iniciada pelo analisador de pré-carregamento. As dicas do cliente se integram ao navegador para disponibilizar esses dados como parte da solicitação.
As dicas do cliente são apenas para recursos de imagem? O principal caso de uso por trás das dicas de DPR, largura da janela de visualização e largura é ativar a seleção de recursos para recursos de imagem. No entanto, as mesmas dicas são fornecidas para todos os sub-recursos, independentemente do tipo. Por exemplo, as solicitações de CSS e JavaScript também recebem as mesmas informações e também podem ser usadas para otimizar esses recursos.
Algumas solicitações de imagem não informam a largura. Por quê? O navegador pode não saber a largura de exibição pretendida, porque o site depende do tamanho intrínseco da imagem. Como resultado, a dica de largura é omitida para essas solicitações e para solicitações que não têm "largura de exibição", por exemplo, um recurso JavaScript. Para receber dicas de largura, especifique um valor de tamanho nas imagens.
E sobre <insira minha dica favorita>? O ServiceWorker permite que os desenvolvedores interceptem e modifiquem (por exemplo, adicionar novos cabeçalhos) todas as solicitações enviadas. Por exemplo, é fácil adicionar informações baseadas em NetInfo para indicar o tipo de conexão atual. Consulte "Relatórios de recursos com ServiceWorker". As dicas "nativas" enviadas ao Chrome (DPR, largura, largura de recurso) são implementadas no navegador porque uma implementação pura baseada em SW atrasaria todas as solicitações de imagem.
Onde posso saber mais, acessar mais demonstrações e muito mais? Confira o documento de explicação e fique à vontade para abrir um problema no GitHub se tiver feedback ou outras dúvidas.