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:
- Den angewendeten Transformationen (d.h.
translate,rotateundscale– siehe unten). - Den aktuellen Werten der folgenden Attribute:
- Dem aktuellen Beschneidungspfad, den wir im nächsten Abschnitt kennenlernen.
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
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.
xgibt die horizontale Verschiebung an,ydie vertikale Verschiebung des Rasters.
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.
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
angleim 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.
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.
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:
Wenn eines der Argumente
Infinityist, 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
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);
}