Start · Sprachen · JavaScript · Referenz · PaintWorkletGlobalScope.devicePixelRatio

PaintWorkletGlobalScope.devicePixelRatio

Eigenschaft

Schreibgeschützte Zahl innerhalb eines Paint Worklets, die angibt, wie viele physische Geräte-Pixel einem CSS-Pixel entsprechen.

seit JavaScript CSS Painting API (Houdini) – Chromium-Browser (Chr Kategorie: webapi

Signatur

PaintWorkletGlobalScope.devicePixelRatio

Beschreibung

devicePixelRatio ist eine schreibgeschützte Eigenschaft des globalen Gültigkeitsbereichs eines Paint Worklets (PaintWorkletGlobalScope). Sie entspricht window.devicePixelRatio im Hauptthread und gibt das Verhältnis von physischen Geräte-Pixeln zu logischen CSS-Pixeln an – auf einem klassischen Monitor meist 1, auf Retina- oder HiDPI-Displays 2, 3 oder auch Zwischenwerte wie 1.5 (Browser-Zoom und Betriebssystem-Skalierung eingeschlossen).

Da ein Worklet keinen Zugriff auf window hat, ist diese Eigenschaft der einzige Weg, innerhalb der paint()-Methode einer mit registerPaint() registrierten Klasse an die Pixeldichte zu gelangen. Sie ist direkt als globale Variable ansprechbar (devicePixelRatio bzw. globalThis.devicePixelRatio).

Wichtig: Der an paint() übergebene PaintRenderingContext2D ist bereits automatisch auf die Pixeldichte skaliert – man zeichnet in CSS-Pixeln und erhält trotzdem scharfe Ergebnisse. devicePixelRatio braucht man daher vor allem für Spezialfälle:

  • Haarlinien exakt 1 physischer Pixel breit (lineWidth = 1 / devicePixelRatio),
  • Ausrichten an Geräte-Pixeln (Pixel-Snapping), um verwaschene Kanten zu vermeiden,
  • Detailgrad anpassen, z. B. feinere Muster oder mehr Partikel auf hochauflösenden Displays.

Rückgabewert

Typ
number
Beschreibung
Gleitkommazahl > 0 mit dem aktuellen Verhältnis physischer Pixel zu CSS-Pixeln (z. B. 1, 1.25, 2, 3). Die Eigenschaft ist schreibgeschützt.

Beispiele

Haarlinien-Raster mit exakt 1 Geräte-Pixel Breite

// hairline-grid.js – wird als Paint Worklet geladen
registerPaint('hairline-grid', class {
  static get inputProperties() {
    return ['--grid-size', '--grid-color'];
  }

  paint(ctx, size, props) {
    const step = parseFloat(props.get('--grid-size')) || 20;
    const color = props.get('--grid-color').toString().trim() || '#ccc';

    // 1 physischer Pixel, unabhängig von der Pixeldichte
    const hairline = 1 / devicePixelRatio;
    ctx.strokeStyle = color;
    ctx.lineWidth = hairline;

    ctx.beginPath();
    for (let x = 0; x <= size.width; x += step) {
      // auf Geräte-Pixel ausrichten, damit die Linie nicht verschwimmt
      const px = Math.round(x * devicePixelRatio) / devicePixelRatio + hairline / 2;
      ctx.moveTo(px, 0);
      ctx.lineTo(px, size.height);
    }
    for (let y = 0; y <= size.height; y += step) {
      const py = Math.round(y * devicePixelRatio) / devicePixelRatio + hairline / 2;
      ctx.moveTo(0, py);
      ctx.lineTo(size.width, py);
    }
    ctx.stroke();
  }
});

Worklet im Hauptthread laden und per CSS verwenden

// main.js
if ('paintWorklet' in CSS) {
  CSS.paintWorklet.addModule('hairline-grid.js');
  console.log('Paint Worklet geladen, DPR im Hauptthread:', window.devicePixelRatio);
} else {
  // Fallback für Firefox/Safari
  document.documentElement.classList.add('no-houdini');
}

/* style.css
.canvas-area {
  --grid-size: 16;
  --grid-color: #3a7bd5;
  background-image: paint(hairline-grid);
}
.no-houdini .canvas-area {
  background-image: linear-gradient(#3a7bd5 1px, transparent 1px);
  background-size: 16px 16px;
}
*/
Paint Worklet geladen, DPR im Hauptthread: 2

Detailgrad an die Pixeldichte anpassen

// dots.js – mehr, feinere Punkte auf HiDPI-Displays
registerPaint('dots', class {
  paint(ctx, size) {
    const dpr = devicePixelRatio; // bei jedem Aufruf frisch lesen
    const spacing = dpr >= 2 ? 6 : 10;
    const radius = dpr >= 2 ? 1.25 : 2;

    ctx.fillStyle = 'rgba(0, 0, 0, 0.35)';
    for (let y = spacing / 2; y < size.height; y += spacing) {
      for (let x = spacing / 2; x < size.width; x += spacing) {
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.fill();
      }
    }
  }
});

// Wichtig · Fallstricke

  • Browser-Unterstützung: Die CSS Painting API (und damit PaintWorkletGlobalScope) gibt es nur in Chromium-basierten Browsern. Firefox und Safari unterstützen sie nicht – immer mit 'paintWorklet' in CSS prüfen und einen CSS-Fallback anbieten.
  • Kontext ist bereits skaliert: Anders als beim normalen <canvas> muss man den Kontext nicht manuell mit ctx.scale(devicePixelRatio, devicePixelRatio) skalieren. Wer das trotzdem tut, zeichnet alles doppelt so groß.
  • Nicht auf Modulebene zwischenspeichern: Der Wert kann sich durch Zoom oder Wechsel auf einen anderen Monitor ändern. Außerdem kann der Browser mehrere Worklet-Scopes erzeugen und verwerfen. devicePixelRatio daher innerhalb von paint() lesen, nicht einmalig beim Laden des Moduls.
  • Schreibgeschützt: Zuweisungen haben keine Wirkung (im Strict Mode – Worklet-Module sind immer strikt – wird ein TypeError ausgelöst).
  • Nur im sicheren Kontext (HTTPS oder localhost) verfügbar, da CSS.paintWorklet einen sicheren Kontext voraussetzt.