Criar para a Web oferece um alcance sem igual. Seu aplicativo da Web está a um clique de distância e disponível em quase todos os dispositivos conectados: smartphones, tablets, 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. Agora você está executando sua lista de verificação de desempenho para garantir que o aplicativo seja carregado o mais rápido possível: você otimiza o caminho de renderização crítico, comprime e armazena em cache os recursos de texto e agora está analisando os recursos de imagem, que geralmente representam a maioria dos bytes transferidos. O problema é que a otimização de imagens é difícil:
- Determinar o formato apropriado (vetorial x raster)
- Determinar os formatos de codificação ideais (jpeg, webp etc.)
- Determinar as configurações de compactação corretas (com perda e sem perda)
- Determinar 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 ou negligenciamos. Os humanos não são bons em explorar o mesmo espaço de pesquisa repetidamente, especialmente quando muitas etapas estão 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 você estiver ajustando seus recursos manualmente, estará fazendo isso errado: você vai esquecer, vai ficar preguiçoso ou outra pessoa vai cometer esse erro por você, com certeza.
A saga da desenvolvedora com foco no desempenho
A pesquisa no espaço de otimização de imagens tem duas fases distintas: build-time e run-time.
- 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 solicita e precisam ser realizadas no "tempo de execução": selecionando o recurso adequado para a DPR do cliente e a largura de exibição pretendida, considerando a velocidade de rede do cliente, as preferências do usuário e do aplicativo e assim por diante.
As ferramentas de build-time existem, mas podem ser aprimoradas. Por exemplo, há muita economia ao ajustar dinamicamente a configuração "qualidade" para cada imagem e cada formato de imagem, mas ainda não vi ninguém usá-la fora da pesquisa. Essa é uma área pronta para inovação, mas, para os fins deste post, vou deixar assim. 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 aplicativo é 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 preocupado com a performance na equipe está se preparando para 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 os outros.
- Para conseguir a melhor qualidade visual, ela precisa gerar várias variantes de cada imagem em diferentes resoluções: 1x, 1,5x, 2x, 2,5x, 3x e talvez até algumas outras.
- Para evitar o envio 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 é que ela também queira oferecer uma experiência resiliente em que os usuários em redes mais lentas busquem automaticamente uma resolução mais baixa. Afinal, o tempo é o mais importante.
- O aplicativo também expõe alguns controles do usuário que afetam qual recurso de imagem precisa ser buscado. Isso também precisa ser levado em consideração.
Ah, e então o designer percebe que precisa mostrar 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, em seguida, tornar a
busca condicional no tamanho da janela de visualização. Eu disse que essas coisas são difíceis? Certo,
vamos lá. O elemento picture
nos ajudará bastante:
<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, seleção de formato e fornecemos seis variantes de cada imagem para considerar a variabilidade na DPR e na largura da viewport 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). Só podemos torcer para que o user agent seja inteligente
o suficiente. (Observação: nenhuma das implementações atuais é). Da mesma forma, não há
hooks no elemento picture
para permitir uma lógica específica do app que leve em conta 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 o JavaScript, mas isso perde as otimizações do scanner de pré-carregamento oferecidas por
picture
. Humm…
Além dessas limitações, ele funciona. Pelo menos para este recurso específico. O desafio real e de longo prazo aqui é que não podemos esperar que o designer ou o desenvolvedor crie códigos como esse para todos os recursos. É um quebra-cabeças divertido na primeira tentativa, mas perde o apelo imediatamente depois disso. 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 modelo acima.
Como automatizar a seleção de recursos com dicas de cliente
Respire fundo, suspenda sua descrença e considere o seguinte exemplo:
<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 "magia" está na primeira linha que
ativa o relatório de dicas do cliente
e informa ao navegador para anunciar a proporção de pixels do dispositivo (DPR
), a largura
do viewport de 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. Além disso, o desenvolvedor também não precisa reescrever e expandir a marcação acima, porque a seleção de recursos real é 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 informa ao Chrome para anexar os cabeçalhos especificados às
solicitações de saída. Com isso em mente, vamos examinar os cabeçalhos de solicitação e resposta
para um exemplo de solicitação de imagem:
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 três próximos cabeçalhos de solicitação são os cabeçalhos de sugestão 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 de um recurso, 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 pelo espaço de otimização de imagens é adiada para computadores e agora é facilmente automatizada em escala. Lembra do desenvolvedor preocupado com a performance? Agora, o trabalho dela é escrever um serviço de imagem que possa aproveitar as dicas fornecidas e retornar a resposta adequada: ela pode usar qualquer idioma ou servidor que quiser ou deixar que um serviço de terceiros ou uma CDN faça isso em nome dela.
<img src="/image/thing" sizes="50vw"
alt="image thing displayed at 50% of viewport width">
Lembra desse cara aí em cima? Com as dicas do cliente, a tag de imagem agora considera a DPR, a viewport e a largura sem nenhuma marcação adicional. Se você precisar adicionar a direção de arte, use a tag picture
, como ilustramos acima. Caso contrário, todas as tags de imagem atuais ficaram muito mais inteligentes. As dicas do cliente aprimoram os elementos img
e picture
.
Como assumir o controle da 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 de cliente definidos pelo user agent.
- É possível 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 sugestão dos cabeçalhos para o URL, se isso facilitar a implantação na sua infraestrutura.
- É possível armazenar respostas em cache e definir a própria lógica para os recursos que serão veiculados.
- É 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 ao servidor.
- Você pode retornar uma resposta alternativa se a busca for lenta.
- É possível contabilizar substituições de preferências de usuário e de apps.
- Você pode fazer o que quiser.
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 as dicas de cliente
Onde estão disponíveis as dicas do cliente? 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 de sites que não usam as dicas do cliente. Para ativar as dicas do cliente, o site precisa fornecer o cabeçalho
Accept-CH
ou a diretiva<meta http-equiv>
equivalente na marcação da página. Com qualquer uma delas, o user agent vai anexar as dicas apropriadas a todas as solicitações de subrecurso. 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 do cliente se temos um ServiceWorker? O ServiceWorker não tem acesso a informações de layout, recurso e largura da viewport. 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 são integradas ao navegador para disponibilizar esses dados como parte da solicitação.
As dicas do cliente são apenas para recursos de imagem? O caso de uso principal por trás de DPR, Viewport-Width e dicas de largura é ativar a seleção de recursos para recursos de imagem. No entanto, as mesmas dicas são enviadas para todos os subrecursos, independentemente do tipo. Por exemplo, as solicitações de CSS e JavaScript também recebem as mesmas informações e 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 tamanhos nas suas imagens.
E <insert my favorite hint>? O ServiceWorker permite que os desenvolvedores interceptem e modifiquem (por exemplo, adicione 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 no Chrome (DPR, largura, Resource-Width) são implementadas no navegador porque uma implementação puramente baseada em SW atrasaria todas as solicitações de imagem.
Onde posso saber mais, conferir mais demonstrações e o que mais? Confira o documento explicativo e abra um problema no GitHub se tiver feedback ou outras dúvidas.