Transformationen
Früher in diesem Leitfaden haben wir über das Canvas-Raster und den Koordinatenraum gelernt. Bisher haben wir nur das Standardraster verwendet und die Größe der gesamten Leinwand nach unseren Bedürfnissen angepasst. Mit Transformationen gibt es leistungsfähigere Möglichkeiten, den Ursprung an eine andere Position zu verschieben, das Raster zu drehen und es sogar zu skalieren.
Speichern und Wiederherstellen des Zustands
Bevor wir uns die Transformationsmethoden ansehen, betrachten wir zwei andere Methoden, die unverzichtbar 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 Stapelspeicher abgelegt. Jedes Mal, wenn die Methode save()
aufgerufen wird, wird der aktuelle Zeichenstatus auf den Stapel geschoben. Ein Zeichenstatus besteht aus
- Den angewendeten Transformationen (d.h.
translate
,rotate
undscale
– siehe unten). - Den aktuellen Werten der folgenden Attribute:
- Der aktuelle Clip-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 Zustand save
und restore
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 = "#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 kleinere blaue Rechteck und speichern den Zustand. Erneut ändern wir einige Zeichen-Einstellungen und zeichnen das dritte halbtransparente weiße Rechteck.
Bisher ist dies ziemlich ähnlich zu dem, was wir in den vorherigen Abschnitten gemacht haben. Sobald wir allerdings die erste restore()
-Anweisung aufrufen, wird der oberste Zeichen-Zustand vom Stapel entfernt und die Einstellungen werden wiederhergestellt. Hätten wir den Zustand nicht mit save()
gespeichert, müssten wir die Füllfarbe und Transparenz manuell ändern, um in den vorherigen Zustand zurückzukehren. Dies wäre bei zwei Eigenschaften einfach, 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 Zustand, 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 uns anschauen, ist translate()
. Diese Methode wird verwendet, um die Leinwand und ihren Ursprung an einen anderen Punkt im Raster zu verschieben.
translate(x, y)
-
Verschiebt die Leinwand und ihren Ursprung im Raster.
x
gibt die horizontale Verschiebung an undy
gibt an, wie weit das Raster vertikal verschoben werden soll.
Es ist sinnvoll, den Zustand der Leinwand zu speichern, bevor Transformationen durchgeführt werden. In den meisten Fällen ist es einfacher, die restore
-Methode aufzurufen, als eine Rückübersetzung durchführen zu müssen, um in den ursprünglichen Zustand zurückzukehren. Auch wenn Sie innerhalb einer Schleife übersetzen und den Zustand der Leinwand nicht speichern und wiederherstellen, könnten Sie am Ende Teile Ihrer Zeichnung verpassen, weil sie außerhalb des Leinwandrands gezeichnet wurden.
Ein Beispiel für translate
Dieses Beispiel zeigt einige Vorteile der Übersetzung des Leinwand-Ursprungs. Ohne die translate()
-Methode würden alle Rechtecke an derselben Position (0,0) gezeichnet werden. Die translate()
-Methode gibt uns auch die Freiheit, das Rechteck an beliebiger Stelle auf der Leinwand zu platzieren, ohne die Koordinaten in der fillRect()
-Funktion manuell anpassen zu müssen. Das macht es etwas einfacher zu verstehen und zu verwenden.
In der draw()
-Funktion rufen wir die fillRect()
-Funktion neunmal 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 und sich auf translate()
verlässt, um die Position der Zeichnung anzupassen.
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();
}
}
}
Rotieren
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
-Winkel in Radianten.
Der Drehpunkt ist immer der Leinwandursprung. Um den Mittelpunkt zu ändern, müssen wir die Leinwand mithilfe der translate()
-Methode verschieben.
Ein Beispiel für rotate
In diesem Beispiel verwenden wir die rotate()
-Methode, um zuerst ein Rechteck vom Leinwandursprung aus und dann vom Zentrum des Rechtecks selbst mit Hilfe von translate()
zu drehen.
Hinweis:
Winkel sind in Radiant, nicht in Grad. Zum Konvertieren verwenden wir: radians = (Math.PI/180)*degrees
.
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 die Leinwand zum Zentrum des Rechtecks, drehen dann die Leinwand, übersetzen 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 Canvas-Raster 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 Canvas-Einheiten horizontal um x und vertikal um y. Beide Parameter sind reelle Zahlen. Werte kleiner als 1.0 verkleinern die Einheitengröße und Werte größer als 1.0 vergrößern die Einheitengröß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);
haben 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, wird die resultierende Einheit 0.5 Pixel groß und somit würden Formen auf halber Größe gezeichnet. Ähnlich würde der Skalierungsfaktor auf 2.0 gesetzt die Einheitengröße vergrößern und eine Einheit wäre nun zwei Pixel. 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("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 ermöglichen die folgenden Transformationsmethoden direkte Modifikationen 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
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 Schrägung.
c
(m21
)-
Vertikale Schrägung.
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 hebt im Grunde die aktuelle Transformation auf und setzt die angegebene Transformation, alles in einem Schritt. resetTransform()
-
Setzt die aktuelle Transformation auf die Einheitsmatrix zurück. Dies ist dasselbe wie der Aufruf von:
ctx.setTransform(1, 0, 0, 1, 0, 0);
Beispiel für transform
und setTransform
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);
}