Skarp lærredsgengivelse på skærme med høj DPI og retina
Hvorfor lærredsgrafik ser sløret ud på Retina- og high-DPI-skærme, og hvordan man løser det med devicePixelRatio, inklusive håndtering af størrelse og ydeevne.
Core Takeaway
Et lærred ser sløret ud på skærme med høj tæthed, når dets pixelstørrelse matcher størrelsen CSS, fordi hver lærredspixel strækkes over flere fysiske pixels. Ret det ved at indstille lærredets bredde og højde til CSS-størrelsen ganget med window.devicePixelRatio, ved at holde CSS-størrelsen uændret og skalere tegnekonteksten med det samme forhold. Opdater den ved at ændre størrelse og zoome.
Hvorfor lærreder sløres
MDN beskriver devicePixelRatio som forholdet mellem fysiske pixels og CSS pixels på den aktuelle skærm: 1 på en klassisk 96 DPI-skærm og 2 på typiske HiDPI- eller Retina-skærme. Et lærred har sin egen pixelbuffer. Hvis bufferen er 300 gange 150 pixels, men vist med 300 gange 150 CSS pixels på en skærm med et forhold på 2, strækker browseren den over 600 gange 300 fysiske pixels, hvilket ser blødt ud.
Rettelsen
MDN's anbefalede tilgang er at bruge devicePixelRatio til at tilføje den ekstra pixeltæthed: Indstil lærredsbufferen til CSS størrelse gange forholdet, hold CSS størrelsen den samme, og skaler 2D konteksten, så din tegnekode kan blive ved med at bruge CSS pixels. Se koden nedenfor.
Håndter størrelsesændringer og zoom
Forholdet ændres, når et vindue flyttes til en anden skærm, eller brugeren zoomer på siden. Genberegn ved ændring af størrelse, og lyt efter ændringer i opløsningsmedieforespørgslen, så du kan tegne igen med den nye tæthed. En ResizeObserver på lærredselementet er en pålidelig måde at holde størrelsen synkroniseret med layoutet.
Ydeevnegrænser
Fordobling af forholdet firdobler antallet af pixels, der skal tegnes. På meget store lærreder eller lave enheder skal du begrænse forholdet, for eksempel til 2, og måle billedtider. Animation runtimes, der trækker til lærred, såsom Lottie's canvas renderer, Rive og ExodeUI, håndterer denne skalering for dig, men den samme afvejning gælder.
Snippet til produktionsimplementering
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 for at huske
- devicePixelRatio = fysiske pixels pr. CSS pixel (2 på typiske Retina-skærme)
- Indstil canvas.width/height til CSS størrelse × forhold; behold CSS størrelsen
- Skaler konteksten, så tegningskoden bruger CSS pixels
- Genberegn ved ændring af størrelse, overvåg ændring og zoom
- Afgrænse forholdet på store lærreder for ydeevne
Ofte stillede spørgsmål
Kilder
- Window: devicePixelRatio property — MDN Web Docs