bubbleChaser
bubbleChaser

Reputation: 925

Trigger a CSS animation once the element is in view with Material-UI

I've done some research on how to trigger CSS animation once the element comes into view, and I've found the answer that makes use of IntersectionObserver and element.classList.add('.some-class-name')

Above method is demonstrated in pure CSS, but I want to implement it with Material-UI. Here is my code.

import React, { useEffect } from 'react';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles((theme) => ({
  root: {
    height: '100vh'
  },
  box: {
    opacity: 0,
    width: 100,
    height: 100,
    backgroundColor: 'red'
  },
  animated: {
    animationName: '$fadein',
    animationDuration: '1s'
  },
  '@keyframes fadein': {
    '0%': {
      opacity: 0
    },
    '100%': {
      opacity: 1
    }
  },
}));

function App() {
  const classes = useStyles();
  
  useEffect(() => {
    const observer = new IntersectionObserver((entries, observer) => {
      entries.forEach((entry) => {
        if (entry.intersectionRatio > 0) {

          // trigger animation
          entry.target.classList.add('animated');

          // remove observer
          observer.unobserve(entry.target);
        }
      });
    });

    const element = document.getElementById('item');
    observer.observe(element);      
  }, []);
  
  return (
    <div>
      <div className={classes.root} />
      <div id="item" className={classes.box} />
    </div>
  );
};

export default App;

Unfortunately, the above code isn't working and I think it's because the className 'animated' does not exist. I know Material-UI has internal logic that generates the unique className, so my question is how do I figure out the real className of 'animated'? Or, is there a better way to go about this? Any help would be appreciated.

Upvotes: 1

Views: 2995

Answers (1)

bubbleChaser
bubbleChaser

Reputation: 925

This is what I came up with.

import React, { useEffect, useState, useRef } from 'react';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles((theme) => ({
  root: {
    height: '100vh'
  },
  box: {
    opacity: 0,
    width: 100,
    height: 100,
    backgroundColor: 'red'
  },
  animated: {
    animationName: '$fadein',
    animationDuration: '1s',
    animationFillMode: 'forwards'
  },
  '@keyframes fadein': {
    '0%': {
      opacity: 0
    },
    '100%': {
      opacity: 1
    }
  }
}));

function App() {
  const classes = useStyles();

  const BoxSection = (props) => {
    const [isVisible, setVisible] = useState(false);
    const domRef = useRef();
    useEffect(() => {
      const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => setVisible(entry.isIntersecting));
      });
      observer.observe(domRef.current);
      return () => observer.unobserve(domRef.current);  // clean up
    }, []);
    return (
      <div className={`${classes.box} ${isVisible ? classes.animated : ''}`} ref={domRef}>
        {props.children}
      </div>
    );
  };

  return (
    <div>
      <div className={classes.root} />
      <BoxSection />
    </div>
  );
}

export default App;

Basically, I've decided to use state to trigger animation by adding the class like above. I've got some pointers from this article, if anyone is interested.

Upvotes: 1

Related Questions