0
votes

While using Framer Motion API to create interaction and animations on my site, I can not find how to use it in order to trigger an animation when something is on the screen.

For example, this SVG draws correctly, but Framer does not wait for the element to be on the viewport and triggers it right after loading site:

import React, { Component } from 'react'
import { motion } from "framer-motion";

class IsometricScreen extends Component {

    constructor() {
        super()
        this.icon = {
            hidden: { pathLength: 0 },
            visible: { pathLength: 1 }
        }
    }

    render() {
        return (
            <motion.svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 1000" className="svg-mobile">
                <motion.path
                    d="M418,988.93H82c-39.76,0-72-32.24-72-72V83.07c0-39.76,32.24-72,72-72h336c39.76,0,72,32.24,72,72v833.86
                    C490,956.69,457.76,988.93,418,988.93z"
                    variants={this.icon}
                    initial="hidden"
                    animate="visible"
                    transition={{
                        default: { duration: 2, ease: "easeInOut" }
                    }}
                />
            </motion.svg>
        )
    }
}

export default IsometricScreen

Does Framer have a viewport detection triggerer to be implemented here?

2
Looks like a duplicate of stackoverflow.com/questions/58958972/…. I provided a solution over there.amann

2 Answers

14
votes

Alternatively, you can use Intersection Observer, blends pretty well with React and framer motion.

import { useInView } from "react-intersection-observer"; // 1.9K gzipped
import { motion, useAnimation } from "framer-motion";

const Component = () => {
    const animation = useAnimation();    
    const [ref, inView, entry] = useInView({ threshold: 0.1 });

    useEffect(() => {
      if (inView) {
        animation.start("visible");
      } else {
        animation.start("hidden");
      }
    }, [animation, inView]);

    const variants = {
        visible: {
          y: 0,
          opacity: 1,
          transition: { duration: 0.5, delayChilden: 0.2, staggerChildren: 0.1 },
        },
        hidden: {
          y: enter,
          opacity: 0,
        },
    }

    return (
        <motion.div
          ref={ref}
          animate={animation}
          initial="hidden"
          variants={{variants}}
        />
      );
}

You can also refine your animation by looking at entry object (entering from top or bottom, etc)

-1
votes

I have finally solved this with a tiny functional component:

function inViewport() {

    const isInViewport = el => {
        const rect = el.getBoundingClientRect()
        const vertInView = (rect.top <= window.innerHeight) && ((rect.top + rect.height) >= 0)
        const horInView = (rect.left <= window.innerWidth) && ((rect.left + rect.width) >= 0)
        return (vertInView && horInView)
    }

    this.elms = document.querySelectorAll('.showOnScreen')

    window.addEventListener("scroll", () => {
        this.elms.forEach(elm => isInViewport(elm) ? elm.classList.add('visible') : elm.classList.remove('visible'))
    })
}

export default inViewport