Rendering nitido della tela su schermi Retina e ad alto DPI
Perché la grafica su tela appare sfocata sugli schermi Retina e a DPI elevato e come risolvere il problema con devicePixelRatio, inclusa la gestione del ridimensionamento e i limiti delle prestazioni.
Punto chiave
Una tela appare sfocata sugli schermi ad alta densità quando la sua dimensione in pixel corrisponde alla dimensione CSS, perché ciascun pixel della tela è allungato su diversi pixel fisici. Risolvi il problema impostando la larghezza e l'altezza della tela sulla dimensione CSS moltiplicata per window.devicePixelRatio, mantenendo la dimensione CSS invariata e ridimensionando il contesto del disegno con lo stesso rapporto. Aggiornalo su ridimensionamento e zoom.
Perché le tele si sfocano
MDN descrive devicePixelRatio come il rapporto tra pixel fisici e CSS pixel sul display corrente: 1 su un classico schermo a 96 DPI e 2 sui tipici schermi HiDPI o Retina. Una tela ha il proprio buffer di pixel. Se il buffer è di 300 x 150 pixel ma viene visualizzato a 300 x 150 CSS pixel su uno schermo con un rapporto di 2, il browser lo allunga di oltre 600 x 300 pixel fisici, il che sembra morbido.
La soluzione
L'approccio consigliato da MDN consiste nell'utilizzare devicePixelRatio per aggiungere densità di pixel aggiuntiva: impostare il buffer di tela sulla dimensione CSS moltiplicata per il rapporto, mantenere la stessa dimensione CSS e ridimensionare il contesto 2D in modo che il codice di disegno possa continuare a utilizzare i pixel CSS. Vedi il codice qui sotto.
Gestire ridimensionamenti e zoom
Il rapporto cambia quando una finestra si sposta su un altro monitor o l'utente ingrandisce la pagina. Ricalcola il ridimensionamento e ascolta le modifiche alla query multimediale di risoluzione in modo da poter ridisegnare con la nuova densità. Un ResizeObserver sull'elemento canvas è un modo affidabile per mantenere le dimensioni sincronizzate con il layout.
Limiti delle prestazioni
Raddoppiando il rapporto si quadruplicano i pixel da disegnare. Su tele molto grandi o dispositivi di fascia bassa, limiti il rapporto, ad esempio a 2, e misuri i tempi dei fotogrammi. I runtime di animazione che disegnano su tela, come il renderer tela di Lottie, Rive e ExodeUI, gestiscono questo ridimensionamento per te, ma si applica lo stesso compromesso.
Estratto di implementazione in produzione
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
}Principi chiave da ricordare
- devicePixelRatio = pixel fisici per pixel CSS (2 sui tipici schermi Retina)
- Imposta canvas.width/height su CSS dimensione × rapporto; mantenere la dimensione CSS
- Ridimensiona il contesto in modo che il codice del disegno utilizzi i pixel CSS
- Ricalcola il ridimensionamento, monitora la modifica e lo zoom
- Limita il rapporto su tele di grandi dimensioni per ottenere prestazioni migliori
Domande frequenti
Fonti
- Window: devicePixelRatio property — MDN Documenti Web