Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.

View in English Always switch to English

Transformationen

Zuvor haben wir in diesem Tutorial das Canvas-Raster und den Koordinatenraum kennengelernt. Bisher haben wir nur das Standardraster verwendet und die Größe des gesamten Canvas an unsere Bedürfnisse angepasst. Transformationen bieten leistungsfähigere Möglichkeiten: Sie können den Ursprung an eine andere Position verschieben, das Raster drehen und es sogar skalieren.

Zustand speichern und wiederherstellen

Bevor wir uns die Transformationsmethoden ansehen, betrachten wir zwei weitere Methoden, die unverzichtbar sind, sobald Sie komplexere Zeichnungen erstellen.

save()

Speichert den gesamten Zustand des Canvas.

restore()

Stellt den zuletzt gespeicherten Canvas-Zustand wieder her.

Canvas-Zustände werden auf einem Stack gespeichert. Bei jedem Aufruf der Methode save() wird der aktuelle Zeichenzustand auf den Stack gelegt. Ein Zeichenzustand besteht aus:

Sie können die Methode save() beliebig oft aufrufen. Bei jedem Aufruf der Methode restore() wird der zuletzt gespeicherte Zustand vom Stack genommen und alle gespeicherten Einstellungen werden wiederhergestellt.

Beispiel für save und restore des Canvas-Zustands

js
function draw() {
  const ctx = document.getElementById("my-canvas").getContext("2d");

  ctx.fillRect(0, 0, 150, 150); // Draw a Black rectangle with default settings
  ctx.save(); // Save the original default state

  ctx.fillStyle = "#0099ff"; // Make changes to saved settings
  ctx.fillRect(15, 15, 120, 120); // Draw a Blue rectangle with new settings
  ctx.save(); // Save the current state

  ctx.fillStyle = "white"; // Make changes to saved settings
  ctx.globalAlpha = 0.5;
  ctx.fillRect(30, 30, 90, 90); // Draw a 50%-White rectangle with newest settings

  ctx.restore(); // Restore to previous state
  ctx.fillRect(45, 45, 60, 60); // Draw a rectangle with restored Blue setting

  ctx.restore(); // Restore to original state
  ctx.fillRect(60, 60, 30, 30); // Draw a rectangle with restored Black setting
}

Zuerst zeichnen wir mit den Standardeinstellungen ein großes Rechteck. Anschließend speichern wir diesen Zustand und ändern die Füllfarbe. Dann zeichnen wir das zweite, kleinere blaue Rechteck und speichern den Zustand erneut. Danach ändern wir wieder einige Zeicheneinstellungen und zeichnen das dritte, halbtransparente weiße Rechteck.

Bis hierhin ähnelt das stark dem, was wir in den vorherigen Abschnitten getan haben. Sobald wir jedoch die erste restore()-Anweisung aufrufen, wird der oberste Zeichenzustand vom Stack entfernt und die Einstellungen werden wiederhergestellt. Hätten wir den Zustand nicht mit save() gespeichert, müssten wir die Füllfarbe und die Transparenz manuell ändern, um zum vorherigen Zustand zurückzukehren. Bei zwei Eigenschaften wäre das einfach; bei mehr Eigenschaften würde unser Code jedoch sehr schnell sehr lang werden.

Beim Aufruf der zweiten restore()-Anweisung wird der ursprüngliche Zustand wiederhergestellt – also der Zustand vor dem ersten Aufruf von save – und das letzte Rechteck wird wieder schwarz gezeichnet.

Verschieben

Die erste Transformationsmethode, die wir betrachten, ist translate(). Mit ihr verschieben Sie den Canvas und seinen Ursprung an eine andere Stelle im Raster.

translate(x, y)

Verschiebt den Canvas und seinen Ursprung im Raster. x gibt die horizontale Verschiebung an, y die vertikale Verschiebung des Rasters.

Der Canvas wird von seinem Ursprungspunkt im Raster um „x“ Einheiten nach rechts und um „y“ Einheiten nach unten verschoben.

Es empfiehlt sich, den Canvas-Zustand vor jeder Transformation zu speichern. Meist ist es einfacher, die Methode restore aufzurufen, als die Verschiebung rückgängig zu machen, um zum ursprünglichen Zustand zurückzukehren. Wenn Sie innerhalb einer Schleife verschieben und den Canvas-Zustand nicht speichern und wiederherstellen, könnte zudem ein Teil Ihrer Zeichnung fehlen, weil er außerhalb des Canvas gezeichnet wurde.

Beispiel für translate

Dieses Beispiel zeigt einige Vorteile der Verschiebung des Canvas-Ursprungs. Ohne die Methode translate() würden alle Rechtecke an derselben Position (0,0) gezeichnet. Mit translate() können wir das Rechteck außerdem an einer beliebigen Stelle auf dem Canvas platzieren, ohne die Koordinaten in der Funktion fillRect() manuell anpassen zu müssen. Dadurch ist der Code etwas leichter zu verstehen und zu verwenden.

In der Funktion draw() rufen wir die Funktion fillRect() mithilfe zweier for-Schleifen neunmal auf. Bei jedem Schleifendurchlauf wird der Canvas verschoben, das Rechteck gezeichnet und der ursprüngliche Canvas-Zustand wiederhergestellt. Beachten Sie, dass der Aufruf von fillRect() jedes Mal dieselben Koordinaten verwendet: Die Zeichenposition wird durch translate() angepasst.

js
function draw() {
  const ctx = document.getElementById("my-canvas").getContext("2d");
  for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
      ctx.save();
      ctx.fillStyle = `rgb(${51 * i} ${255 - 51 * i} 255)`;
      ctx.translate(10 + j * 50, 10 + i * 50);
      ctx.fillRect(0, 0, 25, 25);
      ctx.restore();
    }
  }
}

Drehen

Die zweite Transformationsmethode ist rotate(). Mit ihr drehen wir den Canvas um den aktuellen Ursprung.

rotate(angle)

Dreht den Canvas um den aktuellen Ursprung um den in Radiant angegebenen angle im Uhrzeigersinn.

Der Standardursprung liegt oben links; 0 Grad entspricht einer horizontalen Richtung nach rechts. Die x-Achse geht vom Ursprungspunkt aus und dreht sich im Uhrzeigersinn.

Der Drehpunkt ist immer der Canvas-Ursprung. Um den Drehpunkt zu ändern, müssen wir den Canvas mit der Methode translate() verschieben.

Beispiel für rotate

In diesem Beispiel drehen wir mithilfe der Methode rotate() ein Rechteck zunächst um den Canvas-Ursprung und anschließend mithilfe von translate() um den Mittelpunkt des Rechtecks.

Hinweis: Winkel werden in Radiant angegeben, nicht in Grad. Für die Umrechnung verwenden wir: radians = (Math.PI/180)*degrees.

js
function draw() {
  const ctx = document.getElementById("my-canvas").getContext("2d");

  // left rectangles, rotate from canvas origin
  ctx.save();
  // blue rect
  ctx.fillStyle = "#0095DD";
  ctx.fillRect(30, 30, 100, 100);
  ctx.rotate((Math.PI / 180) * 25);
  // grey rect
  ctx.fillStyle = "#4D4E53";
  ctx.fillRect(30, 30, 100, 100);
  ctx.restore();

  // right rectangles, rotate from rectangle center
  // draw blue rect
  ctx.fillStyle = "#0095DD";
  ctx.fillRect(150, 30, 100, 100);

  ctx.translate(200, 80); // translate to rectangle center
  // x = x + 0.5 * width
  // y = y + 0.5 * height
  ctx.rotate((Math.PI / 180) * 25); // rotate
  ctx.translate(-200, -80); // translate back

  // draw grey rect
  ctx.fillStyle = "#4D4E53";
  ctx.fillRect(150, 30, 100, 100);
}

Um das Rechteck um seinen eigenen Mittelpunkt zu drehen, verschieben wir den Canvas zum Mittelpunkt des Rechtecks, drehen ihn, verschieben ihn zurück nach 0,0 und zeichnen dann das Rechteck.

Skalieren

Die nächste Transformationsmethode ist die Skalierung. Mit ihr vergrößern oder verkleinern wir die Einheiten im Canvas-Raster. So lassen sich Formen und Bitmaps verkleinert oder vergrößert zeichnen.

scale(x, y)

Skaliert die Canvas-Einheiten horizontal um x und vertikal um y. Beide Parameter sind reelle Zahlen. Werte unter 1,0 verkleinern die Einheiten, Werte über 1,0 vergrößern sie. Bei 1,0 bleibt ihre Größe unverändert.

Mit negativen Zahlen können Sie Achsen spiegeln. Wenn Sie beispielsweise translate(0,canvas.height); scale(1,-1); verwenden, erhalten Sie das bekannte kartesische Koordinatensystem mit dem Ursprung in der linken unteren Ecke.

Standardmäßig entspricht eine Einheit auf dem Canvas genau einem Pixel. Wenden wir beispielsweise einen Skalierungsfaktor von 0,5 an, entspricht eine Einheit anschließend 0,5 Pixeln; Formen werden dadurch halb so groß gezeichnet. Setzen wir den Skalierungsfaktor dagegen auf 2,0, entspricht eine Einheit zwei Pixeln. Die Formen werden dann doppelt so groß gezeichnet.

Beispiel für scale

In diesem letzten Beispiel zeichnen wir Formen mit unterschiedlichen Skalierungsfaktoren.

js
function draw() {
  const ctx = document.getElementById("my-canvas").getContext("2d");

  // draw a simple rectangle, but scale it.
  ctx.save();
  ctx.scale(10, 3);
  ctx.fillRect(1, 10, 10, 10);
  ctx.restore();

  // mirror horizontally
  ctx.scale(-1, 1);
  ctx.font = "48px serif";
  ctx.fillText("MDN", -135, 120);
}

Transformationsmatrizen

Die folgenden Transformationsmethoden ermöglichen schließlich direkte Änderungen an der Transformationsmatrix.

transform(a, b, c, d, e, f)

Multipliziert die aktuelle Transformationsmatrix mit der durch die Argumente beschriebenen Matrix. Die Transformationsmatrix wird wie folgt dargestellt:

[acebdf001]\left[ \begin{array}{ccc} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{array} \right]

Wenn eines der Argumente Infinity ist, muss die Transformationsmatrix als unendlich markiert werden, statt dass die Methode eine Ausnahme auslöst.

Die Parameter dieser Funktion sind:

a (m11)

Horizontale Skalierung.

b (m12)

Horizontale Scherung.

c (m21)

Vertikale Scherung.

d (m22)

Vertikale Skalierung.

e (dx)

Horizontale Verschiebung.

f (dy)

Vertikale Verschiebung.

setTransform(a, b, c, d, e, f)

Setzt die aktuelle Transformation auf die Einheitsmatrix zurück und ruft anschließend die Methode transform() mit denselben Argumenten auf. Damit wird die aktuelle Transformation rückgängig gemacht und die angegebene Transformation in einem Schritt festgelegt.

resetTransform()

Setzt die aktuelle Transformation auf die Einheitsmatrix zurück. Das entspricht dem Aufruf von ctx.setTransform(1, 0, 0, 1, 0, 0);.

Beispiel für transform und setTransform

js
function draw() {
  const ctx = document.getElementById("my-canvas").getContext("2d");

  const sin = Math.sin(Math.PI / 6);
  const cos = Math.cos(Math.PI / 6);
  ctx.translate(100, 100);
  let c = 0;
  for (let i = 0; i <= 12; i++) {
    c = Math.floor((255 / 12) * i);
    ctx.fillStyle = `rgb(${c} ${c} ${c})`;
    ctx.fillRect(0, 0, 100, 10);
    ctx.transform(cos, sin, -sin, cos, 0, 0);
  }

  ctx.setTransform(-1, 0, 0, 1, 100, 100);
  ctx.fillStyle = "rgb(255 128 255 / 50%)";
  ctx.fillRect(0, 50, 100, 100);
}