While designing my new site, I am first focussing on mobile first strategy, I want to learn and showcase ideas.
So I have decided that I want to animate the menu on mobile to make best use of the screen. Here is a small video of what I'm trying to achieve.
Logo starting point
HTML
<header> <svg>…</svg> </header>
CSS
header { position: relative; top: 0px; left 0; height: 45px; width: 100%; background-color: #D0021B; padding: 5px; margin: 0 0 0 27px; } svg { position: absolute; top: 1px; left: -30px; z-index: 10; } header svg { transform: rotate(90deg); }
Explanation
I want the header to start half way through the circle in the logo hence the margin-left of 27px. As the header has this margin then the svg needs a left position of -30px. I want the Logo to be on top of the header so gave it a z-index of 10. I have also set the svg in the header to rotate 90º clockwise.
Logo ending point
HTML
<header class="open"> <svg>…</svg> </header>
CSS
header { position: relative; top: 0px; left 0; height: 45px; width: 100%; background-color: #D0021B; padding: 5px; margin: 0 0 0 27px; } svg { position: absolute; top: 1px; left: -30px; z-index: 10; } header.open svg { transform-origin: 28px 28px; transform: rotate(0deg); }
Explanation
When the menu is open, the header has a class="open"
and the logo will be in a horizontal position. It needs to rotate 90º anit-clockwise hence transform: rotate(0deg)
which is going from 90º to 0º. As I want the rotation to happen in the centre of the circle in the logo I need to move the place where it rotates around, aka the origin, transform-origin: 28px 28px
. With out this the default is to rotate in the centre of the image/svg.
Menu open state
HTML
<header> <button class="hidden" name="menu">Menu☰</button> <nav id="menu"> <ul> <li>Ethos</li> <li>Services</li> <li>Work</li> <li>Case Studies</li> <li>Blog</li> <li>Contact</li> <li><a href="#">Close X</a></li> </ul> </nav> </header>
CSS
button.hidden { color: #D0021B; } nav { margin-top: -40px; margin-left: -32px; } nav ul { background-color: #D0021B; color: #FAF9F9; font-family: 'Courier New'; Font-weight: 700; font-size: 2em; list-style: none; margin: 23px 0 0 -27px; padding: 35px 10px 10px 33px; border-radius: 0 0 23px 0; } nav ul a { color: #FAF9F9; } nav ul a:hover { background-color: #FAf9f9; color: #D0021B; }
Explanation
This is all pretty simple the margin top and left are to position the menu around the header. The button is hidden while the menu is open.
Menu hidden state
HTML
<header> <button name="menu">Menu☰</button> <nav class="hidden" id="menu"> <ul> <li>Ethos</li> <li>Services</li> <li>Work</li> <li>Case Studies</li> <li>Blog</li> <li>Contact</li> <li><a href="#">Close X</a></li> </ul> </nav> </header>
CSS
button { background-color: #D0021B; color: #FAF9F9; font-family: 'Courier New'; font-size: 2em; Font-weight: 700; text-decoration: underline; border: none; margin: 5px 0 0 30px; cursor: pointer; } nav.hidden { max-height: 0; }
Explanation
In this instance the button is displayed and the nav hidden, this is done by setting the max-height: 0;
. I am displaying the word "Menu" as I don't think that the 3 horizontal lines (☰), aka hamburger menu, is good enough for accessibility purposes.
Click/touch interaction and recognition
HTML
<header> <button id="menuButton" onclick="menuToggle()" name="menu">Menu☰</button> <nav class="hidden" id="menu"> <ul> <li>Ethos</li> <li>Services</li> <li>Work</li> <li>Case Studies</li> <li>Blog</li> <li>Contact</li> <li><a href="#" onclick="menuToggle()">Close X</a></li> </ul> </nav> </header>
JavaScript
const menuButton = document.querySelector("#menuButton"); const nav = document.querySelector("#menu"); const header = document.querySelector("header"); function menuToggle() { menuButton.classList.toggle("hidden"); nav.classList.toggle("hidden"); header.classList.toggle("open"); }
Explanation
In the HTML I have added an id="menuButton"
to make it easy to target incaase there are other buttons on the page, and an onclick
event listener to the bnutton and close link.
In the JavaScript I am setting up 3 variables:
- menuButton
- nav
- header
I have then created a function thattargets the 3 varibales and toggles the classes on or off depending on their current state. For example if there is no class="hidden"
then it adds it and if the is a class="hidden"
then it removes it.
Animate logo
CSS
header svg { transform-origin: 28px 28px; transform: rotate(0deg); transition: transform 2s ease; }
Explanation
To the svg style I have added a transition
property. This tells it that any transform should take 2s
(seconds) and ease
(start slowly accelerate end slowly).
Animate menu
CSS
nav { max-height: 500px; transition: max-height 2s ease; overflow: hidden; margin-top: -40px; margin-left: -32px; } nav.hidden { max-height: 0; transition: max-height 2s ease; }
Explanation
Here I have added a max-height: 500px;
to the nav this is an arbitary value that is larger than the menu will be so that the is a value to transition upon. Then I have a transition: max-height 2s ease; which performs the transition on the max-height
property for 2s
(seconds) and ease
s.
Add delays
CSS
header.open svg { transform-origin: 28px 28px; transform: rotate(-90deg); transition-delay: 0s; } header svg { transform-origin: 28px 28px; transform: rotate(0deg); transition: transform 2s ease; transition-delay: 2s; } nav { max-height: 500px; transition: max-height 2s ease; transition-delay: 2s; overflow: hidden; margin-top: -40px; margin-left: -32px; } nav.hidden { max-height: 0; transition: max-height 2s ease; transition-delay: 0s; }
Explanation
So here I want the following to happen:
- On menu open:
- Logo folds up
- Menu opens down
- on menu close:
- Menu closes up
- Logo folds down
The logo folding up should happen immediately, so no delay, transition-delay: 0s;
when that has complete the menu opens down transition-delay: 2s;
(this is 2 seconds as that is how long the first part takes).
The menu closing up should happen immediately, so no delay, transition-delay: 0s;
when that has complete the logo folds down transition-delay: 2s;
.
Fallback for no JavaScript, prefers-reduced-motion or no transition support
It is really important to deal with the following situations:
- The user has set
perfers-reduced-motion
, there are a number of reasons this may be enabled, such as:- Users may suffer with a condition where too much motion can induce migraines
- User may have disabled animations due to performance
- The browser they are using may not support animations
- The browser may have JavaScript disabled
In these, and many other, situations we should provide a fallback
HTML
<html class="no-js"> <head> … <script> document.documentElement.classList.remove("no-js"); </script> </head> <body>…</body> </html>
CSS
@media (prefers-reduced-motion) { nav.hidden max-height: 500px; } header svg { transform: rotate(-90deg); } } @supports not (transition) { nav.hidden { max-height: 500px; } } .no-js nav.hidden { max-height: 500px; } .no-js header svg { transform: rotate(-90deg); }
Explanation
In the HTML there is a class="no-js" applied and removed automatically if JavaScript is enabled, if not I can target this class for .no-js nav.hidden
& .no-js header svg
.
The media and supports queries can also be used to see if prefers-reduced-motion
is enabled or is the browser does not support transition
.
Full working example
See the Pen Code Red Logo Rotate with fallback by Dave Letorey (@code-red-uk) on CodePen.
Other articles from the blog
Money is not the only form of Reward
In some down time last week, I helped out a local charity with a Foodbank referral form.
Array.some()
An array is a list of things in programming languages, in JavaScript a method is something that you can do to a variable, such as an array. Today I am focussing on the .some() array method.