Um dos recursos da complexa situação atual dos dispositivos é que há uma gama muito ampla de densidades de pixels da tela disponíveis. Alguns dispositivos têm telas de alta resolução, enquanto outros ficam para trás. Os desenvolvedores de aplicativos precisam oferecer suporte a uma variedade de densidades de pixel, o que pode ser bastante desafiador. Na Web para dispositivos móveis, os desafios são agravados por vários fatores:
- Grande variedade de dispositivos com formatos diferentes.
- Largura de banda de rede e duração da bateria limitadas.
Em termos de imagens, o objetivo dos desenvolvedores de apps da Web é oferecer as imagens de melhor qualidade da maneira mais eficiente possível. Este artigo vai abordar algumas técnicas úteis para fazer isso hoje e no futuro.
Evite imagens, se possível
Antes de abrir essa caixa de Pandora, lembre-se de que a Web tem muitas tecnologias poderosas que são em grande parte independentes de resolução e DPI. Especificamente, texto, SVG e grande parte do CSS "apenas funcionam" devido ao recurso de escalonamento automático de pixels da Web (usando devicePixelRatio).
No entanto, nem sempre é possível evitar imagens raster. Por exemplo, você pode receber recursos que seriam muito difíceis de replicar em SVG/CSS puro ou você está lidando com uma fotografia. Embora seja possível converter a imagem em SVG automaticamente, a vetorização de fotografias não faz muito sentido, porque as versões ampliadas geralmente não ficam boas.
Contexto
Um breve histórico da densidade de exibição
No início, as telas de computador tinham uma densidade de pixel de 72 ou 96 dpi (pontos por polegada).
As telas melhoraram gradualmente na densidade de pixels, em grande parte impulsionadas pelo caso de uso de dispositivos móveis, em que os usuários geralmente seguram os smartphones mais perto do rosto, tornando os pixels mais visíveis. Em 2008, os smartphones de 150 dpi eram a norma. A tendência de aumento da densidade de tela continuou, e os novos smartphones atuais têm telas de 300 dpi (marca "Retina" da Apple).
O objetivo final, é claro, é uma tela em que os pixels são completamente invisíveis. Para o formato de smartphone, a geração atual de telas Retina/HiDPI pode estar próxima do ideal. No entanto, novas classes de hardware e wearables, como o Project Glass, provavelmente vão continuar aumentando a densidade de pixels.
Na prática, as imagens de baixa densidade precisam ter a mesma aparência nas novas telas que nas antigas, mas, em comparação com as imagens nítidas de alta densidade que os usuários estão acostumados a ver, as imagens de baixa densidade parecem irregulares e pixeladas. Confira a seguir uma simulação aproximada de como uma imagem 1x vai parecer em uma tela 2x. Em contraste, a imagem 2x parece muito boa.
![Baboon 1x](https://web.developers.google.cn/static/articles/high-dpi/image/baboon-1x.jpeg?hl=pt-br)
![Baboon 2x](https://web.developers.google.cn/static/articles/high-dpi/image/baboon-2x-d5dac4651f971.jpg?hl=pt-br)
Pixels na Web
Quando a Web foi projetada, 99% das telas tinham 96 dpi (ou pretendiam ser) e poucas disposições foram feitas para variações nesta área. Devido a uma grande variação nos tamanhos e densidades de tela, precisamos de uma maneira padrão para que as imagens ficassem bonitas em várias densidades e dimensões de tela.
A especificação HTML resolveu esse problema recentemente definindo um pixel de referência que os fabricantes usam para determinar o tamanho de um pixel CSS.
Usando o pixel de referência, um fabricante pode determinar o tamanho do pixel físico do dispositivo em relação ao pixel padrão ou ideal. Essa proporção é chamada de proporção de pixels do dispositivo.
Como calcular a proporção de pixels do dispositivo
Suponha que um smartphone tenha uma tela com um tamanho físico de pixel de 180 pixels por polegada (ppi). Para calcular a proporção de pixels do dispositivo, siga estas etapas:
Compare a distância real em que o dispositivo é mantido com a distância do pixel de referência.
De acordo com a especificação, sabemos que, a 28 polegadas, o ideal é 96 pixels por polegada. No entanto, como é um smartphone, as pessoas seguram o dispositivo mais perto do rosto do que seguram um laptop. Vamos estimar essa distância em 45 centímetros.
Multiplique a proporção de distância pela densidade padrão (96 ppi) para conseguir a densidade de pixels ideal para a distância especificada.
idealPixelDensity = (28/18) * 96 = 150 pixels por polegada (aproximadamente)
Use a proporção da densidade física de pixels em relação à densidade ideal de pixels para encontrar a proporção de pixels do dispositivo.
devicePixelRatio
= 180/150 = 1,2
![Como devicePixelRatio é calculado.](https://web.developers.google.cn/static/articles/high-dpi/image/how-devicepixelratio-is-c-74a8888e43bb4.png?hl=pt-br)
Assim, quando um navegador precisa saber como redimensionar uma imagem para ajustar a tela de acordo com a resolução ideal ou padrão, ele se refere à taxa de pixels do dispositivo de 1,2, o que significa que, para cada pixel ideal, esse dispositivo tem 1,2 pixels físicos. A fórmula para alternar entre pixels ideais (conforme definido pela especificação da Web) e físicos (pontos na tela do dispositivo) é a seguinte:
physicalPixels = window.devicePixelRatio * idealPixels
Historicamente, os fornecedores de dispositivos tendem a arredondar devicePixelRatios
(DPRs, na sigla em inglês). O iPhone e o iPad da Apple informam um DPR de 1, e os equivalentes
da Retina informam 2. A especificação do CSS recomenda que
a unidade de pixel se refere ao número inteiro de pixels do dispositivo que mais se aproxima do pixel de referência.
Uma razão pela qual as proporções arredondadas podem ser melhores é que elas podem levar a menos artefatos de subpixel.
No entanto, a realidade do cenário de dispositivos é muito mais variada, e os smartphones Android geralmente têm DPRs de 1,5. O tablet Nexus 7 tem um DPR de ~1,33, que foi alcançado por um cálculo semelhante ao acima. Mais dispositivos com DPRs variáveis serão lançados no futuro. Por isso, nunca presuma que seus clientes terão DPRs inteiros.
Visão geral das técnicas de imagem HiDPI
Há muitas técnicas para resolver o problema de mostrar as imagens de melhor qualidade o mais rápido possível, que se dividem em duas categorias:
- Otimizar imagens únicas e
- Otimização da seleção entre várias imagens.
Abordagens de imagem única: use uma imagem, mas faça algo inteligente com ela. Essas abordagens têm a desvantagem de que você inevitavelmente sacrificará o desempenho, já que vai fazer o download de imagens HiDPI mesmo em dispositivos mais antigos com DPI mais baixo. Confira algumas abordagens para o caso de imagem única:
- Imagem HiDPI altamente compactada
- Formato de imagem totalmente incrível
- Formato de imagem progressiva
Várias abordagens de imagem: use várias imagens, mas faça algo inteligente para escolher qual delas será carregada. Essas abordagens têm sobrecarga inerente para que o desenvolvedor crie várias versões do mesmo recurso e, em seguida, descubra uma estratégia de decisão. As opções são:
- JavaScript
- Entrega do lado do servidor
- Consultas de mídia CSS
- Recursos integrados do navegador (
image-set()
,<img srcset>
)
Imagem HiDPI altamente compactada
As imagens já representam 60% da largura de banda usada para fazer o download de um site médio. Ao exibir imagens HiDPI para todos os clientes, aumentaremos esse número. Quanto maior será o crescimento?
Fiz alguns testes que geraram fragmentos de imagem 1x e 2x com qualidade JPEG de 90, 50 e 20. Este é o script de shell que usei (usando o ImageMagick) para gerar:
![Exemplo de blocos 1.](https://web.developers.google.cn/static/articles/high-dpi/image/tiles-example-1-96a91faef342e.jpg?hl=pt-br)
![Exemplo 2 de blocos.](https://web.developers.google.cn/static/articles/high-dpi/image/tiles-example-2-bcd65cbbb9261.jpg?hl=pt-br)
![Exemplo de blocos 3.](https://web.developers.google.cn/static/articles/high-dpi/image/tiles-example-3-92bb904b4cb26.jpg?hl=pt-br)
Com base nessa amostra pequena e não científica, parece que a compactação de imagens grandes oferece uma boa relação entre qualidade e tamanho. Para mim, as imagens compactadas de 2x são melhores do que as imagens de 1x não compactadas.
É claro que veicular imagens de baixa qualidade e altamente compactadas para dispositivos 2x é pior do que veicular imagens de alta qualidade, e a abordagem acima gera penalidades na qualidade da imagem. Se você comparar a qualidade: 90 imagens com a qualidade: 20 imagens, vai notar uma queda na nitidez e um aumento do grão. Esses artefatos podem não ser aceitáveis em casos em que imagens de alta qualidade são essenciais (por exemplo, um aplicativo de visualização de fotos) ou para desenvolvedores de apps que não querem fazer concessões.
A comparação acima foi feita inteiramente com JPEGs compactados. Vale a pena observar que há muitas compensações entre os formatos de imagem amplamente implementados (JPEG, PNG, GIF), o que nos leva a…
Formato de imagem totalmente incrível
O WebP é um formato de imagem atraente que comprime muito bem, mantendo a alta fidelidade da imagem. Claro, ele ainda não foi implementado em todos os lugares.
Uma maneira de verificar o suporte ao WebP é pelo JavaScript. Você carrega uma imagem de 1 px
por data-uri, espera que os eventos de carregamento ou de erro sejam acionados e
verifica se o tamanho está correto. O Modernizr é enviado com
um script de detecção de recursos, que está disponível
pelo Modernizr.webp
.
No entanto, uma maneira melhor de fazer isso é diretamente no CSS usando a função image(). Portanto, se você tiver uma imagem WebP e um substituto JPEG, poderá escrever o seguinte:
#pic {
background: image("foo.webp", "foo.jpg");
}
Essa abordagem tem alguns problemas. Em primeiro lugar, image()
não é
amplamente implementado. Em segundo lugar, embora a compressão WebP supere o JPEG,
ela ainda é uma melhoria relativamente incremental,
cerca de 30% menor com base nesta galeria WebP (link em inglês). Portanto, o WebP
sozinho não é suficiente para resolver o problema de DPI alto.
Formatos de imagem progressiva
Formatos de imagem progressivos, como JPEG 2000, JPEG progressivo, PNG progressivo e GIF, têm a vantagem (um tanto debatida) de mostrar a imagem antes de ela ser totalmente carregada. Eles podem incorrer em algum overhead de tamanho, embora haja evidências conflitantes sobre isso. Jeff Atwood afirmou que o modo progressivo "adiciona cerca de 20% ao tamanho de imagens PNG e cerca de 10% ao tamanho de imagens JPEG e GIF". No entanto, Stoyan Stefanov afirmou que, para arquivos grandes, o modo progressivo é mais eficiente (na maioria dos casos).
À primeira vista, as imagens progressivas parecem muito promissoras no contexto de veicular as imagens de melhor qualidade o mais rápido possível. A ideia é que o navegador possa parar de fazer o download e a decodificação de uma imagem quando saber que dados adicionais não vão aumentar a qualidade da imagem (ou seja, todas as melhorias de fidelidade são de subpixel).
Embora as conexões sejam fáceis de encerrar, elas geralmente são caras para reiniciar. Para um site com muitas imagens, a abordagem mais eficiente é manter uma única conexão HTTP ativa, reutilizando-a pelo maior tempo possível. Se a conexão for encerrada prematuramente porque uma imagem foi transferida por download, o navegador precisará criar uma nova conexão, o que pode ser muito lento em ambientes de baixa latência.
Uma solução alternativa para isso é usar a solicitação HTTP Range, que permite que os navegadores especifiquem um intervalo de bytes para buscar. Um navegador inteligente pode fazer uma solicitação HEAD para acessar o cabeçalho, processá-lo, decidir quanto da imagem é realmente necessário e, em seguida, fazer a busca. Infelizmente, o intervalo HTTP tem pouco suporte em servidores da Web, o que torna essa abordagem impraticável.
Por fim, uma limitação óbvia dessa abordagem é que você não pode escolher qual imagem carregar, apenas variar a fidelidade da mesma imagem. Como resultado, isso não aborda o caso de uso de direção de arte.
Usar o JavaScript para decidir qual imagem carregar
A primeira e mais óbvia abordagem para decidir qual imagem carregar é
usar JavaScript no cliente. Essa abordagem permite que você descubra
tudo sobre o user agent e faça a coisa certa. É possível
determinar a proporção de pixels do dispositivo usando window.devicePixelRatio
, conferir a largura
e a altura da tela e até mesmo fazer um pouco de sniffing de conexão de rede
usando navigator.connection ou emitindo uma solicitação falsa, como a
biblioteca foresight.js. Depois de coletar todas
essas informações, você pode decidir qual imagem carregar.
Há aproximadamente um milhão de bibliotecas JavaScript que fazem algo como o acima, e, infelizmente, nenhuma delas é particularmente destacada.
Uma grande desvantagem dessa abordagem é que o uso do JavaScript significa que
você vai atrasar o carregamento da imagem até que o analisador look-ahead tenha
terminado. Isso significa que as imagens não vão começar
a ser transferidas até que o evento pageload
seja acionado. Saiba mais sobre isso no
artigo de Jason Grigsby.
Decidir qual imagem carregar no servidor
É possível adiar a decisão para o lado do servidor escrevendo manipuladores de solicitação personalizados para cada imagem que você serve. Esse manipulador verificaria o suporte da Retina com base no User-Agent (a única informação transmitida ao servidor). Em seguida, com base em se a lógica do lado do servidor quer servir ativos HiDPI, carregue o ativo apropriado (nomeado de acordo com alguma convenção conhecida).
Infelizmente, o User-Agent não fornece informações suficientes para decidir se um dispositivo precisa receber imagens de alta ou baixa qualidade. Além disso, é óbvio que qualquer coisa relacionada a User-Agent é um hack e deve ser evitada, se possível.
Usar consultas de mídia CSS
Por serem declarativas, as consultas de mídia do CSS permitem que você declare sua intenção e
permitem que o navegador faça a coisa certa em seu nome. Além do uso mais
comum de consultas de mídia, que corresponde ao tamanho do dispositivo, você também
pode corresponder a devicePixelRatio
. A consulta de mídia associada é
a proporção de pixels do dispositivo e tem variantes mínimas e máximas associadas, como
esperado. Se você quiser carregar imagens de alta DPI e a proporção de pixels do dispositivo
ultrapassar um limite, faça o seguinte:
#my-image { background: (low.png); }
@media only screen and (min-device-pixel-ratio: 1.5) {
#my-image { background: (high.png); }
}
Fica um pouco mais complicado com todos os prefixos de fornecedores misturados, principalmente por causa das diferenças na posição dos prefixos "min" e "max":
@media only screen and (min--moz-device-pixel-ratio: 1.5),
(-o-min-device-pixel-ratio: 3/2),
(-webkit-min-device-pixel-ratio: 1.5),
(min-device-pixel-ratio: 1.5) {
#my-image {
background:url(high.png);
}
}
Com essa abordagem, você recupera os benefícios da análise antecipada, que foi perdida com a solução JS. Você também ganha a flexibilidade de escolher os pontos de interrupção responsivos (por exemplo, é possível ter imagens de DPI baixa, média e alta), o que era perdido com a abordagem do lado do servidor.
Infelizmente, ele ainda é um pouco difícil de usar e leva a um CSS
com aparência estranha (ou requer pré-processamento). Além disso, essa abordagem é restrita a
propriedades CSS. Portanto, não há como definir um <img src>
, e todas as imagens
precisam ser elementos com um plano de fundo. Por fim, ao depender apenas da
proporção de pixels do dispositivo, você pode acabar em situações em que o smartphone
de alta DPI acaba fazendo o download de um recurso de imagem enorme 2x em uma
conexão EDGE. Essa não é a melhor experiência do usuário.
Usar novos recursos do navegador
Recentemente, houve muitas discussões sobre o suporte da plataforma da Web para
o problema de imagens de alta DPI. A Apple entrou recentemente no espaço,
trazendo a função CSS image-set() para o WebKit. Como resultado, o Safari e o Chrome oferecem suporte a ele. Como é uma função CSS, image-set()
não resolve o problema das tags <img>
. Insira
@srcset, que resolve esse problema, mas (no momento
da escrita) não tem implementações de referência (ainda!). A próxima seção
aborda mais detalhes sobre image-set
e srcset
.
Recursos do navegador para suporte a DPI alto
Em última análise, a decisão sobre qual abordagem você usa depende dos seus
requisitos específicos. No entanto, lembre-se de que todas as
abordagens mencionadas têm desvantagens. No entanto, quando
image-set
e srcset tiverem suporte amplo, eles serão as
soluções adequadas para esse problema. Por enquanto, vamos falar
sobre algumas práticas recomendadas que podem nos aproximar o máximo possível
desse futuro ideal.
Em primeiro lugar, qual é a diferença entre os dois? image-set()
é uma função CSS
adequada para uso como um valor da propriedade CSS de plano de fundo.
srcset é um atributo específico para elementos <img>
, com sintaxe semelhante.
As duas tags permitem especificar declarações de imagem, mas o atributo srcset
também permite configurar qual imagem carregar com base no tamanho da
viewport.
Práticas recomendadas para image-set
A função CSS image-set()
está disponível com o prefixo
-webkit-image-set()
. A sintaxe é bastante simples, usando uma ou mais
declarações de imagem separadas por vírgulas, que consistem em uma string de URL ou
função url()
seguida pela resolução associada. Exemplo:
background-image: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
Isso informa ao navegador que há duas imagens para escolher. Um deles é otimizado para telas 1x e o outro para telas 2x. O navegador pode escolher qual carregar com base em vários fatores, que podem incluir até mesmo a velocidade da rede, se o navegador for inteligente o suficiente (não implementado no momento).
Além de carregar a imagem correta, o navegador também a dimensionará da forma adequada. Em outras palavras, o navegador presume que duas imagens são duas vezes maiores do que imagens de 1x e, dessa forma, vai reduzir a imagem de 2x a um fator de 2 para que a imagem pareça ter o mesmo tamanho na página.
Em vez de especificar 1x, 1,5x ou Nx, você também pode especificar uma determinada densidade de pixels do dispositivo em dpi.
Isso funciona bem, exceto em navegadores que não oferecem suporte à propriedade image-set
, que não mostra nenhuma imagem. Isso é claramente ruim, então você
precisa usar um substituto (ou uma série de substitutos) para resolver esse problema:
background-image: url(icon1x.jpg);
background-image: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
/* This will be useful if image-set gets into the platform, unprefixed.
Also include other prefixed versions of this */
background-image: image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
O exemplo acima vai carregar o ativo apropriado em navegadores que oferecem suporte a
image-set e, caso contrário, utilizam o ativo de 1x. A ressalva óbvia
é que, embora o suporte ao navegador image-set()
seja baixo, a maioria dos user agents
recebe o recurso de 1x.
Esta demonstração usa o image-set()
para carregar a imagem
correta, voltando ao recurso 1x se essa função do CSS não tiver
suporte.
Neste ponto, você pode estar se perguntando por que não usar um polifil (ou seja,
criar um shim JavaScript para) image-set()
e encerrar o dia. Como
acontece, é bastante difícil implementar polyfills eficientes para funções
CSS. Para uma explicação detalhada, consulte esta discussão no estilo
www.
Srcset de imagem
Confira um exemplo de srcset:
<img alt="my awesome image"
src="banner.jpeg"
srcset="banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x">
Como você pode ver, além das declarações x que image-set
fornece,
o elemento srcset também usa valores w e h que correspondem ao
tamanho da viewport, tentando exibir a versão mais relevante. O
código acima serviria banner-phone.jpeg para dispositivos com largura de janela de visualização
menor que 640 px, banner-phone-HD.jpeg para dispositivos de alta DPI com tela pequena,
banner-HD.jpeg para dispositivos de alta DPI com telas maiores que 640 px e
banner.jpeg para todos os outros.
Como usar image-set para elementos de imagem
Como o atributo srcset em elementos img não é implementado na maioria dos
navegadores, pode ser tentador substituir os elementos img por <div>
s
com planos de fundo e usar a abordagem de imagem-set. Isso vai funcionar, com
algumas ressalvas. A desvantagem é que a tag <img>
tem valor semântico
de longa duração. Na prática, isso é importante principalmente para rastreadores da Web
e por motivos de acessibilidade.
Se você usar -webkit-image-set
, talvez sinta vontade de usar a
propriedade CSS de plano de fundo. A desvantagem dessa abordagem é que você precisa
especificar o tamanho da imagem, que é desconhecido se você estiver usando uma imagem que não seja 1x.
Em vez disso, use a propriedade CSS de conteúdo da seguinte maneira:
<div id="my-content-image"
style="content: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x);">
</div>
Isso vai dimensionar a imagem automaticamente com base no devicePixelRatio. Confira
este exemplo da técnica acima em ação,
com uma substituição adicional para url()
em navegadores que não oferecem suporte a
image-set
.
Polyfill srcset
Um recurso útil do srcset
é que ele vem com uma substituição natural.
No caso em que o atributo srcset não é implementado, todos os navegadores
sabem processar o atributo src. Além disso, como é apenas um atributo
HTML, é possível criar polyfills com
JavaScript.
Esse polyfill vem com testes de unidade para garantir que ele esteja o mais próximo possível da especificação. Além disso, há verificações em vigor que impedem que o polyfill execute qualquer código se o srcset for implementado de forma nativa.
Confira uma demonstração do polyfill em ação.
Conclusão
Não há uma solução mágica para resolver o problema de imagens de alta DPI.
A solução mais fácil é evitar imagens completamente e optar por SVG e CSS. No entanto, isso nem sempre é realista, especialmente se você tiver imagens de alta qualidade no seu site.
As abordagens em JS, CSS e no lado do servidor têm pontos fortes
e fracos. A abordagem mais promissora, no entanto, é aproveitar os novos
recursos do navegador. Embora o suporte do navegador para image-set
e srcset
ainda seja
incompleto, há substitutos razoáveis para usar hoje.
Para resumir, minhas recomendações são as seguintes:
- Para imagens de plano de fundo, use image-set com as alternativas adequadas para navegadores que não oferecem suporte a ele.
- Para imagens de conteúdo, use um polyfill srcset ou use o uso de image-set como substituto (consulte acima).
- Para situações em que você está disposto a sacrificar a qualidade da imagem, use imagens com compressão 2x.