Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

HTML

              
                <body>

  <div id="canvas"></div>

  <div class="plane">
    <!-- notice here we are using the data-sampler attribute to name our sampler uniforms -->
    <img src="https://www.martin-laxenaire.fr/csstricks/images/displacement.jpg" data-sampler="displacement" crossorigin="anonymous" />
    <video src="https://www.martin-laxenaire.fr/csstricks/videos/plane-video-texture.mp4" data-sampler="firstTexture" crossorigin="anonymous"></video>
    <video src="https://www.martin-laxenaire.fr/csstricks/videos/plane-video-texture-2.mp4" data-sampler="secondTexture" crossorigin="anonymous"></video>
  </div>

  <div id="enter-site-wrapper" class="flex-wrapper">
    <span id="enter-site">
      Click to enter site
    </span>
  </div>

  <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;

    // varyings
    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);

      // varyings
      // 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 varyings
    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>


  <script src="https://www.curtainsjs.com/build/curtains.min.js" type="text/javascript"></script>
              
            
!

CSS

              
                @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;

        opacity: 0;
        transition: opacity 0.5s ease-in;
    }

    .video-started #canvas {
        opacity: 1;
    }

    .plane {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        cursor: pointer;
        z-index: 15;
    }

    .plane img, .plane video {
        display: none;
    }


    #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;

        opacity: 0;
        transition: opacity 0.5s ease-in;
    }

    .curtains-ready #enter-site-wrapper {
        opacity: 1;
    }

    .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;
    }

}


@media screen and (max-width: 1000px) {

    .multi-textures {
        font-size: 2em;
    }

}

              
            
!

JS

              
                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({
      container: "canvas"
    });

    // get our plane element
    var planeElements = document.getElementsByClassName("plane");

    // some basic parameters
    // we don't need to specifiate vertexShaderID and fragmentShaderID because we already passed it via the data attributes of the plane HTML element
    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,
            },
        },
    }
    
    // create our plane
    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;
    });
}

              
            
!
999px

Console