Ostré vykreslování plátna na obrazovkách s vysokým DPI a Retina
Proč grafika plátna vypadá na Retina a obrazovkách s vysokým DPI rozmazaně a jak to opravit pomocí devicePixelRatio, včetně manipulace se změnami velikosti a omezení výkonu.
Core Takeaway
Plátno vypadá na obrazovkách s vysokou hustotou rozmazaně, když jeho velikost pixelů odpovídá velikosti CSS, protože každý pixel plátna je roztažen přes několik fyzických pixelů. Opravte to nastavením šířky a výšky plátna na velikost CSS vynásobenou hodnotou window.devicePixelRatio, přičemž velikost CSS zachováte beze změny a změníte měřítko kontextu kresby ve stejném poměru. Aktualizujte jej při změně velikosti a přiblížení.
Proč se plátna rozmazávají
MDN popisuje devicePixelRatio jako poměr fyzických pixelů k CSS pixelům na aktuálním displeji: 1 na klasické obrazovce s rozlišením 96 DPI a 2 na typických obrazovkách HiDPI nebo Retina. Plátno má svůj vlastní pixel buffer. Pokud je tato vyrovnávací paměť 300 x 150 pixelů, ale zobrazuje se na obrazovce s poměrem 2 pixelů na 300 x 150 pixelů CSS, prohlížeč ji roztáhne na 600 x 300 fyzických pixelů, což vypadá měkce.
Oprava
Doporučený přístup MDN je použít devicePixelRatio k přidání další hustoty pixelů: nastavte vyrovnávací paměť plátna na velikost CSS krát poměr, ponechte stejnou velikost CSS a škálujte 2D kontext, aby váš kreslicí kód mohl nadále používat pixely CSS. Viz kód níže.
Zvládněte změny velikosti a zoom
Poměr se změní, když se okno přesune na jiný monitor nebo uživatel přiblíží stránku. Přepočítejte při změně velikosti a poslouchejte změny v dotazu na médium rozlišení, abyste mohli překreslit na novou hustotu. ResizeObserver na prvku canvas je spolehlivý způsob, jak udržet velikost v synchronizaci s rozložením.
Limity výkonu
Zdvojnásobení poměru zčtyřnásobí počet pixelů pro kreslení. Na velmi velkých plátnech nebo zařízeních nižší třídy omezte poměr, například na 2, a změřte časy snímků. Runtime animace, které kreslí na plátno, jako je canvas renderer Lottie, Rive a ExodeUI, zvládnou toto škálování za vás, ale platí stejný kompromis.
Fragment implementace výroby
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
}Klíč Principle k zapamatování
- devicePixelRatio = fyzické pixely na CSS pixel (2 na typických Retina obrazovkách)
- Nastavte canvas.width/height na CSS velikost × poměr; zachovat velikost CSS
- Změňte měřítko kontextu tak, aby kód výkresu používal CSS pixely
- Přepočítat na změnu velikosti, sledovat změny a zoom
- Uzavřete poměr na velkých plátnech pro výkon
Často kladené otázky
Zdroje
- Window: devicePixelRatio property — Webové dokumenty MDN