Dieser Artikel konzentriert sich auf das Hinzufügen von WebGL-Effekten zu <image> und <video> Elementen einer bereits „fertigen“ Webseite. Obwohl es einige hilfreiche Ressourcen zu diesem Thema gibt (wie diese zwei), hoffe ich, dieses Thema zu vereinfachen, indem ich den Prozess in wenige Schritte unterteile:
- Erstellen Sie eine Webseite, wie Sie es normalerweise tun würden.
- Rendern Sie die Teile, zu denen Sie WebGL-Effekte hinzufügen möchten, mit WebGL.
- Erstellen (oder finden) Sie die zu verwendenden WebGL-Effekte.
- Fügen Sie Event-Listener hinzu, um Ihre Seite mit den WebGL-Effekten zu verbinden.
Wir werden uns speziell auf die *Verbindung* zwischen normalen Webseiten und WebGL konzentrieren. Was werden wir erstellen? Wie wäre es mit einem ziehbaren Bild-Slider mit einem interaktiven Maus-Hover!
Wir werden nicht auf die Kernfunktionalität eines Sliders eingehen und auch nicht sehr tief in die technischen Details von WebGL oder GLSL-Shadern. Es gibt jedoch viele Kommentare im Demo-Code und Links zu externen Ressourcen, wenn Sie mehr erfahren möchten.
Wir verwenden die neueste Version von WebGL (WebGL2) und GLSL (GLSL 300), die derzeit nicht in Safari oder im Internet Explorer funktionieren. Verwenden Sie also Firefox oder Chrome, um die Demos anzuzeigen. Wenn Sie planen, etwas von dem, was wir behandeln, in der Produktion zu verwenden, sollten Sie sowohl die GLSL 100- als auch die 300-Versionen der Shader laden und die GLSL 300-Version nur verwenden, wenn curtains.renderer._isWebGL2 wahr ist. Dies behandle ich in der obigen Demo.
Zuerst erstellen Sie eine Webseite, wie Sie es normalerweise tun würden
Sie wissen schon, HTML und CSS und so weiter. In diesem Fall erstellen wir einen Bild-Slider, aber das dient nur zur Demonstration. Wir werden uns nicht eingehend damit beschäftigen, wie man einen Slider erstellt (Robin hat einen netten Beitrag dazu). Aber hier ist, was ich zusammengestellt habe
- Jede Folie entspricht der vollen Breite der Seite.
- Nachdem eine Folie gezogen wurde, gleitet der Slider weiter in die Richtung des Zugs und verlangsamt sich allmählich mit Impuls.
- Der Impuls rastet den Slider am Endpunkt zur nächsten Folie ein.
- Jede Folie hat eine Austrittsanimation, die beim Start des Zugs ausgelöst wird, und eine Eintretenanimation, die beim Stoppen des Zugs ausgelöst wird.
- Beim Hovern über den Slider wird ein Hover-Effekt angewendet, ähnlich wie in diesem Video.
Ich bin ein großer Fan der GreenSock Animation Platform (GSAP). Sie ist hier besonders nützlich, da sie ein Plugin für das Ziehen, eines, das Impuls beim Ziehen ermöglicht, und eines zum Aufteilen von Text nach Zeilen bietet. Wenn Sie sich nicht wohl dabei fühlen, Slider mit GSAP zu erstellen, empfehle ich, etwas Zeit damit zu verbringen, sich mit dem Code in der obigen Demo vertraut zu machen.
Auch dies dient nur zur Demonstration, aber ich wollte die Komponente zumindest ein wenig beschreiben. Dies sind die DOM-Elemente, mit denen wir unser WebGL synchron halten werden.
Als Nächstes verwenden Sie WebGL, um die Teile zu rendern, die WebGL-Effekte enthalten werden
Jetzt müssen wir unsere Bilder in WebGL rendern. Dazu müssen wir
- Das Bild als Textur in einen GLSL-Shader laden.
- Eine WebGL-Ebene für das Bild erstellen und die Bildtextur korrekt auf die Ebene anwenden.
- Die Ebene dort positionieren, wo sich die DOM-Version des Bildes befindet, und sie korrekt skalieren.
Der dritte Schritt ist besonders nicht-trivial, wenn man reines WebGL verwendet, da wir die Position der DOM-Elemente verfolgen müssen, die wir in die WebGL-Welt übertragen möchten, während wir die DOM- und WebGL-Teile während des Scrollens und bei Benutzerinteraktionen synchron halten.
Es gibt tatsächlich eine Bibliothek, die uns dabei mit Leichtigkeit hilft: CurtainsJS! Es ist die einzige Bibliothek, die ich gefunden habe, die problemlos WebGL-Versionen von DOM-Bildern und -Videos erstellt und sie ohne viele zusätzliche Funktionen synchronisiert (aber ich wäre froh, wenn ich auf diesem Punkt widerlegt werde, also hinterlassen Sie bitte einen Kommentar, wenn Sie andere kennen, die das gut können).
Mit Curtains ist dies die gesamte JavaScript, die wir hinzufügen müssen
// Create a new curtains instance
const curtains = new Curtains({ container: "canvas", autoRender: false });
// Use a single rAF for both GSAP and Curtains
function renderScene() {
curtains.render();
}
gsap.ticker.add(renderScene);
// Params passed to the curtains instance
const params = {
vertexShaderID: "slider-planes-vs", // The vertex shader we want to use
fragmentShaderID: "slider-planes-fs", // The fragment shader we want to use
// Include any variables to update the WebGL state here
uniforms: {
// ...
}
};
// Create a curtains plane for each slide
const planeElements = document.querySelectorAll(".slide");
planeElements.forEach((planeEl, i) => {
const plane = curtains.addPlane(planeEl, params);
// const plane = new Plane(curtains, planeEl, params); // v7 version
// If our plane has been successfully created
if(plane) {
// onReady is called once our plane is ready and all its texture have been created
plane.onReady(function() {
// Add a "loaded" class to display the image container
plane.htmlElement.closest(".slide").classList.add("loaded");
});
}
});
Wir müssen auch unsere updateProgress Funktion aktualisieren, damit sie unsere WebGL-Ebenen aktualisiert.
function updateProgress() {
// Update the actual slider
animation.progress(wrapVal(this.x) / wrapWidth);
// Update the WebGL slider planes
planes.forEach(plane => plane.updatePosition());
}
Wir müssen auch einen sehr grundlegenden Vertex- und Fragment-Shader hinzufügen, um die Textur anzuzeigen, die wir laden. Das können wir tun, indem wir sie über <script> Tags laden, wie ich es in der Demo tue, oder indem wir Backticks verwenden, wie ich es in der letzten Demo zeige.
Auch hier wird dieser Artikel nicht ins Detail auf die technischen Aspekte dieser GLSL-Shader eingehen. Ich empfehle, The Book of Shaders und das WebGL-Thema auf Codrops als Ausgangspunkte zu lesen.
Wenn Sie nicht viel über Shader wissen, genügt es zu sagen, dass der Vertex-Shader die Ebenen *positioniert* und der Fragment-Shader die Pixel der Textur *verarbeitet*. Es gibt auch drei Variablenpräfixe, auf die ich hinweisen möchte
ins werden aus einem Datenpuffer übergeben. In Vertex-Shadern stammen sie von der CPU (unserem Programm). In Fragment-Shadern stammen sie vom Vertex-Shader.uniforms werden von der CPU (unserem Programm) übergeben.outs sind Ausgaben unserer Shader. In Vertex-Shadern werden sie in unseren Fragment-Shader übergeben. In Fragment-Shadern werden sie an den Framebuffer (das, was auf den Bildschirm gezeichnet wird) übergeben.
Sobald wir all dies zu unserem Projekt hinzugefügt haben, haben wir exakt dasselbe wie zuvor, *aber* unser Slider wird jetzt über WebGL angezeigt! Nett.
CurtainsJS konvertiert Bilder und Videos einfach in WebGL. Was das Hinzufügen von WebGL-Effekten zu Text angeht, gibt es verschiedene Methoden, aber die vielleicht gebräuchlichste ist, den Text auf ein <canvas> zu zeichnen und ihn dann als Textur im Shader zu verwenden (z.B. 1, 2). Es ist möglich, die meisten anderen HTML-Elemente mit html2canvas (oder Ähnlichem) zu rendern und dieses Canvas als Textur im Shader zu verwenden; dies ist jedoch nicht sehr performant.
Erstellen (oder finden) Sie die zu verwendenden WebGL-Effekte
Jetzt können wir WebGL-Effekte hinzufügen, da unser Slider mit WebGL gerendert wird. Lassen Sie uns die Effekte aufschlüsseln, die in unserem Inspirationsvideo zu sehen sind
- Die Farben des Bildes sind invertiert.
- Es gibt einen Radius um die Mausposition, der die normale Farbe zeigt und einen Fisheye-Effekt erzeugt.
- Der Radius um die Maus animiert von 0, wenn über den Slider gehovert wird, und animiert zurück auf 0, wenn nicht mehr gehovert wird.
- Der Radius springt nicht auf die Mausposition, sondern animiert im Laufe der Zeit dorthin.
- Das gesamte Bild verschiebt sich basierend auf der Mausposition in Bezug auf die Mitte des Bildes.
Beim Erstellen von WebGL-Effekten ist es wichtig zu bedenken, dass Shader keinen Zustand speichern, der zwischen den Frames existiert. Sie können etwas tun, basierend darauf, wo sich die Maus zu einem bestimmten Zeitpunkt befindet, aber sie können nicht von allein etwas tun, basierend darauf, wo sich die Maus *befunden hat*. Deshalb sollten wir für bestimmte Effekte, wie das Animieren des Radius, sobald die Maus den Slider betreten hat, oder das Animieren der Position des Radius im Laufe der Zeit, eine JavaScript-Variable verwenden und diesen Wert für jeden Frame des Sliders übergeben. Mehr dazu erfahren Sie im nächsten Abschnitt.
Sobald wir unsere Shader modifiziert haben, um die Farbe außerhalb des Radius zu invertieren und den Fisheye-Effekt innerhalb des Radius zu erzeugen, erhalten wir etwas Ähnliches wie die folgende Demo. Auch hier liegt der Fokus des Artikels auf der Verbindung zwischen DOM-Elementen und WebGL, daher werde ich nicht auf Details zu den Shadern eingehen, aber ich habe Kommentare hinzugefügt.
Aber das ist noch nicht sehr aufregend, da der Radius nicht auf unsere Maus reagiert. Das werden wir im nächsten Abschnitt behandeln.
Ich habe kein Repository mit vielen vorgefertigten WebGL-Shadern für normale Webseiten gefunden. Es gibt ShaderToy und VertexShaderArt (die einige wirklich erstaunliche Shader haben!), aber keines davon zielt auf die Art von Effekten ab, die auf den meisten Webseiten passen. Ich würde mir wirklich wünschen, dass jemand ein Repository mit WebGL-Shadern als Ressource für Leute erstellt, die an alltäglichen Seiten arbeiten. Wenn Sie eines kennen, lassen Sie es mich bitte wissen.
Fügen Sie Event-Listener hinzu, um Ihre Seite mit den WebGL-Effekten zu verbinden
Jetzt können wir dem WebGL-Teil Interaktivität hinzufügen! Wir müssen einige Variablen (Uniforms) an unsere Shader übergeben und diese Variablen beeinflussen, wenn der Benutzer mit unseren Elementen interagiert. Dies ist der Abschnitt, in dem ich am detailliertesten auf die Kernfunktion der Verbindung von JavaScript mit unseren Shadern eingehen werde.
Zuerst müssen wir einige Uniforms in unseren Shadern deklarieren. Wir benötigen die Mausposition nur in unserem Vertex-Shader
// The un-transformed mouse position
uniform vec2 uMouse;
Wir müssen den Radius und die Auflösung in unserem Fragment-Shader deklarieren
uniform float uRadius; // Radius of pixels to warp/invert
uniform vec2 uResolution; // Used in anti-aliasing
Dann fügen wir einige Werte für diese hinzu, innerhalb der Parameter, die wir an unsere Curtains-Instanz übergeben. Wir taten dies bereits für uResolution! Wir müssen den name der Variablen im Shader, ihren type und dann den Startvalue angeben
const params = {
vertexShaderID: "slider-planes-vs", // The vertex shader we want to use
fragmentShaderID: "slider-planes-fs", // The fragment shader we want to use
// The variables that we're going to be animating to update our WebGL state
uniforms: {
// For the cursor effects
mouse: {
name: "uMouse", // The shader variable name
type: "2f", // The type for the variable - https://webglfundamentals.org/webgl/lessons/webgl-shaders-and-glsl.html
value: mouse // The initial value to use
},
radius: {
name: "uRadius",
type: "1f",
value: radius.val
},
// For the antialiasing
resolution: {
name: "uResolution",
type: "2f",
value: [innerWidth, innerHeight]
}
},
};
Jetzt sind die Shader uniforms mit unserem JavaScript verbunden! An diesem Punkt müssen wir einige Event-Listener und Animationen erstellen, um die Werte zu beeinflussen, die wir in die Shader übergeben. Zuerst richten wir die Animation für den Radius ein und die Funktion zum Aktualisieren des Werts, den wir an unseren Shader übergeben
const radius = { val: 0.1 };
const radiusAnim = gsap.from(radius, {
val: 0,
duration: 0.3,
paused: true,
onUpdate: updateRadius
});
function updateRadius() {
planes.forEach((plane, i) => {
plane.uniforms.radius.value = radius.val;
});
}
Wenn wir die Radius-Animation abspielen, verwendet unser Shader jeden Takt den neuen Wert.
Wir müssen auch die Mausposition aktualisieren, wenn sie sich über unserem Slider befindet, sowohl für Mausgeräte als auch für Touchscreens. Hier ist viel Code, aber Sie können ihn ziemlich linear durchlaufen. Nehmen Sie sich Zeit und verarbeiten Sie, was passiert.
const mouse = new Vec2(0, 0);
function addMouseListeners() {
if ("ontouchstart" in window) {
wrapper.addEventListener("touchstart", updateMouse, false);
wrapper.addEventListener("touchmove", updateMouse, false);
wrapper.addEventListener("blur", mouseOut, false);
} else {
wrapper.addEventListener("mousemove", updateMouse, false);
wrapper.addEventListener("mouseleave", mouseOut, false);
}
}
// Update the stored mouse position along with WebGL "mouse"
function updateMouse(e) {
radiusAnim.play();
if (e.changedTouches && e.changedTouches.length) {
e.x = e.changedTouches[0].pageX;
e.y = e.changedTouches[0].pageY;
}
if (e.x === undefined) {
e.x = e.pageX;
e.y = e.pageY;
}
mouse.x = e.x;
mouse.y = e.y;
updateWebGLMouse();
}
// Updates the mouse position for all planes
function updateWebGLMouse(dur) {
// update the planes mouse position uniforms
planes.forEach((plane, i) => {
const webglMousePos = plane.mouseToPlaneCoords(mouse);
updatePlaneMouse(plane, webglMousePos, dur);
});
}
// Updates the mouse position for the given plane
function updatePlaneMouse(plane, endPos = new Vec2(0, 0), dur = 0.1) {
gsap.to(plane.uniforms.mouse.value, {
x: endPos.x,
y: endPos.y,
duration: dur,
overwrite: true,
});
}
// When the mouse leaves the slider, animate the WebGL "mouse" to the center of slider
function mouseOut(e) {
planes.forEach((plane, i) => updatePlaneMouse(plane, new Vec2(0, 0), 1) );
radiusAnim.reverse();
}
Wir sollten auch unsere bestehende updateProgress Funktion modifizieren, um unsere WebGL-Maus synchron zu halten.
// Update the slider along with the necessary WebGL variables
function updateProgress() {
// Update the actual slider
animation.progress(wrapVal(this.x) / wrapWidth);
// Update the WebGL slider planes
planes.forEach(plane => plane.updatePosition());
// Update the WebGL "mouse"
updateWebGLMouse(0);
}
Jetzt läuft es rund! Unser Slider erfüllt alle unsere Anforderungen.
Zwei zusätzliche Vorteile der Verwendung von GSAP für Ihre Animationen sind, dass es Zugriff auf Callbacks wie onComplete bietet und GSAP alles perfekt synchron hält, unabhängig von der Bildwiederholrate (z.B. diese Situation).
Sie machen von hier aus weiter!
Dies ist natürlich nur die Spitze des Eisbergs dessen, was wir mit dem Slider machen können, jetzt da er in WebGL ist. Zum Beispiel können gängige Effekte wie Turbulenz und Verschiebung zu den Bildern in WebGL hinzugefügt werden. Das Kernkonzept eines Verschiebungs-Effekts besteht darin, Pixel basierend auf einer Gradienten-Lichtkarte, die wir als Eingabequelle verwenden, zu verschieben. Wir können diese Textur (die ich dieser Verschiebung-Demo von Jesper Landberg entnommen habe — Sie sollten ihm folgen) als Quelle verwenden und sie dann in unseren Shader einbinden.
Um mehr über die Erstellung solcher Texturen zu erfahren, siehe diesen Artikel, diesen Tweet und dieses Tool. Mir sind keine bestehenden Repositories mit solchen Bildern bekannt, aber wenn Sie eines kennen, lassen Sie es mich bitte wissen.
Wenn wir die obige Textur einbinden und die Verschiebungsstärke und -intensität so animieren, dass sie sich im Laufe der Zeit und basierend auf unserer Ziehgeschwindigkeit ändern, erzeugt dies einen schönen, halb-zufälligen, aber natürlich aussehenden Verschiebungseffekt.
Es ist auch erwähnenswert, dass Curtains eine eigene React-Version hat, falls Sie so vorgehen möchten.
Das ist alles, was ich im Moment habe. Wenn Sie etwas mit dem erstellen, was Sie aus diesem Artikel gelernt haben, würde ich es gerne sehen! Verbinden Sie sich mit mir über Twitter.
Hi, WebGL2 ist in der neuesten Safari-Version verfügbar.
Wirklich seltsam... es scheint, als ob alles in Firefox 83 kaputt gegangen ist. Alles funktionierte einwandfrei, ich habe meinen Browser aktualisiert und danach funktionierte nichts mehr.
Zuerst dachte ich, mein Skript sei kaputt. Aber die Beispiele auf dieser Seite funktionierten auch nicht mehr, genauso wie die Beispiele auf der CurtainsJS-Website, und sie alle geben die gleiche Fehlermeldung aus.
mit der Warnung
Die Zeit, die es brauchte, um herauszufinden, was falsch war, hat meine Motivation, GLSL zu lernen, stark gedämpft.
Hallo Remo. Manchmal stürzt der WebGL-Renderer eines Browsers ab. Normalerweise kann dies durch einfaches Neustarten Ihres Browsers behoben werden. Löst dies das Problem für Sie?
Hallo! Guter Beitrag! Wissen Sie, wie man Curtains.js zu Elementor Pro hinzufügt? Ich dachte, ich müsste meine Seite wie gewohnt aufbauen, einen Container namens Canva einfügen, einen weiteren Plane oder Curtain und am Ende des Bodys ein Skript hinzufügen.
Ich weiß nicht, ob der Prozess gut ist und ob Sie mir erklären können, wie man Curtains.js mit Elementor Pro verwendet