JonathanSvärdén

The world’s most satisfying toggle

🕒 6 min read

It's a bold claim, I know. But take a look in the top right corner of this page and tell me I'm wrong, and make sure your sound is turned on.

I love seeing little bits of playful interactivity on the web. Components that may serve a functional purpose, but also double as toys, things to be interacted with for the sheer joy of it.

Skeuomorphic interactions

Years ago, the word skeuomorphism was all the rage. It was most often talked about in terms of the visual design of elements rather than the mechanics of how the user interacted with those elements. I think that may be a fruitful area to explore because digital interfaces often lack something, a sense of tactility, of movement and life. A bit of juice.

Adding life with physics

Verlet integration is a technique often used to implement physics engines for games. It's a numerical method for integrating Newton's equations equations of motion that hits the sweet spot between accuracy and speed.

This post won't go deep into the technical details of all this but will instead focus on how one might use these techniques to add a bit of physicality and fun to an interaction.

Let's see how we can use these simple building blocks to build up to something like a pull cord light switch. Let's first create a random shape of four particles with some sticks between them. Just like we would for a physical object, we need to add crossbeams across the diagonals to make the box keep its shape.

Seeing such simple rules give rise to such lifelike behavior never ceases to amaze me.

We can fix a given particle in space by introducing an isPinned property. In the particle update function we can then use that to ensure that a pinned particle's position never changes from its initial position.

constructor(x, y, mass, isPinned) {
  [...]
  this.initX = x;
  this.initY = y;
  this.isPinned = isPinned;
}

update(deltaT, acceleration) {
  [...]

  if (this.isPinned) {
    this.x = this.initX;
    this.y = this.initY;
  }
}

Here we're pinning the particle in the lower right corner. Note that we're not specifically telling the other particles to rotate around the pinned corner, that's simply a consequence of the constraints placed on the system and the forces working on each particle.

Connecting particles to make a rope

Instead of connecting the particles to each other to form a box, we can connect them end-to-end to form a rope. The first point is pinned to the top of the canvas. The more particles we add, the more lifelike the rope will seem.

Grabbing hold of the rope

Seeing a dangling rope is all well and good, but let's add some interactivity by creating a few event handlers that will let us click and drag each particle.

const handleMouseMove = (e) => {
  e.preventDefault();
  mouseX = e.offsetX;
  mouseY = e.offsetY;
};

const handleMouseDown = (e) => {
  e.preventDefault();
  mouseDown = true;
};

const handleMouseUp = () => {
  mouseDown = false;
};

canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('mousedown', handleMouseDown);
canvas.addEventListener('mouseup', handleMouseUp);

In our animation loop, let's check which particle is closest within a certain threshold, and make that particle follow the cursor's position when the mouse button is held down.

let distanceToClosest = Number.MAX_SAFE_INTEGER;

for (let i = 0; i < particles.length; i++) {
  const distanceToParticle = getDistance(particles[i], {
    x: mouseX,
    y: mouseY
  });
  if (distanceToParticle < distanceToClosest) {
    distanceToClosest = distanceToParticle;
    closestParticleIndex = i;
  }
}

if (distanceToClosest < attachmentThreshold && mouseDown) {
  particles[closestParticleIndex].x = mouseX;
  particles[closestParticleIndex].y = mouseY;
}

Now try clicking and dragging the rope around.

Adding sound effects

With my dark mode toggle switch I ultimately went for a clicky feel but what if we leaned into the more springy, rubbery behavior that this rope is sort of already exhibiting? We can enhance (or juice, remember?) this feeling by adding a rubber band sound.

Try pulling the ball down until you reach the threshold.

A time and a place

A wise man once said,

Your scientists were so preoccupied with whether they could, they didn’t stop to think if they should.

These sorts of things really pop when they're used sparingly, and in the right context. You wouldn't want to be filling out an expense report or a tax form and have things bouncing around or making noise. Let's keep a few ground rules in mind. We don't want to annoy people.

How often does the action occur?

Any kind of animation slows an interaction down, that's inevitable. Be very mindful of this when adding interactive flair to an action that is repeated often.

How time-sensitive is it?

When the need to perform the action arises, how quickly does the user need to complete it? Don't make people feel like the interface is working against them.

How serious is is?

It would obviously be tone-deaf to add whimsy to a "Report abuse" button, for example.

The dark mode toggle on this site adheres to these rules: toggling dark/light mode isn't something you do often, there's no time pressure, and it's not serious.

Further reading