SVG hamburgers and the Web Animation API — a journey away from the standards
A couple of weeks ago, while going through an old sketchbook, I found an interaction idea I had previously jotted down and promptly forgot about.
What I wanted to do was to animate the infamous hamburger menu icon out of the viewport when a scroll event was detected and bring it right back when the user stopped scrolling. Hardly worth showing off — simply a transition I haven't seen before.
DISCLAIMER: Aware that I could achieve all this with a dash of
.addClass() and better-supported, CSS-based approach, I deliberately went against that idea — even if this renders this exercise unfit for production use cases. It’s a record of a small personal project, a quirky proof of concept perhaps, untested and meant for fun. Chances are you won't even see the results, as the API is only partially supported, especially on mobile!
What I was most excited about were the events and methods included in the API. The ability to sync animation start times, chain and reverse them with a method seemed to give me a lot more control over the process, compared to animation-delay CSS trickery I would otherwise have to resolve to.
In the course of coding the animation, though, I had to give up on using the methods and rethink the exercise in order to complete it. I had fallen into a trap I should’ve learned to sidestep a long time ago: taking a fancy to features, rather than keeping my attention fixed on project goals. But let's start at the beginning, shall we?
Having a plan laid out, and full of enthusiasm, I started with a simple SVG export from Illustrator and proceeded to target each of the
<rect> elements, and moving them off the screen using a simple
transitionX declaration. I began playing with the keyframe sequences and some custom cubic bezier easing curves. With all the fun I’ve had tweaking the elements’ movements, I expected smooth sailing further on.
First issue I’ve ran into concerned the feature I was most looking forward to trying: the
Initially, I wanted to use
reverse() to bring the
<rect> elements back onto the screen. Once called on the player element, the animation was indeed reversing, but the delays I set on each of the keyframe sets were completely ignored and, instead of a smooth-moving, orderly queue, I ended up with
<rect> elements rushing back onto the screen at the same time.
Why? My Google searches left me clueless.
reverse() method is a very promising feature of the Web Animation API. Without getting into too much detail — others have done a much better job explaining its usage — it would’ve been ideal for the type of interaction I was tinkering with, keeping the animation symmetrical and saving me from manually re-ordering the keyframes. Which, dejectedly, I ended up doing anyway, tasting that bittersweet feeling of partial victory.
Just as I managed to convince myself to keep going, Problem #2 appeared on the horizon. I contemplated surrender.
Problem #2 — pixels don’t mean what I thought they meant
Prior to implementing the
reverse() method, I’ve only been moving the SVG shapes off canvas, not bothered in the least with bringing them back. Enchanted by their movement and realising my code was actually working, I happily kept refreshing the browser tab, making the shapes move, hitting refresh again… All without bothering to preserve the “complete” state of the animation! Minutes after figuring out that
reverse() won't work for me, I noticed the rectangles don’t exactly come back to their original starting point either, stopping a couple of pixels away:
I rubbed my eyes, swore, begged and googled. Suspicious it might have to do wit the SVG measurements, I dared to mess with the viewBox coordinates and found this excellent article by Sara Soueidan on SVG coordinate systems, accidentally learning a lot more about SVG behaviour than I had prepared for!
Double-checking if my
<rect> elements were sitting neatly on a pixel grid (they were!), I still couldn’t figure out what made the elements not line up following the transition. I couldn’t find a pattern in their mis-alignment, either. At first, I suspected it was down to the viewBox coordinates being different to the pixel dimensions I declared, but after some testing, endless adjustments of viewBox values, even getting rid of it altogether — and a lot of combinations of “SVG transition start end position coordinates” punched into Google — I was out of ideas, again!
The thrill of victory, the agony of defeat
So close to the end, the last thing I wanted to do was give up on the project. Stopping short of re-reading the viewBox spec for the tenth time, I swapped the SVG icon for
<span> elements, wrapped them into an
<a> and breathed a deep sigh of relief. The
<span>s aligned perfectly with each transform. I quickly added open and closed menu states to round off the interaction and make it resemble an actual menu button — my little project was finally working!
Done better than perfect?
Before I even declared a single variable, it crossed my mind to attempt writing a more technical post. Guiding the reader through the process, juggling front-end development terms and techniques with ease of a circus college graduate, throwing in some rationale behind my decisions... That sounded tempting!
The idea got verified — and quickly! I have never written one before, hell, I haven't written anything longer than a tweet or a client email in the last few years! Instead, I figured that documenting this process would serve better as an exercise in organising my thoughts. Noting down the problems, solutions and complications my (decidedly non-developer) ideas introduced to the project might just help in making something better next time, and go beyond posting an obligatory Dribbble shot.
As far as practical usage of this exercise goes, it's probably not fit for your next project — as I mentioned earlier, browser support for the Web Animations API is limited. A polyfill is available, but I find it hard to justify its use (and the extra 34kb) for animations of importance that would similar to mine (read: no importance whatsoever, just use an always-on menu). Another thing is — and that only occurred to me after spending a couple of days writing the post and building this page — it's actually quite annoying to have something constantly jumping back and forth in the corner of your screen. Wow. I might have made a useless thing!
myPlayer.reverse() and SVG viewBox work.
Know what I did wrong? You should let me know on Twitter.