Pedestrians crossing a rain-slick street intersection at night, covered by umbrellas A neon-lit shopping district in Japan Japanese lanterns lining an alleyway at night An outdoor dining area at night A view of video billboards at night in a Japanese shopping district A view of video billboards at night in a Japanese shopping district, with pedestrians below

Inspired by a popular Mac OS X screensaver theme and using the incredible photographs of Liam Wong, I’ve made this automated presentation for sites, suitable for portfolios and marketing. It combines some of the oldest principles of web development - progressive enhancement - with some of the newest: the .

Step By Step

While I will be using a polyfill to gain support for the Web Animation API in browsers other than Chrome and Firefox, it’s important to build the interaction as if won’t work at all. To that end, the images are placed on the page as follows (alt values have been left blank for conciseness and clarity)

<div class="shuffle reveal">
    <img src="umbrellas.jpg" alt>
    <img src="shopping-at-night.jpg" alt>
    <img src="lanterns.jpg" alt>
    <img src="outdoor-dining.jpg" alt>
    <img src="blade-runner.jpg" alt>
    <img src="square-umbrellas.jpg" alt>
</div>

If we assume that the basic loads, the images should be styled in rows, using a variation on the technique I discussed in the last article:

@keyframes reveal {
    to { 
        opacity: 1; 
    }
}
.shuffle {
    min-height: 100vh;
    position: relative;
}
.shuffle img { 
    width: 33%; 
    opacity: 0; 
    transform: scale(1.3);
}
.reveal img { 
    animation: reveal 1s 1s forwards; 
}

The <div> is set to a min-height with vh units and position: relative to ensure that the final presentation takes up the full page. Without the associated JavaScript, the CSS on the div will make no difference, and images will fade in together as one after a one-second delay.

Adding Web Animation

With the basic presentation in place, we can add JavaScript. The first thing we must do is turn off the default animation; we do this by removing the shuffle class:

var reveal = document.querySelector(".reveal");
reveal.classList.remove("reveal");
var revealedImages = reveal.querySelectorAll("img"),
i = 1;

The images will be randomly distributed and angled, so I’ll make a function that covers randomness between two values:

function getRandom(min, max) {
    return Math.random() * (max - min) + min;
}

With all the images inside the <div> read into an array, I can loop through each one:

Array.prototype.forEach.call(revealedImages, function(photo) { 
    setTimeout(function(){ 
        photo.style.position = "absolute";
        photo.style.width = getRandom(33,45)+"%";
        photo.style.left = getRandom(-5,65)+"%";
        photo.style.top = getRandom(-6,60)+"vh";
        photo.classList.add("expose");
        var animate = photo.animate([
            { opacity: '0', transform: 'rotate('+getRandom(-12,12)+'deg) scale(1.2)', 
                boxShadow: '0 0 12px 12px rgba(0,0,0,.3)' },
            { opacity: '1', transform: 'rotate('+getRandom(-8,8)+'deg)', 
                boxShadow: '0 0 6px 6px rgba(0,0,0,.3)' }
          ], {
            duration: 2000,
          fill: 'forwards'
          });                    
    }, 1800*i)
    i++;
})

Each photo in turn is:

  • provided with absolute positioning, so we can move it anywhere in relation to its containing element.
  • given a random width as a percentage of its container, between 33 and 45%.
  • given a random left as a percentage, between -5 and 65, meaning that images may go slightly off the screen to the left or right
  • defined in its vertical position by a top measured in vh units. Again, the range of numbers used mean that the photo may appear slightly outside the viewport.
  • provided with a class of expose, that defines a frame for the image. The class is added to the page’s stylesheet so that the script can draw from it:
.expose {
    border: 1.4rem solid #eee;
}

The random animated nature of the scattered photos makes movement with the Web Animation API an obvious use, via an animate variable:

  • the initial opacity of the photo is set to 0, and provided with a random initial rotation and a scale of 1.2. A spread boxShadow is also applied (note the camelCasing and lack of hyphen in the property when it is used in JavaScript).
  • the photo is animated to full opacity, a (likely) different angle, and a tighter box-shadow. By not defining it in the second keyframe, scale is automatically set to 1.
  • each animation takes 2 seconds, and is delayed by the value of the incrementor i multiplied by 1000 milliseconds.

Conclusion & Improvements

Coupled with a good Web Animation API polyfill for browsers that don’t support the specification, the result works very well. However, there’s plenty of room for improvement:

  1. The random left and top ranges are simply “guesstimates”: it would be nice to work them out from the maximum width and top value of the photos, coupled with their aspect ratios.
  2. The images automatically stack on each other in the order they appear on the page. It might be useful to randomly shuffle the photos so they appear in a different order each time.
  3. The layering effect obscures images that have been previously placed; it would be helpful if the elements could be dragged and re-ordered by the user.

I’ll be covering those possibilities in future articles, in association with other demos.

Photographs by Liam Wong, 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/yJNvaa