Chonky Menu Re-Creation
This post contains 11 interactive illustrations for you to play with. The final menu is at the bottom. Don’t feel bad if you just want to scroll through fiddling with the demos, or watch the video!
I’ve been told the demos don’t work on Android, I’ll try to fix that, in the meantime, Android users might prefer to watch the video.
I don’t usually like to copy other people’s work. I hoped to add some value by putting together these interactive illustrations which show how various parts of the UI work.
Putting Something on the Page
First I set up a menu, and calculated the vertical center of each row. The centers are visualized as horizontal black lines in the illustration below. An event listener tracks mouse movements in order to get the mouse Y position, which is visualized as a red line in the illustration.
Now that we have the mouse Y position, and each row’s center Y position, we can easily check which row is closest to the mouse by looping through the list of row positions and comparing them to the mouse position. I’ll store a reference to the closest row, and how far the mouse is from it, visualized in purple below.
Zoom & Cursor
The original design had a white highlight behind the active menu item, which I’m calling the “cursor”. With the closest row figured out, we can add a box with its Y position such that it is centered on the closest row.
The closest row also zooms in slightly, so I’ve added a CSS scale transform to the closest row. The cursor and zoom effect are visible in the following illustration.
Decay into Radness
So far, the movements are jumpy. We’ll be adding animations shortly. Lets first add what I’m calling “decay” to the cursor and menu content. In Janum’s original menu, the cursor and menu content gravitate towards the pointer (finger) as it slides past. To accomplish this, I’m using a math function that will translate the distance from the pointer to the closest row, into a decayed value that has the desired effect.
To do the decay, we need a function that for small inputs, returns a value close to that input, and as the input value gets larger, the output starts to slow its rate of increase, smoothly approaching a maximum value which it never passes.
I wasn’t sure what type of math function would work for this, but I had happened to see a Retweet from Matt Sephton of a post by @_phocom which is a convenient reference of math functions that are useful in graphics and game programming.
Looking through these graphs, we have a clear winner:
tanh(x). It starts off at
Y=0, increasing first quickly, then slowing down and never passing
Y=1. The maximum value can be adjusted by multiplying the function by a constant, something like,
tanh(x) * max, and the “slowness” of the initial decay can be controlled by manipulating the
X value, I ended up with this function:
Which creates this graph:
The following illustration helps visualize this. You can drag the rectangles and they move together. The blue one follows your pointer and the red one has its Y position run through the decay function. The position of each box is plotted, and you’ll see the blue one traces out a
Y=X graph while the red one traces a version of our
To add this into the menu, I took the distance of the cursor to the closest menu item, passed it through this decay function and used that to calculate the new
Y value of the cursor. I used a version with a slightly adjusted maximum for the menu content, so they have different movement, resulting in a subtle parallax.
Move your mouse across the illustration below to see the original value (purple) and decayed values (green for the cursor’s center and blue for the menu content’s center).
Just add Springs
So far there’s no animations. To make this menu feel natural, we’re going to animate it using a spring simulation. I tried using an off-the-shelf animation library but ran into lots of glitches, so I went looking for some spring animation code I could use. I remembered that Cheng Lou had shared his impressive website which is full of springy animations, and the source code is available on GitHub.
I found a simple spring animation function within the code, and (with his permission) I gave it a try. I created a component in my project called
SpringLayer which accepts a set of values representing the x, y, width, height, opacity, and scale of the layer. By updating any of those values, the layer will animate to the new values using a spring simulation.
You can play with a
SpringLayer in the illustration below. The green dot shows the current “destination” of the rectangle, any time that changes, the layer animates to the new positon. The same applies to all the animated values (like width and height).
The great thing about a spring simulation is that you can interrupt it and give it a new target value, and it will smoothy animate there. Because my animations will be updated every time the mouse moves, this is very important. The spring animations also give a pleasing, lifelike appearance.
So, I recklessly replaced all the elements of my menu that needed to animate with
SpringLayers and just set the whole thing to re-render sixty times a second. I thought surely this would cause performance issues because I’d be re-calculating a spring value for each property of each animated layer each frame, but I never had an issue (on desktop at least), so given that this is a prototype, I just went with it.
Adding springs everywhere ended up being easy way to make the animation work.
The menu is supposed to start out hidden, so I set its opacity to 0% and its scale to 25%, and added an activator button. When pressing on that button, I set the opacity and scale to 100%. Remeber these are “destination” values for the
SpringLayer, so the springs go to work and animate the menu to those values.
The cursor doesn’t appear until you first slide your mouse into the menu. You can see a
wentIn value in the data display in the illustration below which tracks whether the mouse has crossed over the Y position of the bottom of the menu since the last activation.
The remaining demos require you to press the activator circle to activate the menu.
The coolest part of the original menu is the stretch effect that happens when you drag past the top or bottom of the menu. I can add this by setting a CSS transform value like
transform: scaleY(1.2), but that will stretch out from the center, in both directions. The menu needs to stretch only in the direction of the pointer, so I to dynamically update the origin (controlled by the
transform-origin css property)
In the next illustration, you’ll see the origin visualized with a red dot. The distance past the top or bottom of the menu are visualized in green.
I also added a way to cancel the menu activation by dragging even further past the ends, which you can try in the illustration below.
I don’t want the scale to continue forever as your mouse continues past the edge, so I run the distance past the edge through our same decay function from before, giving me a value that slows down as it approaches a maximum distance of my choice. Now I have a distance value that approaches a maximum, and I need to translate that into a stretch amount that approaches a maximum stretch. I do the conversion using a
range function. This post by Trys Mudford explains
Putting it all Together
To finish the menu, I added the names, avatar images, and other polish to make it look like a real menu.
It was fun building this menu, and I learned a ton. I’m excited to make more fluid UI concepts like this using what I’ve learned.
All credit for the menu design goes to Janum, please follow him on Twitter, he shares really cool prototypes.