Reputation: 1133
I have built an animation similar to this and now I want to ensure that it will play only when the user has scrolled to a certain location such that the content being animated is actually visible. How do I do that inside a React App ?
I have tried reading up on the problem but most solutions either point to jQuery or Animate.css or waypoint js.
body{
font-family: Helvetica, Arial, sans-serif;
}
.container{
width: 50%;
margin: 0 auto;
}
@keyframes load{
from {
width: 0%
}
}
@-webkit-keyframes load{
from {
width: 0%
}
}
@-moz-keyframes load{
from {
width: 0%
}
}
@-o-keyframes load{
from {
width: 0%
}
}
.bar{
background-color: #EEE;
padding: 2px;
border-radius: 15px;
margin-bottom: 5px;
font-size: 14px;
color: #FFF;
font-weight: bold;
text-shadow: 1px 1px 1px rgba(0,0,0,0.5);
}
.bar::before{
content: attr(data-skill);
background-color: #f3b0ff;
display: inline-block;
padding: 5px 0 5px 10px;
border-radius: inherit;
animation: load 2s 0s;
-webkit-animation: load 2s 0s;
-moz-animation: load 2s 0s;
-o-animation: load 2s 0s;
}
.bar.front::before{
background-color: #ffcc33;
}
.bar.back::before{
background-color: #a6cfe3;
}
.bar.learning::before{
width: calc(20% - 10px);
}
.bar.basic::before{
width: calc(40% - 10px);
}
.bar.intermediate::before{
width: calc(60% - 10px);
}
.bar.advanced::before{
width: calc(80% - 10px);
}
.bar.expert::before{
width: calc(100% - 10px);
}
<div class="container">
<h1>Skill Set</h1>
<div class="bar learning" data-skill="TDD"></div>
<div class="bar back basic" data-skill="Python"></div>
<div class="bar back intermediate" data-skill="C#"></div>
<div class="bar front advanced" data-skill="CSS3"></div>
<div class="bar front expert" data-skill="HTML5"></div>
</div>
Upvotes: 0
Views: 1872
Reputation: 11
As Emiel mentioned, you can use the Intersection Observer API, and I personally recommend it's React implementation: react-intersection-observer.
I guess all you need is packed in one hook useInView:
const { ref, inView } = useInView({
triggerOnce: true,
});
Where "ref" - is ref, which you set to your component, I believe in your case code will be similar to this:
import React, { useRef } from "react"
import { useInView } from 'react-intersection-observer';
const SomeComponent = () => {
const { ref, inView } = useInView({
triggerOnce: true,
});
return (
<div ref={ref} className="container">
{inView && ..content of div}
</div>
);
}
This way animated content will be rendered only if it is in the view, and will trigger once thanks to optional prop "triggerOnce".
Upvotes: 1
Reputation: 517
useEffect
on the visibility variable, which could then add a class to your to-be-animated component once it becomes visible.Extended documentation from package to suit this answer
import React, { useRef } from "react"
import { useIsVisible } from "react-is-visible"
const SomeComponent = () => {
const nodeRef = useRef();
const isVisible = useIsVisible(nodeRef);
return (
<div ref={nodeRef} className={`bar${isVisible ? ' animate' : ''}`}>
{..content of div}
</div>
);
}
You will then need to edit your CSS as well
.bar{
background-color: #EEE;
padding: 2px;
border-radius: 15px;
margin-bottom: 5px;
font-size: 14px;
color: #FFF;
font-weight: bold;
text-shadow: 1px 1px 1px rgba(0,0,0,0.5);
}
.bar::before{
content: attr(data-skill);
background-color: #f3b0ff;
display: inline-block;
padding: 5px 0 5px 10px;
border-radius: inherit;
}
.bar.animate::before{
animation: load 2s 0s;
-webkit-animation: load 2s 0s;
-moz-animation: load 2s 0s;
-o-animation: load 2s 0s;
}
.bar.front::before{
background-color: #ffcc33;
Upvotes: 2
Reputation: 20944
You can do this with vanilla JavaScript. Use the Intersection Observer API to detect whenever your element has entered the viewport. When it has, add a class to element that you want to animate.
See the example below:
const observerCallback = (entries, observer) => {
for (const { target, isIntersecting } of entries) {
if (isIntersecting) {
target.classList.add('animate');
observer.unobserve(target);
}
}
};
const observer = new IntersectionObserver(observerCallback, {
root: null,
rootMargin: '0px',
threshold: [0]
});
const trigger = document.querySelector('.js-animation-trigger');
observer.observe(trigger);
body {
font-family: Helvetica, Arial, sans-serif;
}
.container {
width: 50%;
margin: 1000px auto 0;
}
@keyframes load {
from {
width: 0%
}
}
.bar {
background-color: #EEE;
padding: 2px;
border-radius: 15px;
margin-bottom: 5px;
font-size: 14px;
color: #FFF;
font-weight: bold;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
}
.bar::before {
content: attr(data-skill);
background-color: #f3b0ff;
display: inline-block;
padding: 5px 0 5px 10px;
border-radius: inherit;
animation: load 2s 0s paused;
}
/* Start playing */
.animate .bar::before {
animation-play-state: running;
}
.bar.front::before {
background-color: #ffcc33;
}
.bar.back::before {
background-color: #a6cfe3;
}
.bar.learning::before {
width: calc(20% - 10px);
}
.bar.basic::before {
width: calc(40% - 10px);
}
.bar.intermediate::before {
width: calc(60% - 10px);
}
.bar.advanced::before {
width: calc(80% - 10px);
}
.bar.expert::before {
width: calc(100% - 10px);
}
<div class="container">
<div class="js-animation-trigger">
<h1>Skill Set</h1>
<div class="bar learning" data-skill="TDD"></div>
<div class="bar back basic" data-skill="Python"></div>
<div class="bar back intermediate" data-skill="C#"></div>
<div class="bar front advanced" data-skill="CSS3"></div>
<div class="bar front expert" data-skill="HTML5"></div>
</div>
</div>
Upvotes: 2