Representación nítida de Canvas en pantallas Retina y de alto DPI
Por qué los gráficos del lienzo se ven borrosos en pantallas Retina y de alto DPI, y cómo solucionarlo con devicePixelRatio, incluido el manejo de cambio de tamaño y los límites de rendimiento.
Idea clave
Un lienzo se ve borroso en pantallas de alta densidad cuando su tamaño de píxel coincide con su tamaño CSS, porque cada píxel del lienzo se extiende sobre varios píxeles físicos. Solucionelo estableciendo el ancho y alto del lienzo en el tamaño CSS multiplicado por window.devicePixelRatio, manteniendo el tamaño CSS sin cambios y escalando el contexto del dibujo en la misma proporción. Actualícelo al cambiar el tamaño y hacer zoom.
¿Por qué los lienzos se desdibujan?
MDN describe devicePixelRatio como la proporción de píxeles físicos a píxeles CSS en la pantalla actual: 1 en una pantalla clásica de 96 DPI y 2 en pantallas HiDPI o Retina típicas. Un lienzo tiene su propio búfer de píxeles. Si ese búfer es de 300 por 150 píxeles pero se muestra en 300 por 150 píxeles CSS en una pantalla con una proporción de 2, el navegador lo extiende a más de 600 por 300 píxeles físicos, lo que parece suave.
La solución
El enfoque recomendado de MDN es usar devicePixelRatio para agregar la densidad de píxeles adicional: establezca el búfer del lienzo en el tamaño CSS multiplicado por la relación, mantenga el tamaño CSS igual y escale el contexto 2D para que su código de dibujo pueda seguir usando píxeles CSS. Vea el código a continuación.
Manejar cambios de tamaño y zoom
La proporción cambia cuando una ventana se mueve a otro monitor o el usuario hace zoom en la página. Vuelva a calcular al cambiar el tamaño y escuche los cambios en la consulta de medios de resolución para poder volver a dibujar con la nueva densidad. Un ResizeObserver en el elemento lienzo es una forma confiable de mantener el tamaño sincronizado con el diseño.
Límites de rendimiento
Duplicar la proporción cuadruplica los píxeles a dibujar. En lienzos muy grandes o dispositivos de gama baja, limite la proporción, por ejemplo a 2, y mida los tiempos de fotograma. Los tiempos de ejecución de animación que se dibujan en el lienzo, como el renderizador de lienzo de Lottie, Rive y ExodeUI, manejan esta escala por usted, pero se aplica la misma compensación.
Fragmento de implementación en producción
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
}Principios clave para recordar
- devicePixelRatio = píxeles físicos por píxel CSS (2 en pantallas Retina típicas)
- Establezca canvas.width/height en tamaño CSS × relación; mantener el tamaño CSS
- Escale el contexto para que el código de dibujo utilice píxeles CSS
- Recalcular al cambiar el tamaño, monitorear el cambio y hacer zoom
- Limite la proporción en lienzos grandes para mejorar el rendimiento
Preguntas frecuentes
Fuentes
- Window: devicePixelRatio property — Documentos web MDN