Renderização nítida de tela em telas de alto DPI e Retina
Por que os gráficos da tela ficam borrados em telas Retina e de alto DPI e como corrigir isso com devicePixelRatio, incluindo manipulação de redimensionamento e limites de desempenho.
Ponto principal
Uma tela parece desfocada em telas de alta densidade quando o tamanho do pixel corresponde ao tamanho CSS, porque cada pixel da tela é esticado sobre vários pixels físicos. Corrija-o definindo a largura e a altura da tela para o tamanho CSS multiplicado por window.devicePixelRatio, mantendo o tamanho CSS inalterado e dimensionando o contexto do desenho na mesma proporção. Atualize-o no redimensionamento e zoom.
Por que as telas ficam borradas
MDN descreve devicePixelRatio como a proporção de pixels físicos para pixels CSS na exibição atual: 1 em uma tela clássica de 96 DPI e 2 em telas HiDPI ou Retina típicas. Uma tela possui seu próprio buffer de pixels. Se esse buffer tiver 300 por 150 pixels, mas for mostrado em 300 por 150 pixels CSS em uma tela com uma proporção de 2, o navegador o estenderá para 600 por 300 pixels físicos, o que parece suave.
A correção
A abordagem recomendada de MDN é usar devicePixelRatio para adicionar a densidade de pixels extra: defina o buffer de tela para o tamanho CSS vezes a proporção, mantenha o tamanho CSS igual e dimensione o contexto 2D para que seu código de desenho possa continuar usando pixels CSS. Veja o código abaixo.
Lidar com redimensionamento e zoom
A proporção muda quando uma janela se move para outro monitor ou o usuário amplia a página. Recalcule ao redimensionar e ouça as alterações na consulta de mídia de resolução para poder redesenhar na nova densidade. Um ResizeObserver no elemento canvas é uma maneira confiável de manter o tamanho sincronizado com o layout.
Limites de desempenho
Dobrar a proporção quadruplica os pixels a serem desenhados. Em telas muito grandes ou dispositivos de baixo custo, limite a proporção, por exemplo, para 2, e meça os tempos dos quadros. Os tempos de execução de animação que são desenhados na tela, como o renderizador de tela do Lottie, Rive e ExodeUI, lidam com esse dimensionamento para você, mas a mesma compensação se aplica.
Trecho de implementação em produção
function setupCanvas(canvas) {
const ctx = canvas.getContext('2d');
function resize() {
const ratio = Math.min(window.devicePixelRatio || 1, 2); // cap for performance
const { width, height } = canvas.getBoundingClientRect(); // CSS size
canvas.width = Math.round(width * ratio); // backing store in device pixels
canvas.height = Math.round(height * ratio);
ctx.setTransform(ratio, 0, 0, ratio, 0, 0); // draw in CSS pixels
draw(ctx, width, height);
}
new ResizeObserver(resize).observe(canvas);
resize();
}
function draw(ctx, width, height) {
ctx.clearRect(0, 0, width, height);
ctx.lineWidth = 1;
ctx.strokeRect(0.5, 0.5, width - 1, height - 1); // crisp 1px border
}Princípios-chave para lembrar
- devicePixelRatio = pixels físicos por pixel CSS (2 em telas Retina típicas)
- Defina canvas.width/height como CSS tamanho × proporção; mantenha o tamanho CSS
- Dimensione o contexto para que o código de desenho use pixels CSS
- Recalcular ao redimensionar, monitorar alterações e aplicar zoom
- Limite a proporção em telas grandes para desempenho
Perguntas frequentes
Fontes
- Window: devicePixelRatio property — Documentos da Web MDN