Harsha Limaye
Harsha Limaye

Reputation: 1133

How do I delay a css animation till I have scrolled to the element in React?

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.


  1. Is there any way to do this without using any of these ?
  2. How does React and jQuery work together without causing a conflict ? (I ask because I am also using react-bootstrap and they explicitly state that they have built it from scratch to avoid using jQuery with React) Where should i even write the jQuery code ?

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

Answers (3)

Highrow
Highrow

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

JDansercoer
JDansercoer

Reputation: 517

  1. You can always use a library like react-is-visible. This tracks the visibility of the component. You could then set a 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;
  1. Why would you want jQuery and React to work together? React should be able to achieve what you want to achieve with jQuery!

Upvotes: 2

Emiel Zuurbier
Emiel Zuurbier

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

Related Questions