I have a function that is meant to animate opening of a ul
based navigation menu. unfortunately it just snaps into place instead of animating like it should.
Anyone know how to fix this?
function transitionComplete(event) {
event.target.style.removeProperty("height")
event.target.removeEventListener('transitionend', transitionComplete)
}
function openNavMenu() {
let nav = document.querySelector("#main-nav")
let menus = nav.querySelectorAll(".menus")
for (let menu of menus) {
// disable any modified height
menu.style.height = 'initial'
//measure height
let expandedHeight = menu.offsetHeight
// set the height back to zero in prep for animation.
menu.style.height = '0px';
menu.addEventListener('transitionend', transitionComplete, true)
// HERE - I cannot figure out what I am supposed to do here
// menu.style.height = expandedHeight
requestAnimationFrame(() => {
menu.style.height = expandedHeight "px"
})
}
nav.setAttribute(expanded, "true")
}
Here is the complete code including the script above, css and html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
nav ul {
list-style: none;
}
#main-nav {
width: 80vw;
background-color: #0000000A;
display: flex;
padding: 10px 0;
margin: 20px auto;
}
#main-nav .menus {
flex: 1;
overflow: hidden;
margin-left: max(0px, (100vw - 300px) / 4);
padding-left: 20px;
transition: 1s ease height;
}
#main-nav .menus li a {
text-decoration: none;
font-family: "Crimson Text", serif;
letter-spacing: 2px;
padding: 5px 5px 10px 0;
margin: 5px 0;
display: block;
}
#main-nav .menus > li > a {
color: #7c6949;
}
#main-nav .menus > li ul {
padding-left: 20px;
}
#main-nav .menus > li ul li:not(:last-child) a {
border-bottom: 1px solid #0000000A;
}
#main-nav .control {
background: none;
border: none;
display: flex;
margin-right: 10px;
background: orange;
padding: 10px;
}
#main-nav[expanded] .control.open {
display: none;
}
#main-nav:not([expanded]) .menus {
height: 0;
}
#main-nav:not([expanded]) .control.close {
display: none;
}
#footer-nav > .menus {
letter-spacing: 2px;
padding: 0;
margin: 0;
}
#footer-nav > .menus > li ul {
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
}
#footer-nav > .menus > li ul li {
display: inline-block;
}
#footer-nav > .menus > li ul li a {
text-decoration: none;
padding: 3px 0;
text-transform: uppercase;
display: inline-block;
margin: 6px 0 0;
}
#footer-nav > .menus > li > a {
display: none;
}
</style>
</head>
<body>
<nav id="main-nav">
<ul >
<li>
<a href="#">About</a>
<ul>
<li><a href="/">Biographies</a></li>
<li><a href="/">Testimonials</a></li>
<li><a href="/">FAQ</a></li>
</ul>
</li>
<li>
<a href="#">Subscribe</a>
<ul>
<li><a href="/">Subscribe</a></li>
<li><a href="/">Renew</a></li>
</ul>
</li>
<li>
<a href="#">Contact</a>
<ul>
<li><a href="/">Contact Us</a></li>
</ul>
</li>
</ul>
<div >
<button onclick="openNavMenu()">
open
</button>
<button onclick="closeNavMenu()">
close
</button>
</div>
</nav>
<script>
const expanded = "expanded"
function transitionComplete(event) {
event.target.style.removeProperty("height")
event.target.removeEventListener('transitionend', transitionComplete)
}
function openNavMenu() {
let nav = document.querySelector("#main-nav")
let menus = nav.querySelectorAll(".menus")
for (let menu of menus) {
// disable any modified height
menu.style.height = 'initial'
//measure height
let expandedHeight = menu.offsetHeight
// replace disabled styles
menu.style.height = '0px';
menu.addEventListener('transitionend', transitionComplete, true)
// HERE - What do I do?
// menu.style.height = expandedHeight
requestAnimationFrame(() => {
menu.style.height = expandedHeight "px"
})
}
nav.setAttribute(expanded, "true")
}
function closeNavMenu() {
let nav = document.querySelector("#main-nav")
let menus = nav.querySelectorAll(".menus")
nav.removeAttribute(expanded)
for (let menu of menus) {
menu.style.removeProperty("height");
}
}
</script>
</body>
</html>
CodePudding user response:
You can't animate from a property that has a value to a property value that isn't set.
Meaning, menu.style.removeProperty("height")
this line removes the property and there for it has no value. By setting the height to 0 it will animate back to that.
function closeNavMenu() {
let nav = document.querySelector("#main-nav");
let menus = nav.querySelectorAll(".menus");
nav.removeAttribute(expanded);
for (let menu of menus) {
menu.style.height = "0px"
}
}
Another topic:
There are probably opinions about this but I believe the majority of the industry do like this (also linters are setup default like it) but don't use let
use const
, use let
for values you want to override and one example could be a for
loop where you want to increment the i, const
will assure you value won't get overridden.
CodePudding user response:
Interesting...
- in a chromimum browser (Chrome and Edge on my windows PC and Chrome on my Android phone) the animation works when opening the menu, but there is no animation when closing
- when viewed in FireFox the result is reversed, the closing animation works and the opening one doesn't
Chrome, I think, is suffering from the lack of a defined height at the start of the closing animation, as mentioned by @Dejan.S
Removing the event.target.style.removeProperty("height", event.target)
from the transitionComplete
callback fixes that issue
Unfortunately the requestAnimationFrame
doesn't seem to have the desired effect in FireFox, but the old faithful trick of setting a zero timeout seems to work
setTimeout(() => {
menu.style.height = expandedHeight "px"
}, 0)