Harry Parton

Making a Landing Page for See+Do

I've been working on See+Do over the past few months, to make it possible to add new cities to the platform. As part of this effort I needed to add a new landing page. I wanted to create something interesting that users would remember and add some interaction to the page. So I decided to let users lob Emojis all over the place.

To do this I used Matter.js and created a simple 2D physics sandbox. Matter.js is a fun library to work with, it’s a powerful 2D physics engine with a fairly easy to use interface, the only issue is the documentation can be a bit painful to get around and a general lack of examples with up to date code.

I want to cover some of the interesting parts of building the landing page.

Populating the world

Different objects are added in different ways, I have a few types of emoji as well as invisible hitboxes being added to replicate the DOM in the canvas. I'll go over the key points of each.

Emojis.

Emojis are randomly positioned off screen and dropped into the world, right now there are 3 different kinds of emoji: Faces, Pictures and Pizza. These different types are specified in an array at the top of the file to make it easier to add more types later on.

var emojiTypes = [
  {
    name: 'Laugh',
    shape: 'circle',
    radius: 38.75,
    sprite: 'http://i.imgur.com/9Nj59hX.png'
  },
  //etc...
  {
    name: 'Pizza',
    shape: 'triangle',
    points: [
      { x: 0, y: 0 }, { x: -40, y: -80 }, { x: -80, y: 0 }
    ],
    sprite: 'http://i.imgur.com/AbJKujs.png'
  }
]

Each type has to be generated differently, picture frames can just be a rectangle (the sprite doesn't have to be completely accurate to the shape). Circles are all the same size with different sprites and then triangles are simple polygons.

if (emojiTypes[randomNumber].shape === 'circle') {
  var emoji = Bodies.circle(horizontalOffset, verticalOffset, emojiTypes[randomNumber].radius, {
    render: {
      sprite: {
       texture: emojiTypes[randomNumber].sprite
      }
    },
    restitution: 0.3,
    angle: randomAngle
  });
}
//etc..

World.addBody(engine.world, emoji);
emojis.push(emoji);

Generating Platforms

The final effect is to make it seem like the canvas elements can interact with the elements on the page. So you can bounce the emojis off the city titles.

On page load and resize I do a check against the DOM and find any elements with the js-canvas-dom class. Then for each of these I insert a rectangle into the world and set its width/height based on the element. Then reposition them based on the top/left offset of the dom element.

$('.js-canvas-dom').each(function() {
  var width = $(this).width();
  var height = $(this).height();
  var cords = $(this).offset();
  var x = cords.left + width / 2;
  var y = cords.top + height / 2;
  var outline = 'transparent';

  // Make the platform based off the dimension/cords of the DOM element
  var platform = Bodies.rectangle(x, y, width + 25, height, {
    isStatic: true,
    render: {
      lineWidth: 0,
      strokeStyle: outline,
      fillStyle: 'transparent'
    }
  });
  // Keep a track of the platform so we can modify/remove it later.
  platforms.push(platform);
  // Let it free into the world.
  World.addBody(engine.world, platform);
})

Limiting objects on screen

Every time I add more emojis to the world, I need to do a check and make sure that I don't add too many. This is partly due to performance limitations and also just to try and keep visual clutter to a minimum. If it turns out I do have too many then I just remove some of the old ones before adding anymore.


function limitBodies(bodies, limit) {
  if (bodies.length > limit) {
    var diff = bodies.length - limit;
    // removeBodies() explained below.
    removeBodies(bodies.splice(0, diff), true);
  }
}

Making a graceful exit

Instead of having the emojis just randomly vanish from existence I implemented a nicer leaving animation. Before I send the bodies through to removeBodies(), I order the array based on distance from the bottom. So the emojis fall out of the world from the bottom row.

emojis.sort(sortPositionY)

function sortPositionY(a,b) {
    return b.position.y - a.position.y;
}

I then change the collision group so the emojis fall out of the world and then after a short delay, remove them from the world. This creates a much more natural leaving animation.

function removeBodies(bodies, fall) {
  var fall = typeof fall !== 'undefined' ?  fall : false;

  for (var i = bodies.length - 1; i >= 0; i--) {
    var body = bodies[i];

    if (bodies[i].collisionFilter && fall) {
      // They no longer colide with any other objects
      bodies[i].collisionFilter.mask = 2

      // After 2 seconds they should be out of the viewport.
      setTimeout(function(bodies, i) {
        Matter.Composite.remove(engine.world, bodies[i]);
      }.bind(this, bodies, i), 2000)
    } else {
      Matter.Composite.remove(engine.world, body);
    }
  }
}

If you want to take a look at all the code then its available here on GitHub or if you want to try the live version, you can here.