Transformationen

Früher in diesem Leitfaden haben wir über das Canvas-Gitter und den Koordinatenraum gelernt. Bisher haben wir nur das Standardgitter verwendet und die Größe des gesamten Canvas nach unseren Bedürfnissen angepasst. Mit Transformationen gibt es jedoch mächtigere Möglichkeiten, den Ursprung an eine andere Position zu verschieben, das Gitter zu drehen und es sogar zu skalieren.

Speichern und Wiederherstellen des Zustands

Bevor wir uns die Transformationsmethoden ansehen, betrachten wir zwei weitere Methoden, die unverzichtbar werden, sobald Sie immer 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 Stapel gespeichert. Jedes Mal, wenn die Methode save() aufgerufen wird, wird der aktuelle Zeichenzustand auf den Stapel geschoben. Ein Zeichenzustand besteht aus

Sie können die Methode save() so oft aufrufen, wie Sie möchten. Jedes Mal, wenn die Methode restore() aufgerufen wird, wird der zuletzt gespeicherte Zustand vom Stapel entfernt und alle gespeicherten Einstellungen werden wiederhergestellt.

Ein Beispiel für save und restore Canvas-Zustand

js
function draw() {
  const ctx = document.getElementById("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 = "#09F"; // 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 = "#FFF"; // 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
}

Der erste Schritt besteht darin, ein großes Rechteck mit den Standardeinstellungen zu zeichnen. Als Nächstes speichern wir diesen Zustand und ändern die Füllfarbe. Dann zeichnen wir das zweite und kleinere blaue Rechteck und speichern den Zustand. Wieder ändern wir einige Zeicheneinstellungen und zeichnen das dritte halbtransparente weiße Rechteck.

Bis jetzt ist das ziemlich ähnlich zu dem, was wir in den vorherigen Abschnitten getan haben. Sobald wir jedoch die erste restore()-Anweisung aufrufen, wird der oberste Zeichenzustand vom Stapel entfernt und die Einstellungen werden zurückgesetzt. Hätten wir den Zustand nicht mit save() gespeichert, müssten wir die Füllfarbe und Transparenz manuell ändern, um zum vorherigen Zustand zurückzukehren. Das wäre einfach für zwei Eigenschaften, aber wenn wir mehr als das haben, würde unser Code sehr schnell sehr lang werden.

Wenn die zweite restore()-Anweisung aufgerufen wird, wird der ursprüngliche Zustand (derjenige, den wir vor dem ersten Aufruf von save eingerichtet haben) wiederhergestellt und das letzte Rechteck wird erneut in Schwarz gezeichnet.

Übersetzen

Die erste der Transformationsmethoden, die wir uns ansehen werden, ist translate(). Diese Methode wird verwendet, um das Canvas und seinen Ursprung an einen anderen Punkt im Gitter zu verschieben.

translate(x, y)

Verschiebt das Canvas und seinen Ursprung im Gitter. x gibt die horizontale Entfernung an, die verschoben werden soll, und y gibt an, wie weit das Gitter vertikal verschoben werden soll.

Das Canvas wird nach unten und nach rechts verschoben, oder übersetzt, von seinem Ursprungspunkt im Gitter um 'x' Einheiten horizontal und 'y' Einheiten vertikal.

Es ist eine gute Idee, den Canvas-Zustand zu speichern, bevor Sie irgendwelche Transformationen durchführen. In den meisten Fällen ist es einfacher, die restore-Methode aufzurufen, als eine Rückübersetzung durchzuführen, um zum ursprünglichen Zustand zurückzukehren. Außerdem, wenn Sie in einer Schleife übersetzen und den Canvas-Zustand nicht speichern und wiederherstellen, könnten Sie letztendlich einen Teil Ihrer Zeichnung verpassen, weil sie außerhalb der Canvas-Rand gezeichnet wurde.

Ein translate-Beispiel

Dieses Beispiel demonstriert einige der Vorteile der Übersetzung des Canvas-Ursprungs. Ohne die translate()-Methode würden alle Rechtecke an der gleichen Position (0,0) gezeichnet. Die translate()-Methode gibt uns auch die Freiheit, das Rechteck überall auf dem Canvas zu platzieren, ohne die Koordinaten in der fillRect()-Funktion manuell anpassen zu müssen. Dies macht es etwas einfacher zu verstehen und zu verwenden.

In der draw()-Funktion rufen wir die fillRect()-Funktion neunmal unter Verwendung von zwei for-Schleifen auf. In jeder Schleife wird das Canvas übersetzt, das Rechteck gezeichnet und das Canvas wird wieder in seinen ursprünglichen Zustand versetzt. Beachten Sie, wie der Aufruf von fillRect() jedes Mal die gleichen Koordinaten verwendet und sich auf translate() verlässt, um die Zeichnungsposition anzupassen.

js
function draw() {
  const ctx = document.getElementById("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(). Wir verwenden sie, um das Canvas um den aktuellen Ursprung zu drehen.

rotate(angle)

Dreht das Canvas im Uhrzeigersinn um den aktuellen Ursprung um die angle-Anzahl von Bogenmaß.

Der Standardursprungspunkt befindet sich oben links, 0 Grad ist horizontal und nach rechts. Der Drehpunkt beginnt vom Ursprungspunkt und geht im Uhrzeigersinn.

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

Ein rotate-Beispiel

In diesem Beispiel verwenden wir die Methode rotate(), um zuerst ein Rechteck vom Canvas-Ursprung zu drehen und dann mit Hilfe von translate() vom Zentrum des Rechtecks selbst.

Hinweis: Winkel sind in Bogenmaß, nicht in Grad. Zum Umrechnen verwenden wir: radians = (Math.PI/180)*degrees.

js
function draw() {
  const ctx = document.getElementById("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 sein eigenes Zentrum zu drehen, übersetzen wir das Canvas zum Zentrum des Rechtecks, drehen dann das Canvas, übersetzen das Canvas zurück zu 0,0 und zeichnen dann das Rechteck.

Skalierung

Die nächste Transformationsmethode ist das Skalieren. Wir verwenden es, um die Einheiten in unserem Canvas-Gitter zu vergrößern oder zu verkleinern. Dies kann verwendet werden, um verkleinerte oder vergrößerte Formen und Bitmap-Bilder zu zeichnen.

scale(x, y)

Skaliert die Canvas-Einheiten horizontal um x und vertikal um y. Beide Parameter sind reelle Zahlen. Werte kleiner als 1.0 reduzieren die Einheitsgröße und Werte über 1.0 erhöhen die Einheitsgröße. Werte von 1.0 lassen die Einheiten unverändert.

Mit negativen Zahlen können Sie eine Achsenspiegelung durchführen (zum Beispiel mit translate(0,canvas.height); scale(1,-1); erhalten Sie das bekannte kartesische Koordinatensystem, mit dem Ursprung in der unteren linken Ecke).

Standardmäßig ist eine Einheit auf dem Canvas genau ein Pixel. Wenn wir beispielsweise einen Skalierungsfaktor von 0,5 anwenden, würde die resultierende Einheit 0,5 Pixel betragen und somit würden Formen in halber Größe gezeichnet. In ähnlicher Weise würde das Setzen des Skalierungsfaktors auf 2,0 die Einheitsgröße erhöhen, und eine Einheit entspräche nun zwei Pixeln. Dies führt dazu, dass Formen doppelt so groß gezeichnet werden.

Ein scale-Beispiel

In diesem letzten Beispiel zeichnen wir Formen mit unterschiedlichen Skalierungsfaktoren.

js
function draw() {
  const ctx = document.getElementById("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);
}

Transformationen

Schließlich erlauben die folgenden Transformationsmethoden, Modifikationen direkt an der Transformationsmatrix vorzunehmen.

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

Multipliziert die aktuelle Transformationsmatrix mit der durch ihre Argumente beschriebenen Matrix. Die Transformationsmatrix wird beschrieben durch:

[ a c e b d f 0 0 1 ] \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, anstatt dass die Methode eine Ausnahme auslöst.

Die Parameter dieser Funktion sind:

a (m11)

Horizontale Skalierung.

b (m12)

Horizontale Verzerrung.

c (m21)

Vertikale Verzerrung.

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 dann die transform()-Methode mit den gleichen Argumenten auf. Dies hebt im Wesentlichen die aktuelle Transformation auf und setzt die angegebene Transformation in einem Schritt.

resetTransform()

Setzt die aktuelle Transformation auf die Einheitsmatrix zurück. Dies ist dasselbe wie der Aufruf: ctx.setTransform(1, 0, 0, 1, 0, 0);

Beispiel für transform und setTransform

js
function draw() {
  const ctx = document.getElementById("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);
}