Transformationen
Früher in diesem Tutorial haben wir über das Canvas-Gitter und den Koordinatenraum gelernt. Bisher haben wir nur das Standardgitter verwendet und die Größe der gesamten Leinwand an unsere Bedürfnisse angepasst. Mit Transformationen gibt es jedoch leistungsstärkere 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 unerlässlich sind, sobald Sie immer komplexere Zeichnungen erstellen.
save()-
Speichert den gesamten Zustand der Leinwand.
restore()-
Stellt den zuletzt gespeicherten Zustand der Leinwand wieder her.
Canvas-Zustände werden in einem Stapel gespeichert. Jedes Mal, wenn die save()-Methode aufgerufen wird, wird der aktuelle Zeichenstatus auf den Stapel geschoben. Ein Zeichenstatus besteht aus:
- Den angewendeten Transformationen (d.h.
translate,rotateundscale– siehe unten). - Den aktuellen Werten der folgenden Attribute:
- Dem aktuellen Clipping-Pfad, den wir im nächsten Abschnitt sehen werden.
Sie können die save()-Methode so oft aufrufen, wie Sie möchten. Jedes Mal, wenn die restore()-Methode aufgerufen wird, wird der zuletzt gespeicherte Zustand vom Stapel entfernt und alle gespeicherten Einstellungen werden wiederhergestellt.
Ein Beispiel für den save und restore Canvas-Zustand
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
}
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. Erneut ändern wir einige Zeichnungseinstellungen und zeichnen das dritte halbtransparente weiße Rechteck.
Bisher ist dies ziemlich ähnlich zu dem, was wir in den vorherigen Abschnitten getan haben. Sobald wir jedoch die erste restore()-Anweisung aufrufen, wird der oberste Zeichenstatus vom Stapel 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. Dies wäre einfach bei 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 (der, den wir vor dem ersten Aufruf von save festgelegt haben) wiederhergestellt und das letzte Rechteck wird erneut in Schwarz gezeichnet.
Übersetzen
Die erste der Transformationsmethoden, die wir betrachten werden, ist translate(). Diese Methode wird verwendet, um die Leinwand und ihren Ursprung zu einem anderen Punkt im Gitter zu verschieben.
translate(x, y)-
Verschiebt die Leinwand und ihren Ursprung im Gitter.
xgibt die horizontale Verschiebung an, undygibt an, wie weit das Gitter vertikal verschoben werden soll.

Es ist eine gute Idee, den Leinwandzustand zu speichern, bevor Transformationen durchgeführt werden. 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 innerhalb einer Schleife übersetzen und den Leinwandzustand nicht speichern und wiederherstellen, könnte es passieren, dass Sie einen Teil Ihrer Zeichnung vermissen, weil sie außerhalb der Leinwandkante gezeichnet wurde.
Ein Beispiel für translate
Dieses Beispiel zeigt einige der Vorteile der Übersetzung des Leinwandursprungs. Ohne die Methode translate() würden alle Rechtecke an derselben Position (0,0) gezeichnet werden. Die Methode translate() gibt uns auch die Freiheit, das Rechteck überall auf der Leinwand zu platzieren, ohne die Koordinaten in der Funktion fillRect() manuell anpassen zu müssen. Dies macht es etwas einfacher zu verstehen und zu verwenden.
In der Funktion draw() rufen wir die Funktion fillRect() neun Mal mit zwei for-Schleifen auf. In jeder Schleife wird die Leinwand übersetzt, das Rechteck gezeichnet und die Leinwand in ihren ursprünglichen Zustand zurückversetzt. Beachten Sie, wie der Aufruf von fillRect() jedes Mal dieselben Koordinaten verwendet, wobei translate() die Zeichenposition anpasst.
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(). Wir verwenden sie, um die Leinwand um den aktuellen Ursprung zu drehen.
rotate(angle)-
Dreht die Leinwand im Uhrzeigersinn um den aktuellen Ursprung um den
angle-Wert in Bogenmaß.

Der Rotationsmittelpunkt ist immer der Ursprung der Leinwand. Um den Mittelpunkt zu ändern, müssen wir die Leinwand mit der Methode translate() verschieben.
Ein Beispiel für rotate
In diesem Beispiel verwenden wir die Methode rotate(), um zuerst ein Rechteck vom Ursprung der Leinwand und dann vom Zentrum des Rechtecks selbst mit Hilfe von translate() zu drehen.
Hinweis:
Winkel sind in Bogenmaß, nicht in Grad. Zur Umwandlung verwenden wir: radians = (Math.PI/180)*degrees.
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 sein eigenes Zentrum zu drehen, verschieben wir die Leinwand zum Zentrum des Rechtecks, drehen die Leinwand, verschieben die Leinwand zurück zu 0,0 und zeichnen dann das Rechteck.
Skalieren
Die nächste Transformationsmethode ist das Skalieren. Wir verwenden sie, um die Einheiten in unserem Leinwandgitter zu vergrößern oder zu verkleinern. Dies kann verwendet werden, um verkleinerte oder vergrößerte Formen und Bitmaps zu zeichnen.
scale(x, y)-
Skaliert die Leinwandeinheiten um x horizontal und um y vertikal. Beide Parameter sind reelle Zahlen. Werte, die kleiner als 1,0 sind, verkleinern die Einheitengröße, und Werte über 1,0 vergrößern die Einheitengröße. Werte von 1,0 lassen die Einheiten gleich groß bleiben.
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 der Leinwand 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 halb so groß gezeichnet. In ähnlicher Weise vergrößert das Setzen des Skalierungsfaktors auf 2,0 die Einheitengröße und eine Einheit wird jetzt zu zwei Pixeln. Dies führt dazu, dass Formen doppelt so groß gezeichnet werden.
Ein Beispiel für scale
In diesem letzten Beispiel zeichnen wir Formen mit unterschiedlichen Skalierungsfaktoren.
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);
}
Transformationen
Schließlich erlauben die folgenden Transformationsmethoden direkte Modifikationen an der Transformationsmatrix.
transform(a, b, c, d, e, f)-
Multipliziert die aktuelle Transformationsmatrix mit der Matrix, die durch ihre Argumente beschrieben wird. Die Transformationsmatrix wird beschrieben durch:
Wenn eines der Argumente
Infinityist, muss die Transformationsmatrix als unendlich markiert werden, anstatt dass die Methode eine Ausnahme wirft.
Die Parameter dieser Funktion sind:
a(m11)-
Horizontale Skalierung.
b(m12)-
Horizontales Scheren.
c(m21)-
Vertikales Scheren.
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 denselben Argumenten auf. Dies macht im Wesentlichen die aktuelle Transformation rückgängig und setzt die angegebene Transformation, alles in einem Schritt. resetTransform()-
Setzt die aktuelle Transformation auf die Einheitsmatrix zurück. Dies entspricht dem Aufruf von:
ctx.setTransform(1, 0, 0, 1, 0, 0);
Beispiel für transform und setTransform
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);
}