Interested in this project?
A new startup that my agency had been working with since the beginning, looking to build an online portal for hiring servers and catering for luxury home parties was looking for a way to announce their new brand, and build hype. Their whole brand experience was based around; being an internet company (so we knew we had to have something somewhat advanced), and catering to a higher end client (thus the name “Lux Gatherings”).
So I took to building a beautifully minimalistic landing page to help communicate those two ideas. I worked with our designer to put together a simple layout, with a slight gradient, and this beautiful string light effect.
With the initial design in place I knew exactly what I wanted to do; animate the string lights. But not a static animation, I wanted them to be interactive.
So I settled on creating a physics based string lights effect. I found a great little 2D rigid-body physics engine in vanilla Javascript. This allowed me to create a rigid chain of small circular bodies, the small bodies would be set to not render. Meaning, it was a rigid line, connected to an invisible circle, then to another rigid line, and so on. Then at each invisible circle, I create a new, larger circle, connected (via a constraint, visualized as a thicker black line) to the invisible circle. Then I randomly positioned those larger, visible circles so that when the page was loaded, it would have a nice, falling effect.
function initString() { var groupId = Body.nextGroupId(); var stack = Composites.stack(0, -10, 10, 1, 0, 0, function(x, y, column, row) { var isStatic = false; x = ((column/9) * $string.width()); y = (column/9) * 200; if (column == 0) { x = 50; y = -10; isStatic = true; } if (column == 9) { isStatic = true; } return Bodies.circle(x, y, 5, { groupId: groupId, isStatic: isStatic, frictionAir: 0.3, render: { visible: false, } }); }); string = Composites.chain(stack, 0, 0, 0, 0, { groupId: groupId, stiffness: 0.9, render: { strokeStyle: "#231f20", lineWidth: 3 } }); // add string World.add(engine.world, string); }
Now. I could have created more points along the chain, giving the rope a more fluid feel, but I hedged away from this, due to the fact that in my cross browser testing, I noticed that some browsers would slow down dramatically due to the added physics calculations.
Now I wanted to give the user a hint that they could interact with it. I came up with the idea of doing a wind effect. The problem was Matter did not have any sort of additional force effects. So I decided to shift gravity. At a random interval (between 0 and 3 seconds), and at a random duration (between 0 and 3 seconds), and at a random intensity (between 0.25 and 0.5), and at a random duration (between 10 and 20 frames), and in a random direction (half the length), I would ease the gravity from straight down, to the chosen direction, imitating a gust of wind.
function initWind() { wind = window.setTimeout(function() { gust(); }, 2000); function gust() { var gravity = 0; var i = 0; var intensity = 0.25 - (Math.random() * 0.5); var length = Math.ceil(Math.random() * 20) + 10; var halflength = length/2; guster = window.setInterval(function() { if (i <= halflength) { gravity = ease(i, 0, intensity*halflength, halflength); } else { gravity = ease(i - halflength, intensity*halflength, 0 - intensity*halflength, halflength); } if (i == length) { window.clearInterval(guster); wind = window.setTimeout(function() { gust(); }, Math.random() * 3000); } i++; engine.world.gravity.x = gravity; }, 100); } }
Then the last part, creating interaction. For desktop I created an invisible circle that would simply track with the mouse, and repel other circles.
function initMouse() { var mouser = Matter.MouseConstraint.create(engine, { constraint: { render: { visible: false } } }); var groupId2 = Body.nextGroupId(); var mover = Bodies.circle(0, 0, 10, { groupId: groupId2, isStatic: true, render: { visible: false, } }); var mover2 = Bodies.circle(0, 0, 20, { groupId: groupId2, isStatic: false, render: { visible: false, } }); var mouseConstraint = Constraint.create({ bodyA: mover, bodyB: mover2, length: 0, render: { visible: false, } }); Matter.Events.on(engine, 'mousemove', function(event) { mover.position = {x: event.mouse.position.x, y: event.mouse.position.y}; }); World.add(engine.world, [mouser, mover, mover2, mouseConstraint]); }
And for Mobile I removed the wind, and instead opted to shift the gravity in the direction the user tilted their phone.
function initMobile() { $("#string").css("pointer-events", "none"); window.addEventListener('deviceorientation', updateGravity, true); function updateGravity() { oldorient = orientation; orientation = window.orientation, gravity = engine.world.gravity; if (orientation != oldorient) resize(); if (orientation === 0) { gravity.x = Common.clamp(event.gamma, -90, 90) / 90; gravity.y = Common.clamp(event.beta, -90, 90) / 90; } else if (orientation === 180) { gravity.x = Common.clamp(event.gamma, -90, 90) / 90; gravity.y = Common.clamp(-event.beta, -90, 90) / 90; } else if (orientation === 90) { gravity.x = Common.clamp(event.beta, -90, 90) / 90; gravity.y = Common.clamp(-event.gamma, -90, 90) / 90; } else if (orientation === -90) { gravity.x = Common.clamp(-event.beta, -90, 90) / 90; gravity.y = Common.clamp(event.gamma, -90, 90) / 90; } }; }
All In all, I created a very simplistic, beautiful way to grasp peoples attention, instigate interaction, and increase their likelihood of signing up to be a member of their mailing list. Thus increasing the companies likelihood of launch success.