While there are many solutions to achieve parallax scrolling on the web, it’s my impression that most fake the images-in-depth effect. Inspired by a OS X screensaver, I realized that CSS 3D would allow images to be actually set back on the z axis, providing true perspective and parallax when the images were moved up and down. While the code for this example remains in prototype form, I think it’s interesting enough to share an explanation, along with a CodePen demo.

All About That Base

The code starts with a singular piece of markup, in the form of a <div> element:

<div id="parallax-container">

This element will be filled with images loaded by our JavaScript; first, we need to set up styles for the <div> and the images that will appear inside it:

#parallax-container {
    background: #16161d;
    margin: 0;
    overflow: hidden;
    perspective: 1200px;
    height: 100vh;
    width: 100vw;
    transform-style: preserve-3d;
#parallax-container img {
    transform-origin: center;
    box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.4);
    position: relative;

Rolling Images

The progressive approach would be to insert the images as markup, style them with CSS, and them have the JavaScript hide and manipulate the images. This being a demo, I’ve gone the direct route. Since the filenames of the images all follow the same pattern (wave1.jpg, wave2.jpg… etc) I could also use JavaScript to generate the filenames; along with the other global variables I’ll use in this script:

var container = document.getElementById("parallax-container"),
waveSrc = [],
waves = [],
imgLoc = "",
j = 0;

for (var i = 0; i < 10; i++) {
    waveSrc[i] = imgLoc+"wave"+(i+1)+".jpg";

I’ll need several random numbers, which I’ll generate in a function:

function getRandomInRange(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;

I’ll also need to reference the images that are loaded, together with the screen’s current width and height (assuming that the browser is maximized).

var screenWidth = window.screen.width,
screenHeight = window.screen.height;

As the images are loaded and appended to the container element, they are provided properties - .xPlane, .yPlane and zPlane - with random values that position the element in 3D space. The image’s alt attributes are blank for this demo.

function preloadImage(filename){
    var img=new Image();
    img.onload = function(){ 
        img.xPlane = getRandomInRange(-500, screenWidth - 500);
        img.yPlane = getRandomInRange(500, 1000);
        img.zPlane = getRandomInRange(300,2000);
        img.style = "transform: translate3d(" + img.xPlane +"px, " + 
            img.yPlane + "px, -" + img.zPlane +"px)";
    imgLoc = "";
    img.src= imgLoc + filename;
    img.alt = "";
    waves[j] = img;

function loadImages(){
    for (var i = 0; i < waveSrc.length; ++i) {
        var filename = waveSrc[i];

Finally, the images are moved using a function:

function moveImages(){
    waves.forEach(function(image) {
            image.yPlane = image.yPlane - 2;
            image.style.cssText = "transform: translate3d(" + image.xPlane+"px, 
" + image.yPlane+"px,  -" + image.zPlane + "px); z-index: " + image.zIndex;

All of this is made to work by calling the appropriate functions at the end of the script:

	function() { 


As a prototype, the code is still a little rough at this stage. There are several obvious improvements that can be made:

  1. Right now the script continues to run as the images disappear off the top of the container, essentially running into infinity; ideally the script would cut images from the start of the array as they disappear and add them to the bottom, off the screen, to continue upwards.
  2. Perhaps paradoxically, it would be good to have a little less randomness in the positioning of images: right now, one image can appear directly behind another, or very close in z-space (and therefore, move upward at very close to the same pace). To do that, I’d need to compare the position values of newly images to those that are already established in the array, calling a new random value if they are too close.
  3. Right now this is an “auto-scroll”, technique. If you wanted to associate the movement of the image with changing the scrollbar on the page, you could alter the position of the images in relation to window.scrollY.

Images by Luke Shadbolt, used with permission.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/YWgAEP