Desenvolvimento

21 out, 2013

Automatizando troca de DPR por client-hints

Publicidade

Monitores de alta densidade empacotam múltiplos pixels físicos por pixel CSS, e a razão entre os dois é conhecida como a proporção de pixels do dispositivo (DPR). Para oferecer a melhor qualidade visual, uma imagem HiDPI que corresponde ao valor DPR do dispositivo deve ser servida ao cliente. A questão é: como?

A resposta até agora tem sido a utilização de medias queries no CSS, mas isso é complicado e insuficiente: é a única solução CSS, a tag img é mais ou menos inútil etc. Em resposta, nós temos agora uma série de propostas na mesa: elemento picture, srcset e, o menos conhecido, image-set. Muita tinta já foi derramada sobre os prós e contras de cada um, mas o desenvolvimento de um novo e notável WebKit recentemente conseguiu uma implementação de srcset.

srcset é bom o suficiente

Ambos srcset e tag picture são muito mais ambiciosos e vão muito além de troca de DPR, mas é importante notar que a implementação atual do WebKit aborda um subconjunto da especificação maior – mais especificamente, apenas a troca de DPR. Você pode olhar para isso de duas maneiras: como um fracasso, porque não vem com a “solução”, ou como um primeiro passo positivo, necessário e bem atrasado.

Acontece que eu estou neste último grupo. Na verdade, eu acho que é um grande passo positivo (é bom o suficiente), e espero que outros navegadores sigam. Dito isso, eu tenho algumas reservas também. Vamos dar uma olhada no que está em cima da mesa:

<img src="img.jpg" srcset="img-3.0.jpg 3x,   img-2.0.jpg 2x,
                                       img-1.5.jpg 1.5x, img-1.0.jpg 1x"  />
#someid {
  background-image: image-set(
                          url(img-3.0.jpg) 3x,   url(img-2.0.jpg) 2x,
                          url(img-2.0.jpg) 1.5x, url(img-1.0.jpg) 1x)
}

Chame-me louco, mas parece bobo. Eu sei que os desenvolvedores web saboreiam escrever um código padronizado repetido e, melhor, inflando nossa marcação com sintaxe complexa, pois ajuda a nossa segurança no trabalho, mas… </sarcasmo > talvez haja uma maneira mais fácil? Que tal:

<img src="img.jpg">
#someid { background-image: url(img.jpg) }

Sim, as antigas tag img e url( ) poderiam se dar muito bem ao entregarem todos os mesmos benefícios do exemplo acima, mas sem o inchaço e, ainda melhor, sem a necessidade de quaisquer alterações à maneira como nós escrevemos o nosso código hoje. O navegador pode fazer todo o trabalho por nós, e HTTP já fornece os mecanismos para lidar com isso.

Client-hints e negociação de DPR

Client-hints podem ser usadas como entrada para negociação de conteúdo pró-ativo; exatamente como o cabeçalho Accept permite que clientes indiquem quais os formatos que eles preferem, client-hints permitem que os clientes indiquem uma lista de preferências específicas de dispositivos e agente.

Você pode encontrar a especificação completa sobre o rastreador IETF, mas o que está acima faz as coisas parecerem muito mais complicadas do que realmente são. Em poucas palavras, assim como nós podemos agora usar o cabeçalho Accept para determinar qual formato de imagem que o cliente suporta, o cabeçalho CH (ou seja, client-hints) comunica a resolução DPR do dispositivo para o servidor, e o servidor fornece o recurso apropriado. Falando em HTTP, ele funciona assim:

(request)
GET /img.jpg HTTP/1.1
User-Agent: Awesome Browser
Accept: image/webp, image/jpg
CH: =2.0

(response)
HTTP/1.1 200 OK
Server: Awesome Server
Content-Type: image/jpg
Content-Length: 124523
Vary: CH

(image data)

O cliente envia a dica dpr=2.0 para o servidor, e o servidor pode optar por usar essa informação para entregar a imagem apropriada. Se o servidor diz não, ou ignora o cabeçalho CH, então ele pode entregar a imagem padrão – sem nenhum dano feito. Mas se ele decidir usar a dica dpr, então também acrescenta o cabeçalho Vary: CH para indicar que o valor do cabeçalho CH deve ser utilizado como parte da chave de cache. Nada de novo, continuando.

Colocando as mãos em client-hints e Nginx

Não demora muito para implementar um servidor “DPR consciente”. Por uma questão de exemplo, vamos fazer isto com Nginx:

http {
  # - capture the DPR resolution from CH header
  # - if no CH header is present, default to 1.0
  map $http_ch $dpr {
    ~*dpr=(?<val>.*) $val;
    default "1.0";
  }

  server {
    # - check for appropriate DPR asset
    # - if appropriate DPR is not available, fallback to original
    location ~* /images/(?<name>.*)\.(?<ext>.*)$ {
      try_files /images/$name-$dpr.$ext $uri =404;
    }

    # ... regular nginx configuration
  }
}

Sete linhas de código, isso é tudo o que é necessário. Em primeiro lugar, o diretivo map captura e extrai o valor DPR do cabeçalho CH. Então, vamos criar um novo diretivo de localização que intercepta solicitação de pasta /images/ e reescreve o pedido, observando-se o arquivo apropriado no disco. Se esse arquivo não for encontrado, ele tenta o arquivo original e, finalmente, 404 se ele estiver faltando também. Vamos testá-lo:

 gt; curl -s  http://localhost:8080/images/awesome.jpg | wc -c
  252680
 gt;  curl -s -H"CH: dpr=1.5" http://localhost:8080/images/awesome.jpg | wc -c
  381135
 gt; curl -s -H"CH: dpr=2.0" http://localhost:8080/images/awesome.jpg | wc -c
  710175

O primeiro pedido não contém o cabeçalho CH, e o servidor retorna o recurso padrão (1.0). No segundo pedido, vamos adicionar o cabeçalho CH com valor DPR definido como “1.5” e uma imagem diferente é entregue, que você pode ver pela contagem de byte. Mágico! Só que não. Apenas HTTP simples fazendo exatamente o que ele foi projetado para fazer.

Como Nginx produz o ativo DPR certo? Nesse caso, eu simplesmente pré-gerei os ativos corretos e os coloquei no disco: awesome-1.0.jpg, awesome-1.5.jpg, e awesome-2.0.jpg. Não demora muito para acrescentar uma etapa de compilação usando uma ferramenta como o grunhido de automatizar essa parte do seu fluxo de trabalho. Como alternativa, o pedido poderia ter enviado para um serviço de imagem via proxy (local ou de terceiros), que pode criar esses ativos de forma dinâmica. O ponto é, esta é a automação no trabalho. Eu gosto de automação; eu sou preguiçoso, e tenho problemas mais importantes para resolver do que repetir a marcação padronizada.

Mas, mas, mas…

Mas isso coloca mais bytes de cabeçalho na conexão! Sim, coloca. Marcação padronizada repetida coloca muito mais bytes na conexão. Concedido, os bytes do cabeçalho HTTP 1.x não são compactados, mesmo que o cabeçalho CH adicione colossais 11 bytes. Depois de repetir toda a marcação padronizada para cada breakpoint DPR, para cada imagem, CH vai sair na frente por uma boa margem. CH vai economizar bytes.

Mas… isso vai fragmentar o cache, e suporte para Vary é “ausente”, e os clientes antigos não funcionam bem com Vary? Eu enfrentei tudo isso anteriormente ao falar sobre negociação Accept. Desde então, tivemos múltiplos CDNs para deploy com êxito a negociação Accept para entrega WebP, e agora temos grandes serviços (incluindo o Google) que dependem da negociação Accept.

Mas podemos automatizar a geração padronizada! Sim, podemos. Etapas de compilação extra poderiam analisar o HTML e o CSS, e reescrevê-los para injetar a marcação apropriada. Eu vou deixar você ser o juiz se isso é uma “solução melhor”. Dito isso, eu não tenho nada contra a implementação srcset, eu só penso que client-hints abordam o problema de uma forma mais elegante.

Mas o meu servidor não faz X! Obtenha um servidor melhor. Sim, estou falando sério. Essa é uma solução muito mais barata a longo prazo – seu tempo é mais valioso. Além disso, se o CH é adotado, você pode apostar que haverá dezenas de plugins e módulos disponíveis em algum momento plano para automatizar todo o problema. Como vimos acima, não é preciso muito.

Simplificar a discussão de “imagens responsivas”

A mudanca de DPR é apenas uma parte da maior discussão de “imagens responsivas”: também temos uma resolução trocando direção de arte e um número crescente de pessoas pedindo imagens “pixel perfect” (ou seja, imagens em escala de tamanho exato da janela para evitar redimensionamento do cliente). Estes são problemas independentes e devemos tratá-los como tal.

Na verdade, eu diria que a única solução que requer marcação explícita é o caso da direção de arte – por definição, isso é com o designer. O resto é apenas padrão que pode e deve ser automatizado. Eu não quero pensar sobre essas coisas, ele deve funcionar. HTTP pode ajudar.

***

Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://www.igvita.com/2013/08/29/automating-dpr-switching-with-client-hints/