Skarp lerretsgjengivelse på skjermer med høy DPI og retina
Hvorfor lerretsgrafikk ser uskarpt ut på Retina- og høy-DPI-skjermer, og hvordan fikser det med devicePixelRatio, inkludert endre størrelseshåndtering og ytelsesgrenser.
Kjerne takeaway
Et lerret ser uskarpt ut på skjermer med høy tetthet når pikselstørrelsen samsvarer med størrelsen CSS, fordi hver lerretspiksel er strukket over flere fysiske piksler. Fiks det ved å sette lerretets bredde og høyde til CSS-størrelsen multiplisert med window.devicePixelRatio, holde CSS-størrelsen uendret, og skalere tegnekonteksten med samme forhold. Oppdater den ved å endre størrelse og zoome.
Hvorfor lerreter uskarpt
MDN beskriver devicePixelRatio som forholdet mellom fysiske piksler og CSS piksler på gjeldende skjerm: 1 på en klassisk 96 DPI-skjerm og 2 på typiske HiDPI- eller Retina-skjermer. Et lerret har sin egen pikselbuffer. Hvis bufferen er 300 x 150 piksler, men vist med 300 x 150 CSS piksler på en skjerm med et forhold på 2, strekker nettleseren den over 600 x 300 fysiske piksler, noe som ser mykt ut.
Løsningen
MDNs anbefalte tilnærming er å bruke devicePixelRatio for å legge til den ekstra pikseltettheten: still lerretsbufferen til CSS-størrelse ganger forholdet, hold CSS-størrelsen den samme, og skaler 2D-konteksten slik at tegnekoden kan fortsette å bruke CSS-piksler. Se koden nedenfor.
Håndter størrelser og zoom
Forholdet endres når et vindu flyttes til en annen skjerm eller brukeren zoomer på siden. Beregn på nytt ved endring av størrelse, og lytt etter endringer i oppløsningsmediespørringen slik at du kan tegne på nytt med den nye tettheten. En ResizeObserver på lerretselementet er en pålitelig måte å holde størrelsen synkronisert med layout.
Ytelsesgrenser
Ved å doble forholdet firedobles pikslene som skal tegnes. På veldig store lerreter eller lavendte enheter, sett en tak for forholdet, for eksempel til 2, og mål bildetider. Animasjonskjøringer som trekker til lerret, som Lottie sin lerretsgjengivelse, Rive og ExodeUI, håndterer denne skaleringen for deg, men samme avveining gjelder.
Snippet for produksjonsimplementering
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
}Tast Principles å huske
- devicePixelRatio = fysiske piksler per CSS piksel (2 på typiske Retina-skjermer)
- Sett canvas.width/height til CSS størrelse × ratio; behold CSS-størrelsen
- Skaler konteksten slik at tegnekoden bruker CSS piksler
- Beregn på nytt ved endring av størrelse, overvåk endring og zoom
- Avgrens forholdet på store lerreter for ytelse
Ofte stilte spørsmål
Kilder
- Window: devicePixelRatio property — MDN Web Docs