Wenn Sie sich die neuesten preisgekrönten Websites ansehen, bemerken Sie vielleicht viele ausgefallene Bildverzerrungsanimationen oder nette 3D-Effekte. Die meisten davon werden mit WebGL erstellt, einer API, die GPU-beschleunigte Bildverarbeitungseffekte und Animationen ermöglicht. Sie verwenden oft auch Bibliotheken, die auf WebGL aufbauen, wie z. B. three.js oder pixi.js. Beide sind sehr mächtige Werkzeuge zur Erstellung von 3D- bzw. 2D-Szenen.
Bedenken Sie jedoch, dass diese Bibliotheken ursprünglich nicht für die Erstellung von Diashows oder die Animation von DOM-Elementen konzipiert wurden. Es gibt jedoch eine Bibliothek, die speziell dafür entwickelt wurde, und wir werden in diesem Beitrag behandeln, wie Sie sie verwenden.

WebGL, CSS-Positionierung und Responsivität
Angenommen, Sie arbeiten mit einer Bibliothek wie three.js oder pixi.js und möchten sie für Interaktionen wie Mouseover- und Scroll-Events auf Elementen verwenden. Dabei könnten Sie auf Probleme stoßen! Wie positionieren Sie Ihre WebGL-Elemente relativ zum Dokument und zu anderen DOM-Elementen? Wie gehen Sie mit Responsivität um?
Genau das hatte ich im Sinn, als ich curtains.js entwickelt habe.
Curtains.js ermöglicht es Ihnen, Flächen mit Bildern und Videos (in WebGL werden wir sie Texturen nennen) zu erstellen, die sich wie einfache HTML-Elemente verhalten, wobei Position und Größe durch CSS-Regeln bestimmt werden. Diese Flächen können jedoch mit den endlosen Möglichkeiten von WebGL und Shadern erweitert werden.
Moment mal, Shader?
Shader sind kleine Programme, die in GLSL geschrieben sind und Ihrer GPU mitteilen, wie Ihre Flächen gerendert werden sollen. Das Verständnis der Funktionsweise von Shadern ist hier unerlässlich, da wir damit Animationen steuern. Wenn Sie noch nie davon gehört haben, sollten Sie sich vielleicht zuerst die Grundlagen aneignen. Es gibt viele gute Websites, um mit dem Erlernen zu beginnen, wie z. B. The Book of Shaders.
Nachdem Sie die Idee verstanden haben, erstellen wir unsere erste Fläche!
Einrichtung einer Basisebene
Um unsere erste Fläche anzuzeigen, benötigen wir etwas HTML, CSS und etwas JavaScript, um die Fläche zu erstellen. Dann werden unsere Shader sie animieren.
HTML
Das HTML ist hier wirklich einfach. Wir erstellen ein <div>, das unseren Canvas enthält, und ein <div>, das unser Bild enthält.
<body>
<!-- div that will hold our WebGL canvas -->
<div id="canvas"></div>
<!-- div used to create our plane -->
<div class="plane">
<!-- image that will be used as a texture by our plane -->
<img src="path/to/my-image.jpg" />
</div>
</body>
CSS
Wir verwenden CSS, um sicherzustellen, dass das <div>, das den Canvas umschließt, das Fenster ausfüllt, und weisen der plane-div eine beliebige Größe zu. (Unsere WebGL-Fläche hat exakt die gleiche Größe und Position wie diese div.)
Wir stellen auch einige grundlegende CSS-Regeln bereit, die im Falle eines Fehlers während der Initialisierung angewendet werden.
body {
/* make the body fit our viewport */
position: relative;
width: 100%;
height: 100vh;
margin: 0;
/* hide scrollbars */
overflow: hidden;
}
#canvas {
/* make the canvas wrapper fit the window */
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
}
.plane {
/* define the size of your plane */
width: 80%;
max-width: 1400px;
height: 80vh;
position: relative;
top: 10vh;
margin: 0 auto;
}
.plane img {
/* hide the img element */
display: none;
}
/*** in case of error show the image ***/
.no-curtains .plane {
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.no-curtains .plane img {
display: block;
max-width: 100%;
object-fit: cover;
}
JavaScript
Im JavaScript gibt es etwas mehr zu tun. Wir müssen unseren WebGL-Kontext instanziieren, eine Fläche mit einheitlichen Parametern erstellen und sie verwenden. Für dieses erste Beispiel sehen wir auch, wie Fehler abgefangen werden.
window.onload = function() {
// pass the id of the div that will wrap the canvas to set up our WebGL context and append the canvas to our wrapper
var webGLCurtain = new Curtains("canvas");
// if there's any error during init, we're going to catch it here
webGLCurtain.onError(function() {
// we will add a class to the document body to display original images
document.body.classList.add("no-curtains");
});
// get our plane element
var planeElement = document.getElementsByClassName("plane")[0];
// set our initial parameters (basic uniforms)
var params = {
vertexShaderID: "plane-vs", // our vertex shader ID
fragmentShaderID: "plane-fs", // our fragment shader ID
uniforms: {
time: {
name: "uTime", // uniform name that will be passed to our shaders
type: "1f", // this means our uniform is a float
value: 0,
},
}
}
// create our plane mesh
var plane = webGLCurtain.addPlane(planeElement, params);
// if our plane has been successfully created
// we use the onRender method of our plane fired at each requestAnimationFrame call
plane && plane.onRender(function() {
plane.uniforms.time.value++; // update our time uniform value
});
}
Shader
Wir müssen den Vertex-Shader schreiben. Grundsätzlich müssen wir die Position unserer Fläche basierend auf der Modell-, Ansichts- und Projektionsmatrix festlegen und die variablen Variablen an den Fragment-Shader weitergeben.
Eine dieser variablen Variablen heißt vTextureCoord und wird im Fragment-Shader verwendet, um unsere Textur auf die Fläche abzubilden. Wir könnten direkt unser aTextureCoord-Attribut übergeben, aber dann bekämen wir eine gestreckte Textur, da unsere Fläche und unser Bild nicht unbedingt das gleiche Seitenverhältnis haben. Glücklicherweise stellt die Bibliothek eine Texturmatrix-Uniform bereit, die wir verwenden könnten, um neue Koordinaten zu berechnen, die die Textur zuschneiden, sodass sie immer auf die Fläche passt (denken Sie daran als Äquivalent zu „background-size: cover“).
<!-- vertex shader -->
<script id="plane-vs" type="x-shader/x-vertex">
#ifdef GL_ES
precision mediump float;
#endif
// those are the mandatory attributes that the lib sets
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
// those are mandatory uniforms that the lib sets and that contain our model view and projection matrix
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
// our texture matrix uniform (this is the lib default name, but it could be changed)
uniform mat4 uTextureMatrix0;
// if you want to pass your vertex and texture coords to the fragment shader
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
void main() {
// get the vertex position from its attribute
vec3 vertexPosition = aVertexPosition;
// set its position based on projection and model view matrix
gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);
// set the varying variables
// thanks to the texture matrix we will be able to calculate accurate texture coords
// so that our texture will always fit our plane without being distorted
vTextureCoord = (uTextureMatrix0 * vec4(aTextureCoord, 0.0, 1.0)).xy;
vVertexPosition = vertexPosition;
}
</script>
Nun zu unserem Fragment-Shader. Hier fügen wir basierend auf unserer Zeit-Uniform und den Texturkoordinaten einen kleinen Verdrängungseffekt hinzu.
<!-- fragment shader -->
<script id="plane-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
// get our varying variables
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
// the uniform we declared inside our javascript
uniform float uTime;
// our texture sampler (this is the lib default name, but it could be changed)
uniform sampler2D uSampler0;
void main() {
// get our texture coords
vec2 textureCoord = vTextureCoord;
// displace our pixels along both axis based on our time uniform and texture UVs
// this will create a kind of water surface effect
// try to comment a line or change the constants to see how it changes the effect
// reminder : textures coords are ranging from 0.0 to 1.0 on both axis
const float PI = 3.141592;
textureCoord.x += (
sin(textureCoord.x * 10.0 + ((uTime * (PI / 3.0)) * 0.031))
+ sin(textureCoord.y * 10.0 + ((uTime * (PI / 2.489)) * 0.017))
) * 0.0075;
textureCoord.y += (
sin(textureCoord.y * 20.0 + ((uTime * (PI / 2.023)) * 0.023))
+ sin(textureCoord.x * 20.0 + ((uTime * (PI / 3.1254)) * 0.037))
) * 0.0125;
gl_FragColor = texture2D(uSampler0, textureCoord);
}
</script>
Et voilà! Sie sind fertig, und wenn alles gut gelaufen ist, sollten Sie so etwas sehen.
Siehe den Pen curtains.js basic plane von Martin Laxenaire (@martinlaxenaire) auf CodePen.
Hinzufügen von 3D und Interaktionen
Okay, das ist bisher ziemlich cool, aber wir haben diesen Beitrag mit 3D und Interaktionen begonnen, also schauen wir uns an, wie wir diese hinzufügen könnten.
Über Vertices
Um einen 3D-Effekt hinzuzufügen, müssten wir die Position der Flächen-Vertices im Vertex-Shader ändern. In unserem ersten Beispiel haben wir jedoch nicht angegeben, wie viele Vertices unsere Fläche haben soll, daher wurde sie mit einer Standardgeometrie erstellt, die sechs Vertices enthält und zwei Dreiecke bildet.

Um ordentliche 3D-Animationen zu erhalten, bräuchten wir mehr Dreiecke, also mehr Vertices.

Refactoring unseres JavaScripts
Glücklicherweise ist es einfach, unsere Flächendefinition anzugeben, da sie in unseren anfänglichen Parametern festgelegt werden kann.
Wir werden auch auf die Mausposition hören, um etwas Interaktion hinzuzufügen. Um dies richtig zu tun, müssen wir warten, bis die Fläche bereit ist, unsere Maus-Dokumenten-Koordinaten in unsere WebGL-Clip-Space-Koordinaten umwandeln und sie als Uniform an die Shader senden.
// we are using window onload event here but this is not mandatory
window.onload = function() {
// track the mouse positions to send it to the shaders
var mousePosition = {
x: 0,
y: 0,
};
// pass the id of the div that will wrap the canvas to set up our WebGL context and append the canvas to our wrapper
var webGLCurtain = new Curtains("canvas");
// get our plane element
var planeElement = document.getElementsByClassName("plane")[0];
// set our initial parameters (basic uniforms)
var params = {
vertexShaderID: "plane-vs", // our vertex shader ID
fragmentShaderID: "plane-fs", // our framgent shader ID
widthSegments: 20,
heightSegments: 20, // we now have 20*20*6 = 2400 vertices !
uniforms: {
time: {
name: "uTime", // uniform name that will be passed to our shaders
type: "1f", // this means our uniform is a float
value: 0,
},
mousePosition: { // our mouse position
name: "uMousePosition",
type: "2f", // notice this is a length 2 array of floats
value: [mousePosition.x, mousePosition.y],
},
mouseStrength: { // the strength of the effect (we will attenuate it if the mouse stops moving)
name: "uMouseStrength", // uniform name that will be passed to our shaders
type: "1f", // this means our uniform is a float
value: 0,
},
}
}
// create our plane mesh
var plane = webGLCurtain.addPlane(planeElement, params);
// if our plane has been successfully created we could start listening to mouse/touch events and update its uniforms
plane && plane.onReady(function() {
// set a field of view of 35 to exaggerate perspective
// we could have done it directly in the initial params
plane.setPerspective(35);
// listen our mouse/touch events on the whole document
// we will pass the plane as second argument of our function
// we could be handling multiple planes that way
document.body.addEventListener("mousemove", function(e) {
handleMovement(e, plane);
});
document.body.addEventListener("touchmove", function(e) {
handleMovement(e, plane);
});
}).onRender(function() {
// update our time uniform value
plane.uniforms.time.value++;
// continually decrease mouse strength
plane.uniforms.mouseStrength.value = Math.max(0, plane.uniforms.mouseStrength.value - 0.0075);
});
// handle the mouse move event
function handleMovement(e, plane) {
// touch event
if(e.targetTouches) {
mousePosition.x = e.targetTouches[0].clientX;
mousePosition.y = e.targetTouches[0].clientY;
}
// mouse event
else {
mousePosition.x = e.clientX;
mousePosition.y = e.clientY;
}
// convert our mouse/touch position to coordinates relative to the vertices of the plane
var mouseCoords = plane.mouseToPlaneCoords(mousePosition.x, mousePosition.y);
// update our mouse position uniform
plane.uniforms.mousePosition.value = [mouseCoords.x, mouseCoords.y];
// reassign mouse strength
plane.uniforms.mouseStrength.value = 1;
}
}
Nachdem unser JavaScript fertig ist, müssen wir unsere Shader umschreiben, damit sie unsere Mauspositions-Uniform verwenden.
Shader refactoring
Betrachten wir zuerst unseren Vertex-Shader. Wir haben drei Uniforms, die wir für unseren Effekt nutzen können:
- die Zeit, die ständig ansteigt
- die Mausposition
- unsere Mausstärke, die ständig abnimmt, bis zur nächsten Mausbewegung
Wir werden alle drei nutzen, um eine Art 3D-Ripple-Effekt zu erzeugen.
<script id="plane-vs" type="x-shader/x-vertex">
#ifdef GL_ES
precision mediump float;
#endif
// those are the mandatory attributes that the lib sets
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
// those are mandatory uniforms that the lib sets and that contain our model view and projection matrix
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
// our texture matrix uniform (this is the lib default name, but it could be changed)
uniform mat4 uTextureMatrix0;
// our time uniform
uniform float uTime;
// our mouse position uniform
uniform vec2 uMousePosition;
// our mouse strength
uniform float uMouseStrength;
// if you want to pass your vertex and texture coords to the fragment shader
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
void main() {
vec3 vertexPosition = aVertexPosition;
// get the distance between our vertex and the mouse position
float distanceFromMouse = distance(uMousePosition, vec2(vertexPosition.x, vertexPosition.y));
// this will define how close the ripples will be from each other. The bigger the number, the more ripples you'll get
float rippleFactor = 6.0;
// calculate our ripple effect
float rippleEffect = cos(rippleFactor * (distanceFromMouse - (uTime / 120.0)));
// calculate our distortion effect
float distortionEffect = rippleEffect * uMouseStrength;
// apply it to our vertex position
vertexPosition += distortionEffect / 15.0;
gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);
// varying variables
// thanks to the texture matrix we will be able to calculate accurate texture coords
// so that our texture will always fit our plane without being distorted
vTextureCoord = (uTextureMatrix0 * vec4(aTextureCoord, 0.0, 1.0)).xy;
vVertexPosition = vertexPosition;
}
</script>
Was den Fragment-Shader angeht, so halten wir ihn einfach. Wir werden Licht und Schatten basierend auf der Position jedes Vertices simulieren.
<script id="plane-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
// get our varying variables
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
// our texture sampler (this is the lib default name, but it could be changed)
uniform sampler2D uSampler0;
void main() {
// get our texture coords
vec2 textureCoords = vTextureCoord;
// apply our texture
vec4 finalColor = texture2D(uSampler0, textureCoords);
// fake shadows based on vertex position along Z axis
finalColor.rgb -= clamp(-vVertexPosition.z, 0.0, 1.0);
// fake lights based on vertex position along Z axis
finalColor.rgb += clamp(vVertexPosition.z, 0.0, 1.0);
// handling premultiplied alpha (useful if we were using a png with transparency)
finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
gl_FragColor = finalColor;
}
</script>
Und da haben Sie es!
Siehe den Pen curtains.js ripple effect example von Martin Laxenaire (@martinlaxenaire) auf CodePen.
Mit diesen beiden einfachen Beispielen haben wir gesehen, wie man eine Fläche erstellt und mit ihr interagiert.
Videos und Displacement Shader
Unser letztes Beispiel erstellt eine einfache Vollbild-Video-Slideshow mit einem Displacement-Shader zur Verbesserung der Übergänge.
Konzept des Displacement-Shaders
Der Displacement-Shader erzeugt einen schönen Verzerrungseffekt. Er wird in unserem Fragment-Shader mithilfe eines Graustufenbildes geschrieben und verschiebt die Pixelkoordinaten der Videos basierend auf den RGB-Werten der Textur. Hier ist das Bild, das wir verwenden werden

Der Effekt wird basierend auf jedem Pixel-RGB-Wert berechnet, wobei ein schwarzer Pixel [0, 0, 0] und ein weißer Pixel [1, 1, 1] (GLSL-Äquivalent für [255, 255, 255]) ist. Zur Vereinfachung verwenden wir nur den Rotkanalwert, da bei einem Graustufenbild Rot, Grün und Blau immer gleich sind.
Sie können versuchen, Ihr eigenes Graustufenbild zu erstellen (es funktioniert hervorragend mit geometrischen Formen), um Ihren einzigartigen Übergangseffekt zu erzielen.
Mehrere Texturen und Videos
Eine Fläche kann mehr als eine Textur haben, indem einfach mehrere Bild-Tags hinzugefügt werden. Dieses Mal möchten wir anstelle von Bildern Videos verwenden. Wir müssen lediglich die <img />-Tags durch ein <video />-Tag ersetzen. Beim Thema Video gibt es jedoch zwei Dinge zu beachten:
- Auf Mobilgeräten können Videos nicht ohne Benutzergeste, wie z. B. ein Klick-Event, automatisch abgespielt werden. Es ist daher sicherer, eine Schaltfläche „Seite betreten“ hinzuzufügen, um unsere Videos anzuzeigen und zu starten.
- Videos können erhebliche Auswirkungen auf Speicher, Leistung und Bandbreite haben. Sie sollten versuchen, sie so leicht wie möglich zu halten.
HTML
Das HTML ist immer noch recht einfach. Wir erstellen unseren Canvas-Div-Wrapper, unseren plane-Div, der die Texturen enthält, und eine Schaltfläche, um die Video-Autoplay-Funktion auszulösen. Beachten Sie die Verwendung des data-sampler-Attributs auf den Bild- und Video-Tags – es wird in unseren Shadern nützlich sein.
<body>
<div id="canvas"></div>
<!-- our plane -->
<div class="plane">
<!-- notice here we are using the data-sampler attribute to name our sampler uniforms -->
<img src="path/to/displacement.jpg" data-sampler="displacement" />
<video src="path/to/video.mp4" data-sampler="firstTexture"></video>
<video src="path/to/video-2.mp4" data-sampler="secondTexture"></video>
</div>
<div id="enter-site-wrapper">
<span id="enter-site">
Click to enter site
</span>
</div>
</body>
CSS
Die Stylesheet kümmert sich um ein paar Dinge: Anzeige der Schaltfläche und Ausblenden des Canvas, bevor der Benutzer die Seite betreten hat, und dafür sorgen, dass unser plane-Div das Fenster ausfüllt.
@media screen {
body {
margin: 0;
font-size: 18px;
font-family: 'PT Sans', Verdana, sans-serif;
background: #212121;
line-height: 1.4;
height: 100vh;
width: 100vw;
overflow: hidden;
}
/*** canvas ***/
#canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
z-index: 10;
/* hide the canvas until the user clicks the button */
opacity: 0;
transition: opacity 0.5s ease-in;
}
/* display the canvas */
.video-started #canvas {
opacity: 1;
}
.plane {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 15;
/* tell the user he can click the plane */
cursor: pointer;
}
/* hide the original image and videos */
.plane img, .plane video {
display: none;
}
/* center the button */
#enter-site-wrapper {
display: flex;
justify-content: center;
align-items: center;
align-content: center;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 30;
/* hide the button until everything is ready */
opacity: 0;
transition: opacity 0.5s ease-in;
}
/* show the button */
.curtains-ready #enter-site-wrapper {
opacity: 1;
}
/* hide the button after the click event */
.curtains-ready.video-started #enter-site-wrapper {
opacity: 0;
pointer-events: none;
}
#enter-site {
padding: 20px;
color: white;
background: #ee6557;
max-width: 200px;
text-align: center;
cursor: pointer;
}
}
JavaScript
Was das JavaScript angeht, gehen wir wie folgt vor:
- Setzen einiger Variablen zur Speicherung des Slideshow-Zustands.
- Erstellen des Curtains-Objekts und Hinzufügen der Fläche dazu.
- Wenn die Fläche bereit ist, auf ein Klick-Event warten, um die Videowiedergabe zu starten (beachten Sie die Verwendung der
playVideos()-Methode). Ein weiteres Klick-Event hinzufügen, um zwischen den beiden Videos zu wechseln. - Aktualisieren unseres Übergangs-Timer-Uniforms innerhalb der
onRender()-Methode.
window.onload = function() {
// here we will handle which texture is visible and the timer to transition between images
var activeTexture = 1;
var transitionTimer = 0;
// set up our WebGL context and append the canvas to our wrapper
var webGLCurtain = new Curtains("canvas");
// get our plane element
var planeElements = document.getElementsByClassName("plane");
// some basic parameters
var params = {
vertexShaderID: "plane-vs",
fragmentShaderID: "plane-fs",
imageCover: false, // our displacement texture has to fit the plane
uniforms: {
transitionTimer: {
name: "uTransitionTimer",
type: "1f",
value: 0,
},
},
}
var plane = webGLCurtain.addPlane(planeElements[0], params);
// if our plane has been successfully created
plane && plane.onReady(function() {
// display the button
document.body.classList.add("curtains-ready");
// when our plane is ready we add a click event listener that will switch the active texture value
planeElements[0].addEventListener("click", function() {
if(activeTexture == 1) {
activeTexture = 2;
}
else {
activeTexture = 1;
}
});
// click to play the videos
document.getElementById("enter-site").addEventListener("click", function() {
// display canvas and hide the button
document.body.classList.add("video-started");
// play our videos
plane.playVideos();
}, false);
}).onRender(function() {
// increase or decrease our timer based on the active texture value
// at 60fps this should last one second
if(activeTexture == 2) {
transitionTimer = Math.min(60, transitionTimer + 1);
}
else {
transitionTimer = Math.max(0, transitionTimer - 1);
}
// update our transition timer uniform
plane.uniforms.transitionTimer.value = transitionTimer;
});
}
Shader
Hier passiert die ganze Magie. Wie in unserem ersten Beispiel wird der Vertex-Shader nicht viel tun, und Sie müssen sich auf den Fragment-Shader konzentrieren, der einen „Dive-In“-Effekt erzeugt.
<script id="plane-vs" type="x-shader/x-vertex">
#ifdef GL_ES
precision mediump float;
#endif
// default mandatory variables
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
// our texture matrices
// notice how it matches our data-sampler attributes + "Matrix"
uniform mat4 firstTextureMatrix;
uniform mat4 secondTextureMatrix;
// varying variables
varying vec3 vVertexPosition;
// our displacement texture will use original texture coords attributes
varying vec2 vTextureCoord;
// our videos will use texture coords based on their texture matrices
varying vec2 vFirstTextureCoord;
varying vec2 vSecondTextureCoord;
// custom uniforms
uniform float uTransitionTimer;
void main() {
vec3 vertexPosition = aVertexPosition;
gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);
// varying variables
// texture coords attributes because we want our displacement texture to be contained
vTextureCoord = aTextureCoord;
// our videos texture coords based on their texture matrices
vFirstTextureCoord = (firstTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
vSecondTextureCoord = (secondTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
// vertex position as usual
vVertexPosition = vertexPosition;
}
</script>
<script id="plane-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
// all our varying variables
varying vec3 vVertexPosition;
varying vec2 vTextureCoord;
varying vec2 vFirstTextureCoord;
varying vec2 vSecondTextureCoord;
// custom uniforms
uniform float uTransitionTimer;
// our textures samplers
// notice how it matches our data-sampler attributes
uniform sampler2D firstTexture;
uniform sampler2D secondTexture;
uniform sampler2D displacement;
void main( void ) {
// our texture coords
vec2 textureCoords = vTextureCoord;
// our displacement texture
vec4 displacementTexture = texture2D(displacement, textureCoords);
// our displacement factor is a float varying from 1 to 0 based on the timer
float displacementFactor = 1.0 - (cos(uTransitionTimer / (60.0 / 3.141592)) + 1.0) / 2.0;
// the effect factor will tell which way we want to displace our pixels
// the farther from the center of the videos, the stronger it will be
vec2 effectFactor = vec2((textureCoords.x - 0.5) * 0.75, (textureCoords.y - 0.5) * 0.75);
// calculate our displaced coordinates of the first video
vec2 firstDisplacementCoords = vec2(vFirstTextureCoord.x - displacementFactor * (displacementTexture.r * effectFactor.x), vFirstTextureCoord.y- displacementFactor * (displacementTexture.r * effectFactor.y));
// opposite displacement effect on the second video
vec2 secondDisplacementCoords = vec2(vSecondTextureCoord.x - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.x), vSecondTextureCoord.y - (1.0 - displacementFactor) * (displacementTexture.r * effectFactor.y));
// apply the textures
vec4 firstDistortedColor = texture2D(firstTexture, firstDisplacementCoords);
vec4 secondDistortedColor = texture2D(secondTexture, secondDisplacementCoords);
// blend both textures based on our displacement factor
vec4 finalColor = mix(firstDistortedColor, secondDistortedColor, displacementFactor);
// handling premultiplied alpha
finalColor = vec4(finalColor.rgb * finalColor.a, finalColor.a);
// apply our shader
gl_FragColor = finalColor;
}
</script>
Hier ist unsere kleine Video-Slideshow mit einem coolen Übergangseffekt
Siehe den Pen curtains.js video slideshow von Martin Laxenaire (@martinlaxenaire) auf CodePen.
Dieses Beispiel zeigt Ihnen hervorragend, wie Sie mit curtains.js eine Slideshow erstellen: Sie möchten vielleicht Bilder anstelle von Videos verwenden, die Displacement-Textur ändern, den Fragment-Shader modifizieren…
Wir könnten auch weitere Folien hinzufügen, müssten dann aber den Texturwechsel handhaben. Dies werden wir hier nicht behandeln, aber Sie sollten wissen, dass es auf der Website der Bibliothek ein Beispiel gibt, das sich damit beschäftigt.
Tiefer eintauchen
Wir haben nur an der Oberfläche dessen gekratzt, was mit curtains.js möglich ist. Sie könnten versuchen, mehrere Flächen mit einem coolen Mouseover-Effekt für Ihre Artikel-Thumbnails zu erstellen. Die Möglichkeiten sind nahezu endlos.
Wenn Sie sich die vollständige API-Dokumentation ansehen oder weitere Beispiele zu all diesen grundlegenden Anwendungen sehen möchten, können Sie die Website der Bibliothek oder das GitHub-Repository besuchen.