in my HTML/CSS web project I wanted to include a CSS-based animation using @keyframes, which worked like a charm. Then I tried adding a possibility to only load the animations when they are visible to the viewer using IntersectionObserver - Intersection Observer API.
For that I followed this tutorial - (section: "Add the class when the element is scrolled into view")
So far so good but here comes my Problem:
I have three classes that are sliding in from the right and are supposed to stop at different widths. For some reason though as soon as I add the intersection observer, the animation is super laggy. They slide in, suddenly stop, slide a little bit more and then jump into the final position instead of sliding smoothly as they were before.
Here is what I have done:
const observer = new IntersectionObserver(entries => {
// Loop over the entries
entries.forEach(entry => {
// If the element is visible
if (entry.isIntersecting) {
// Add the animation class
entry.target.classList.add('rectangle-animation-one','rectangle-animation-two','rectangle-animation-three');
}
});
});
observer.observe(document.querySelector('.rectangle-one'));
observer.observe(document.querySelector('.rectangle-two'));
observer.observe(document.querySelector('.rectangle-three'));
.section-flex{
display:flex;
flex-direction: column;
padding: 30px;
}
.rectangle-one {
margin-left: auto;
height: 125px;
line-height: 125px;
width: 80%;
font-size: 20px;
color: #fff;
text-align: center;
background: #EC6F72;
box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-one {
animation-duration: 1s;
animation-name: slidein-one;
animation-iteration-count: 1;
animation-direction:normal;
}
@keyframes slidein-one {
from {
margin-left:100%;
width:300%
}
to {
margin-left:20%;
}
}
.rectangle-two {
margin-top: 0px;
margin-left: auto;
height: 125px;
line-height: 125px;
width: 55%;
font-size: 20px;
color: #fff;
text-align: center;
background: #FFB94D;
box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-two {
animation-duration: 1s;
animation-name: slidein-two;
animation-iteration-count: 1;
animation-direction:normal;
}
@keyframes slidein-two {
from {
margin-left:100%;
width:300%
}
to {
margin-left:45%;
}
}
.rectangle-three {
margin-top: 0px;
margin-left: auto;
height: 125px;
line-height: 125px;
width: 40%;
font-size: 20px;
color: #fff;
text-align: center;
background: #3C51AA;
box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-three {
animation-duration: 1s;
animation-name: slidein-three;
animation-iteration-count: 1;
animation-direction:normal;
}
@keyframes slidein-three {
from {
margin-left:100%;
width:300%
}
to {
margin-left:60%;
}
}
<div >
<div >
<p>TEXT 1</p>
</div>
<div >
<p>TEXT 2</p>
</div>
<div >
<p>TEXT 3</p>
</div>
</div>
<script src="app.js"></script>
Any ideas on what I'm doing wrong here? I'm almost certain its the way I'm trying to add all three animations in the javascript code, I've tried all sorts of combinations but can't get it right.
Thanks for any help
CodePudding user response:
Okay, I'll be honest, there could be a bunch of reason why your code above isn't doing the thing you think it is, but I think mainly because, be it transformed or placed with margins etc, your off-screen content stradles the line between getting on screen, which ruins your animation. I would suggest using a simpler setup to make this work.
In general, we want to wrap your content inside another element that doesn't move, but is used a the trigger for appearing on screen. When the wrapping (section
in my example) section intersects, it will add a class, which should trigger a transition
on your children.
Transitions are useful when something toggles between two states - it means you don't really have to keep in mind the animation yourself. Animations are best used when you need multiple keyframes with different values, but here, there are two states with a value each.
This would look something like this:
const observer = new IntersectionObserver(entries => {
// Loop over the entries
entries.forEach(entry => {
// Let's just toggle a class here
// We can respond to it however we want in CSS
entry.target.classList.toggle('in-view', entry.isIntersecting);
});
});
observer.observe(document.querySelector('section#one', { treshold: 1 }));
observer.observe(document.querySelector('section#two', { treshold: 1 }));
observer.observe(document.querySelector('section#three', { treshold: 1 }));
main {
display: flex;
flex-direction: column;
padding: 30px;
align-items: flex-end;
}
/* I have made this 100vh high, just so we can see the effect properly. Remove the 100vh below to see something more akin to your original. */
section {
width: 100%;
height: 100vh;
display: flex;
justify-content: flex-end;
}
/* I have made this bit shared, so we don't get lost in the repeated CSS. It's fine otherwise, but this way we don't focus on the wrong thing. */
section > div {
height: 125px;
line-height: 125px;
width: 80%;
font-size: 20px;
color: #fff;
text-align: center;
background: #EC6F72;
box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
transform: translateX(100vw);
/* Instead of an animation, perhaps a transition will do better. */
transition: transform .4s;
}
section.in-view > div {
/* When a section contains in view, the div inside it will slide in using a smooth transform. */
transform: translateX(0);
}
section#two > div {
width: 55%;
color: #fff;
background: #FFB94D;
}
section#three > div {
width: 40%;
color: #fff;
background: #3C51AA;
}
<main>
<!--
So we need to split this up, one wrapping <section> (the type of element doesn't matter) will be used to detect when the content should come into view.
-->
<section id="one">
<div>
<p>TEXT 1</p>
</div>
</section>
<section id="two">
<div>
<p>TEXT 2</p>
</div>
</section>
<section id="three">
<div>
<p>TEXT 3</p>
</div>
</section>
</main>
I hope this helps a bit. I know it's not directly solving the animation, but animations are hard enough to pull off, so if you can solve it with a transition, that is usually the most reasonable route to take.
CodePudding user response:
After playing around with it and getting some help from @somethinghere (Thanks a lot!) I figured out what was going wrong on my side..
For anyone else stuck with the same problem, I couldn't simply add all animations via one constant observer in my .js file. I now added an individual observer const per class and it now looks like this:
const observer = new IntersectionObserver(entries => {
// Loop over the entries
entries.forEach(entry => {
// If the element is visible
if (entry.isIntersecting) {
// Add the animation class
entry.target.classList.add('rectangle-animation-one', entry.isIntersecting);
}
});
});
observer.observe(document.querySelector('.rectangle-one'));
const observer2 = new IntersectionObserver(entries => {
// Loop over the entries
entries.forEach(entry => {
// If the element is visible
if (entry.isIntersecting) {
// Add the animation class
entry.target.classList.add('rectangle-animation-two', entry.isIntersecting);
}
});
});
observer2.observe(document.querySelector('.rectangle-two'));
const observer3 = new IntersectionObserver(entries => {
// Loop over the entries
entries.forEach(entry => {
// If the element is visible
if (entry.isIntersecting) {
// Add the animation class
entry.target.classList.add('rectangle-animation-three', entry.isIntersecting);
}
});
});
observer3.observe(document.querySelector('.rectangle-three'));
.section-flex{
display:flex;
flex-direction: column;
padding: 30px;
}
.rectangle-one {
margin-left: auto;
height: 125px;
line-height: 125px;
width: 80%;
font-size: 20px;
color: #fff;
text-align: center;
background: #EC6F72;
box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-one {
animation-duration: 1s;
animation-name: slidein-one;
animation-iteration-count: 1;
animation-direction:normal;
}
@keyframes slidein-one {
from {
margin-left:100%;
width:300%
}
to {
margin-left:20%;
}
}
.rectangle-two {
margin-top: 0px;
margin-left: auto;
height: 125px;
line-height: 125px;
width: 55%;
font-size: 20px;
color: #fff;
text-align: center;
background: #FFB94D;
box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-two {
animation-duration: 1s;
animation-name: slidein-two;
animation-iteration-count: 1;
animation-direction:normal;
}
@keyframes slidein-two {
from {
margin-left:100%;
width:300%
}
to {
margin-left:45%;
}
}
.rectangle-three {
margin-top: 0px;
margin-left: auto;
height: 125px;
line-height: 125px;
width: 40%;
font-size: 20px;
color: #fff;
text-align: center;
background: #3C51AA;
box-shadow: 0px 7px 24px 3px rgba(0, 0, 0, 0.25);
}
.rectangle-animation-three {
animation-duration: 1s;
animation-name: slidein-three;
animation-iteration-count: 1;
animation-direction:normal;
}
@keyframes slidein-three {
from {
margin-left:100%;
width:300%
}
to {
margin-left:60%;
}
}
<div >
<div >
<p>TEXT 1</p>
</div>
<div >
<p>TEXT 2</p>
</div>
<div >
<p>TEXT 3</p>
</div>
</div>
<script src="app.js"></script>