Please have a look at this code snippet:
const highlight = (element) => {
element.classList.remove('highlight');
setTimeout(() => {
element.classList.add('highlight');
}, 0);
element.onanimationend = () => {
element.classList.remove('highlight');
}
}
document.querySelector('body').addEventListener('click', event => {
if (event.target.hash) {
event.preventDefault();
const element = document.querySelector(event.target.hash);
highlight(element);
}
});
@keyframes highlight {
from {
background: red;
}
to {
background: white;
}
}
.highlight {
animation: highlight 5s;
}
<ul>
<li>
<a href="#foo">Foo</a>
<div>
<ul>
<li>
<a href="#foo-1">Foo 1</a>
</li>
<li>
<a href="#foo-2">Foo 2</a>
</li>
<li>
<a href="#foo-3">Foo 3</a>
</li>
</ul>
</div>
</li>
<li>
<a href="#bar">Bar</a>
</li>
<li>
<a href="#baz">Baz</a>
</li>
</ul>
<hr>
<div id="foo">
<h2>Foo</h2>
<ul>
<li id="foo-1">Foo 1</li>
<li id="foo-2">Foo 2</li>
<li id="foo-3">Foo 3</li>
</ul>
</div>
<div id="bar">
<h2>Bar</h2>
</div>
<div id="baz">
<h2>Baz</h2>
</div>
Then please run the code snippet (preferably on full page) and try:
Click on "Foo", wait a second or two, click on "Foo 1", wait another second, click on "Foo 2", wait a second, click on "Foo 2" again, wait a second, click on "Foo 3". Everything works as expected.
Now click on "Foo 1" (or "Foo 2" or "Foo 3"), wait 3 seconds and then click on "Foo". As you can see, the background color animation of "Foo" ends at the same time as the background color animation of "Foo 1" (or "Foo 2" or "Foo 3"). One could also say that the CSS class "highlight" is removed from "Foo" too early.
Why is that and how to fix this?
The issue is because the animationend
event is propagating up the DOM. This means that if the animation ends on the child li
before the parent div
, the event traverses up the DOM from the li
and is caught by the event handler on the div
which also removes the .highlight
class there.
To fix this, call stopPropagation()
on the event in the animationend
handler:
const highlight = (element) => {
element.classList.remove('highlight');
setTimeout(() => {
element.classList.add('highlight');
}, 0);
element.onanimationend = e => {
e.stopPropagation();
element.classList.remove('highlight');
}
}
document.querySelector('body').addEventListener('click', event => {
if (event.target.hash) {
event.preventDefault();
const element = document.querySelector(event.target.hash);
highlight(element);
}
});
@keyframes highlight {
from {
background: red;
}
to {
background: white;
}
}
.highlight {
animation: highlight 5s;
}
<ul>
<li>
<a href="#foo">Foo</a>
<div>
<ul>
<li><a href="#foo-1">Foo 1</a></li>
<li><a href="#foo-2">Foo 2</a></li>
<li><a href="#foo-3">Foo 3</a></li>
</ul>
</div>
</li>
<li><a href="#bar">Bar</a></li>
<li><a href="#baz">Baz</a></li>
</ul>
<hr>
<div id="foo">
<h2>Foo</h2>
<ul>
<li id="foo-1">Foo 1</li>
<li id="foo-2">Foo 2</li>
<li id="foo-3">Foo 3</li>
</ul>
</div>
<div id="bar">
<h2>Bar</h2>
</div>
<div id="baz">
<h2>Baz</h2>
</div>