Transformationen
Früher in diesem Leitfaden haben wir über das Canvas-Raster und den Koordinatenraum gelernt. Bis jetzt haben wir nur das Standardraster verwendet und die Größe des gesamten Canvas nach unseren Bedürfnissen angepasst. Mit Transformationen gibt es leistungsfähigere Methoden, um den Ursprung an eine andere Position zu verschieben, das Raster zu rotieren und es sogar zu skalieren.
Speichern und Wiederherstellen des Zustands
Bevor wir uns die Transformationsmethoden ansehen, betrachten wir zwei weitere Methoden, die unverzichtbar sind, sobald Sie beginnen, immer komplexere Zeichnungen zu 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 Zeichenstatus auf den Stapel gelegt. 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 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 den save
und restore
Canvas-Zustand
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. Wiederum ändern wir einige Zeicheneinstellungen 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 jedoch die erste restore()
-Anweisung aufrufen, wird der oberste Zeichenstatus vom Stapel entfernt und die Einstellungen werden wiederhergestellt. Wenn wir den Zustand nicht mit save()
gespeichert hätten, müssten wir die Füllfarbe und die Transparenz manuell ändern, um zum vorherigen Zustand zurückzukehren. Das wäre für zwei Eigenschaften einfach, aber wenn wir mehr 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
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 Raster zu verschieben.
translate(x, y)
-
Bewegt das Canvas und seinen Ursprung im Raster.
x
gibt die horizontale Entfernung an, die bewegt werden soll, undy
gibt an, wie weit das Raster vertikal bewegt werden soll.
Es ist eine gute Idee, den Canvas-Zustand zu speichern, bevor Sie Transformationen vornehmen. In den meisten Fällen ist es einfach, die Methode restore
aufzurufen, anstatt eine Rückübersetzung durchzuführen, um zum ursprünglichen Zustand zurückzukehren. Außerdem, wenn Sie innerhalb einer Schleife übersetzen und den Canvas-Zustand nicht speichern und wiederherstellen, könnte es sein, dass ein Teil Ihrer Zeichnung fehlt, da er außerhalb des Canvas-Randes gezeichnet wurde.
Ein Beispiel für translate
Dieses Beispiel zeigt einige der Vorteile der Übersetzung des Canvas-Ursprungs. Ohne die Methode translate()
würden alle Rechtecke an derselben Position (0,0) gezeichnet. Die Methode translate()
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 Funktion draw()
rufen wir die Funktion fillRect()
neunmal mit zwei for
-Schleifen auf. In jeder Schleife wird das Canvas übersetzt, das Rechteck gezeichnet und das Canvas in seinen 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 zum Zeichnen 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 das Canvas um den aktuellen Ursprung zu drehen.
rotate(angle)
-
Dreht das Canvas im Uhrzeigersinn um den aktuellen Ursprung um den
angle
in Radiant.
Der Rotationsmittelpunkt ist immer der Ursprung des Canvas. Um den Mittelpunkt zu ändern, müssen wir das Canvas mit der Methode translate()
verschieben.
Ein Beispiel für rotate
In diesem Beispiel verwenden wir die Methode rotate()
, um zuerst ein Rechteck vom Ursprung des Canvas und dann von der Mitte des Rechtecks selbst mit Hilfe von translate()
zu drehen.
Hinweis:
Winkel sind in Radiant, nicht in Grad. Um zu 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, verschieben wir das Canvas zum Zentrum des Rechtecks, dann drehen wir das Canvas, dann verschieben wir das Canvas zurück zu 0,0, und dann zeichnen wir das Rechteck.
Skalieren
Die nächste Transformationsmethode ist 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, die kleiner als 1.0 sind, reduzieren die Einheitsgröße, und Werte über 1.0 erhöhen die Einheitsgröße. Werte von 1.0 lassen die Einheiten gleich groß.
Mit negativen Zahlen können Sie eine Achsenspiegelung durchführen (zum Beispiel mit translate(0,canvas.height); scale(1,-1);
erhalten Sie das wohlbekannte kartesische Koordinatensystem mit dem Ursprung in der unteren linken Ecke).
Standardmäßig ist eine Einheit auf dem Canvas genau ein Pixel groß. Wenn wir beispielsweise einen Skalierungsfaktor von 0.5 anwenden, wird die resultierende Einheit 0.5 Pixel groß und so würden Formen halb so groß gezeichnet. Auf ähnliche Weise würde das Setzen des Skalierungsfaktors auf 2.0 die Einheitengröße erhöhen, und eine Einheit würde nun zwei Pixel groß sein. Dies führt dazu, dass Formen doppelt so groß gezeichnet werden.
Ein Beispiel für scale
In diesem letzten Beispiel werden wir Formen mit unterschiedlichen Skalierungsfaktoren zeichnen.
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 seine 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 Methode
transform()
mit denselben Argumenten auf. Dies macht im Grunde die aktuelle Transformation rückgängig und setzt die angegebene Transformation in einem Schritt. resetTransform()
-
Setzt die aktuelle Transformation auf die Einheitsmatrix zurück. Dies ist das gleiche wie:
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);
}