Click here to Skip to main content
15,564,017 members
Articles / Web Development / CSS3
Posted 28 Aug 2011

Tagged as


90 bookmarked

Scattered Polaroids - Providing Image Animations with CSS3 and jQuery

Rate me:
Please Sign up or sign in to vote.
4.95/5 (66 votes)
29 Aug 2011CPOL5 min read
Scattered Polaroids is like when you take an old box full of pictures, drop them all on a table, and start picking them randomly. This article deals with this the CSS3 and jQuery implementation of this concept.
This article breaks down the animation process of Scattered Images step by step. It looks at Loading the Images, Creating the Polaroids, Generating Random Positions and Rotations, Positioning and Rotating the Polaroids, Dragging and Dropping Polaroids, triggering the animation functionality in our application that will bring a polaroid to the front, as well as Animating Transitions via JavaScript.

Image 1

Table of Contents


Scattered Polaroids is the first part of a project I'm developing for my friend Janaina Rossi Moreira, who works in a Brazilian theater company named "As Bárbaras do Multeatro".

The goal of the project is to publicize the work of the company, and at the same time provide the visitors with a nice audio and visual experience, relying on multimedia, videos, imagery, poems, and recollections that better capture the artistic essence of the company.

There are many ways one could develop to navigate through collections of photographs, usually in an orderly fashion. Scattered Polaroids is a different method, like when you take an old box full of pictures, drop them all on a table, and start picking them randomly. This article deals with this technique, and hopefully will be helpful for you in some way.

System Requirements

In order to run the Scattered Polaroids sample attached to this article, you must have a browser that supports CSS3 2D transforms:

Image 2

  • Internet Explorer 9 or superior
  • Firefox
  • Safari
  • Chrome

The Polaroid Box

Image 3

The Polaroid Box is the element that will hold the pictures and also receive some of the events needed for the mouse functionality.

It is mandatory that you have a polaroid_box div in your page, or another one that could serve as a holder for the pictures.

    <div class="polaroid_box">

Loading the Images

Image 4

The images are loaded in a simple array of strings that describes the URL path for the images:

$(document).ready(function () {
    var images = new Array();
    images[0] = 'Images/Barbaras1.jpg';
    images[1] = 'Images/Barbaras2.jpg';
    images[2] = 'Images/Barbaras3.jpg';
    images[3] = 'Images/Barbaras4.jpg';
    images[4] = 'Images/Barbaras5.jpg';
    images[5] = 'Images/Barbaras6.jpg';
    images[6] = 'Images/Barbaras7.jpg';
    images[7] = 'Images/Barbaras8.jpg';
    images[8] = 'Images/Barbaras9.jpg';
    images[9] = 'Images/Barbaras10.jpg';
    images[10] = 'Images/Barbaras11.jpg';
    images[11] = 'Images/Barbaras12.jpg';
    images[12] = 'Images/Barbaras13.jpg';
    images[13] = 'Images/Barbaras14.jpg';
    images[14] = 'Images/Barbaras15.jpg';
    images[15] = 'Images/Barbaras16.jpg';
    images[16] = 'Images/Barbaras17.jpg';
    images[17] = 'Images/Barbaras18.jpg';
    images[18] = 'Images/Barbaras19.jpg';
    images[19] = 'Images/Barbaras20.jpg';

Once the array is loaded, you can call the createPolaroids function to render all needed DOM elements inside the given parent element:

    maxZIndex = images.length;
    createPolaroids($('.polaroid_box'), images);

Creating the Polaroids

Image 5

Here, the most important part is obviously creating the DOM elements that will render the polaroids in the page for us. Notice that we will rely heavily on the jQuery JavaScript framework. This will both reduce most of the boring work and also ensure that we will avoid cross-browser problems.

function createPolaroids($parentEl, images) {

    var parentWidth = $parentEl.width();
    var parentHeight = $parentEl.height();

    for (var i = 0; i < images.length; i++) {

        var polaroidHtml = 
            '<div id="polaroid' + (i + 1) + '" class="polaroid">' +
            '    <img id="img' + (i + 1) + '" class="photo" />' +
            '    <img src="Images/Polaroid_2.png" class="polaroid_photo" />' +

        var $polaroid = $('#polaroid' + (i + 1));
        var $img = $('#img' + (i + 1));

        var width = $polaroid.width();
        var height = $polaroid.height();

        $img.attr('src', images[i]);
        $polaroid.css('z-index', 1000);

Generating Random Positions and Rotations

We need to generate random positions and rotations for each polaroid, otherwise this couldn't be called "scattered" polaroids. So, all we need to do here is generate numbers that fall between acceptable boundaries of our screen (that is, the pictures are contained in a box, so we don't want our pictures falling off the screen).

for (var i = 0; i < images.length; i++) {
    var $el = $('#polaroid' + (i + 1));
    var windowWidth = $(window).width();
    var windowHeight = $(window).height();
    var offset = (windowWidth - windowHeight) / 2;
    var width = $el.width();
    var height = $el.height();
    var rotation = Math.random() * maxRotationAngle * 2 - maxRotationAngle;
    var left = offset + Math.random() * (parentHeight - width);
    var top = Math.random() * (parentHeight - height);

Now that we have gathered all the data needed for a polaroid, we create a new polaroid type that hold this information. This will be useful to encapsulate data and provide methods for manipulating the polaroids.

for (var i = 0; i < images.length; i++) {
    polaroids[i] = new polaroid(i + 1, $el, $parentEl, minScale, 
                   rotation, left, top, width / 2, height / 2, i);

The information is then provided to the polaroid function as a constructor, that will then hold the parameters in the instance:

function polaroid(id, $el, $parentEl, scale, rotation, left, 
         top, xTransOrigin, yTransOrigin, zIndex) { = id;
    this.$el = $el;
    this.$parentEl = $parentEl;
    this.scale = scale;
    this.rotation = rotation;
    this.originalScale = scale;
    this.originalRotation = rotation;
    this.originalLeft = left;
    this.originalTop = top;
    this.originalZIndex = zIndex;
    this.startRotation = rotation;
    this.left = left; = top;
    this.xTransOrigin = xTransOrigin
    this.yTransOrigin = yTransOrigin;
    this.interval = null;
    this.isMoving = false;
    this.zIndex = zIndex;
    this.xOffset = 0;
    this.yOffset = 0;

Positioning and Rotating the Polaroids

Image 6

Once the polaroid data is loaded, we can then call the updateLayout prototype function that will update the required CSS attributes accordingly:

polaroid.prototype.updateLayout = function () {
    this.$el.css('left', this.left + 'px');
    this.$el.css('top', + 'px');
    this.$el.css('z-index', this.zIndex);
    var transform = 
        'rotate(' + this.rotation + 'deg) scale(' + this.scale + ')';
    this.$el.css({ "-webkit-transform": transform, 
         "-moz-transform": transform, msTransform: transform });

It's important to notice that the last line in the code snippet above is providing the CSS transforms for the major browsers:

  • -webkit-transform: Chrome and Safari
  • -moz-transform: Firefox
  • msTransform: Internet Explorer

Using CSS3 Box Shadows

Image 7

CSS3 box shadows provide the shadow that outlines our pictures. It's a nice feature and makes our pages visually appealing. But as happens with most of the nice things in this world, you should use it with moderation. A box shadow can require intense processing, depending on how you use it. Our polaroid CSS class defines a box shadow with an offset of (4,4) and a depth of 32 pixels:

    -moz-box-shadow: 4px 4px 32px #000;
    -webkit-box-shadow: 4px 4px 32px #000;
    box-shadow: 4px 4px 32px #000;

As you will see later on in this article, we remove the box shadow during the polaroid animations, for the benefit of better performance. As a tip, it could be a good idea to drop the use of box shadows at all, replacing them by a static .png image (PNG images allow transparency), thus improving the performance.

Dragging and Dropping Polaroids

The application knows which polaroid is being dragged by storing the polaroid instance in the selectedPolaroid variable. When you click on some polaroid, the mousedown event of the polaroid will be triggered and the selectedPolaroid will be stored.

$('.polaroid').mousedown(function (event) {
    var id = $(this).attr('id');
    var i = id.replace('polaroid', '');

    var polaroid = polaroids[i - 1];

    if (polaroid.scale == minScale) {
        polaroid.zIndex = maxZIndex++;

        if (selectedPolaroid) {
            selectedPolaroid = null;
        else {
            selectedPolaroid = polaroid;
            selectedPolaroid.xOffset = event.pageX - selectedPolaroid.left;
            selectedPolaroid.yOffset = event.pageY -;
    else {
        if ( == {

From this point on, any mouse movement will be triggered by the mousemove event of the polaroid box, and the selected polaroid position will be updated:

$polaroid_box.mousemove(function (event) {
    if (selectedPolaroid) {
        selectedPolaroid.left = event.pageX - selectedPolaroid.xOffset; = event.pageY - selectedPolaroid.yOffset;

The drag-and-drop is finished when you release the mouse button. At this point, we set the selectedPolaroid variable to null.

$polaroid_box.mouseup(function (event) {
    if (selectedPolaroid) {
        selectedPolaroid.originalLeft = selectedPolaroid.left;
        selectedPolaroid.originalTop =;
        selectedPolaroid = null;

Pulling a Polaroid

At some point, you find a picture interesting and want to see it closer. So you double-click it. This will trigger the animation functionality in our application that will bring a polaroid to the front. The picture will look greater and perfectly straight in the center of the screen. This is done by an animation, which is the more complex part of this application, to be explained better later on. Once a polaroid is taken, we set the zoomedPolaroid variable and wait until the user drops it or double-clicks another picture.

$('.polaroid').dblclick(function () {
    var id = $(this).attr('id');
    var i = id.replace('polaroid', '');

    var polaroid = polaroids[i - 1];

    if (zoomedPolaroid) {
        zoomedPolaroid.zIndex = maxZIndex++;
        zoomedPolaroid.drop(null, function () {
            zoomedPolaroid = null;

    if (polaroid.scale == minScale) {
        polaroid.originalZIndex = maxZIndex++;
        polaroid.zIndex = maxZIndex + 1000;
        polaroid.pull(null, function () {
            zoomedPolaroid = polaroids[ - 1];
            selectedPolaroid = null;
    } else {
        polaroid.originalZIndex = maxZIndex++;
        polaroid.zIndex = maxZIndex++;

Dropping a Polaroid

The zoomed polaroid is dropped when the user clicks on it or double-clicks another polaroid. This will start the reverse animation, and the polaroid will leave the center of the screen and go back to its previous position, scale, and rotation angle.

Animating Transitions via JavaScript

Image 8

The animations are the complex part. They require the target scale, target rotation angle, and target position. Since the polaroid may be going to the center or coming back to its previous location, we avoid code duplication by encapsulating the core functionality inside the animate function.

First, we calculate the deltas (one for the angle, another one for the scale, and two deltas for the location). Then we calculate the steps (that is, the increment to be applied to the current animation value at each iteration). Then we start the setInterval function to reposition the polaroid, rotate it, and update the underlying DOM element, until the animation reaches the target values.

polaroid.prototype.animate = function (targetScale, targetRotation, 
         targetLeft, targetTop, beginCallback, endCallback) {
    var pol = this;

    if (pol.isMoving)

    pol.isMoving = true;

    var parentWidth = this.$parentEl.width();
    var parentHeight = this.$parentEl.height();

    var polaroid = pol;

    if (beginCallback)

    var angleDelta = targetRotation - polaroid.rotation;
    var scaleDelta = targetScale - polaroid.scale;
    var leftDelta = targetLeft - polaroid.left;
    var topDelta = targetTop -;

    var steps = animationSteps;
    var scaleStep = scaleDelta / steps;
    var rotationStep = angleDelta / steps;
    var leftStep = leftDelta / steps;
    var topStep = topDelta / steps;

    var interval;

    var rotation = polaroid.rotation;
    var scale = polaroid.scale;
    var left = polaroid.left;
    var top =;

    var $polaroid = $('#polaroid' +;

    $polaroid.css('z-index', 100);

    $polaroid.css({ "-moz-box-shadow": "0 0 0 #fff" });
    $polaroid.css({ "-webkit-box-shadow": "0 0 0 #fff" });
    $polaroid.css({ "box-shadow": "0 0 0 #fff" });

    var interval = setInterval(function () {
        if (
                (scaleStep > 0 && scale >= targetScale) ||
                (scaleStep < 0 && scale <= targetScale)
            ) {
            scale = targetScale;

        $polaroid.css({ WebkitTransform: 'rotate(' + rotation + 
                        'deg) scale(' + scale + ')' });
        $polaroid.css({ '-moz-transform': 'rotate(' + rotation + 
                        'deg) scale(' + scale + ')' });
        $polaroid.css({ msTransform: 'rotate(' + rotation + 
                        'deg) scale(' + scale + ')' });
        $polaroid.css('left', left + 'px');
        $polaroid.css('top', top + 'px');

        if (scale == targetScale) {
            $polaroid.css({ "-moz-box-shadow": "4px 4px 32px #000" });
            $polaroid.css({ "-webkit-box-shadow": "4px 4px 32px #000" });
            $polaroid.css({ "box-shadow": "4px 4px 32px #000" });

            pol.isMoving = false;


            rotation = targetRotation;
            scale = targetScale;
            left = targetLeft;
            top = targetTop;

            pol.rotation = targetRotation;
            pol.scale = targetScale;
            pol.left = targetLeft;
   = targetTop;


            if (endCallback)

        rotation += rotationStep;
        scale += scaleStep;
        left += leftStep;
        top += topStep;
    }, 5);

Final Considerations

I hope you liked both the article and the code. I think the techniques presented here are simple and useful. Please let me know what you think about it, by leaving a comment in the section below.


  • 2011-08-28: Initial version.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Written By
Instructor / Trainer Alura Cursos Online
Brazil Brazil

Comments and Discussions

Question5 from me Pin
Gandalf_TheWhite13-Jan-14 0:05
professionalGandalf_TheWhite13-Jan-14 0:05 
GeneralMy vote of 5 Pin
csharpbd13-Nov-12 20:04
professionalcsharpbd13-Nov-12 20:04 
GeneralMy vote of 5 Pin
ofsarac13-Jul-12 2:37
ofsarac13-Jul-12 2:37 
GeneralMy vote of 5 Pin
Sophia_xd21-Dec-11 1:48
Sophia_xd21-Dec-11 1:48 
GeneralRe: My vote of 5 Pin
Marcelo Ricardo de Oliveira1-Mar-12 16:46
mvaMarcelo Ricardo de Oliveira1-Mar-12 16:46 
QuestionNice article. About performance issue. Pin
Syed BASHAR17-Sep-11 10:05
Syed BASHAR17-Sep-11 10:05 
AnswerRe: Nice article. About performance issue. Pin
Marcelo Ricardo de Oliveira17-Sep-11 13:39
mvaMarcelo Ricardo de Oliveira17-Sep-11 13:39 
GeneralRe: Nice article. About performance issue. Pin
Syed BASHAR17-Sep-11 18:18
Syed BASHAR17-Sep-11 18:18 
GeneralMy vote of 5 Pin
Dave Kerr12-Sep-11 22:12
mentorDave Kerr12-Sep-11 22:12 
GeneralRe: My vote of 5 Pin
Marcelo Ricardo de Oliveira13-Sep-11 3:19
mvaMarcelo Ricardo de Oliveira13-Sep-11 3:19 
QuestionMy Vote 5 Pin
Shakeel Iqbal10-Sep-11 11:44
Shakeel Iqbal10-Sep-11 11:44 
AnswerRe: My Vote 5 Pin
Marcelo Ricardo de Oliveira11-Sep-11 9:58
mvaMarcelo Ricardo de Oliveira11-Sep-11 9:58 
GeneralMy vote of 5 Pin
David C# Hobbyist.7-Sep-11 13:08
professionalDavid C# Hobbyist.7-Sep-11 13:08 
GeneralRe: My vote of 5 Pin
Marcelo Ricardo de Oliveira8-Sep-11 5:37
mvaMarcelo Ricardo de Oliveira8-Sep-11 5:37 
GeneralRe: My vote of 5 Pin
David C# Hobbyist.8-Sep-11 5:43
professionalDavid C# Hobbyist.8-Sep-11 5:43 
General5 Pin
Dan Mos7-Sep-11 8:49
Dan Mos7-Sep-11 8:49 
GeneralRe: 5 Pin
Marcelo Ricardo de Oliveira8-Sep-11 5:35
mvaMarcelo Ricardo de Oliveira8-Sep-11 5:35 
GeneralMy vote of 5 Pin
Saraf Talukder6-Sep-11 10:32
Saraf Talukder6-Sep-11 10:32 
GeneralRe: My vote of 5 Pin
Marcelo Ricardo de Oliveira6-Sep-11 12:02
mvaMarcelo Ricardo de Oliveira6-Sep-11 12:02 
GeneralMy vote of 5 Pin
BrianBissell1-Sep-11 11:10
BrianBissell1-Sep-11 11:10 
GeneralRe: My vote of 5 Pin
Marcelo Ricardo de Oliveira1-Sep-11 12:31
mvaMarcelo Ricardo de Oliveira1-Sep-11 12:31 
GeneralMy vote of 5 Pin
mahendren30-Aug-11 20:44
mahendren30-Aug-11 20:44 
GeneralRe: My vote of 5 Pin
Marcelo Ricardo de Oliveira31-Aug-11 4:28
mvaMarcelo Ricardo de Oliveira31-Aug-11 4:28 
QuestionVery well written article Pin
Espen Harlinn30-Aug-11 14:17
professionalEspen Harlinn30-Aug-11 14:17 
AnswerRe: Very well written article Pin
Marcelo Ricardo de Oliveira30-Aug-11 16:03
mvaMarcelo Ricardo de Oliveira30-Aug-11 16:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.