Framer Motion | Viewpoint 'useAnimation' triggers after the last animation loads - reactjs

After reading Framer Motion's documentation, I couldn't find, or maybe I have missed, a possible way to trigger an animation when an element gets into a viewpoint. I've been doing a recommended way to do it, but only with one element. When I add more elements, animations will only trigger after the last element get into a viewpoint, and that shouldn't be like that. The plan is to load every element individually.
So, I've been doing research on a possible way to do it using React Intersection Observer. Unfortunately, I cannot set a valid script that could do the job.
So my goal is to: load every element individually possible using forEach method.
You can check what is happening in the link. You can see the animations under the header load only after the last animate loads.
https://xen.grga.now.sh/
import React, { useEffect } from "react";
import Button from "../components/Button";
import { motion, useAnimation } from "framer-motion";
import { useInView } from "react-intersection-observer";
import Layout from "../components/Layout";
const easing = [0.6, -0.05, 0.01, 0.99];
const fadeInUp = {
initial: {
y: 60,
opacity: 0
},
animate: {
y: 0,
opacity: 1,
transition: {
duration: 0.6,
ease: easing
}
}
};
const fadeInUp2 = {
initial: {
y: 60,
opacity: 0
},
animate: {
y: 0,
opacity: 1,
transition: {
duration: 0.6,
delay: 0.3,
ease: easing
}
}
};
const fadeInUp3 = {
initial: {
y: 60,
opacity: 0
},
animate: {
y: 0,
opacity: 1,
transition: {
duration: 0.6,
delay: 0.6,
ease: easing
}
}
};
const fadeToRight = {
initial: {
x: -100,
opacity: 0
},
animate: {
x: 0,
opacity: 1,
transition: {
duration: 0.6,
delay: 0.9,
ease: easing
}
}
};
const fadeToLeft = {
initial: {
x: 100,
opacity: 0
},
animate: {
x: 0,
opacity: 1,
transition: {
duration: 0.6,
delay: 0.9,
ease: easing
}
}
};
// const animTitle = {
// initial: {
// y: 100,
// opacity: 0
// },
// animate: {
// y: 0,
// opacity: 1,
// transition: {
// duration: 0.6,
// delay: 1.2,
// ease: easing
// }
// }
// };
const index = () => {
const controls = useAnimation();
const [ref, inView] = useInView();
useEffect(() => {
if (inView) {
controls.start("visible");
}
}, [controls, inView]);
return (
<motion.div exit={{ opacity: 0 }} initial="initial" animate="animate">
<Layout title="Digitalna agencija za sve digitalno ― Digitalko">
<section className="home">
<header>
<div className="title">
<motion.div variants={fadeInUp}>
<h6>
Trenutno traju sniženja od <span>20%</span> popusta
</h6>
</motion.div>
<motion.div variants={fadeInUp2}>
<h1>Digitalna agencija za sve digitalno, doslovno</h1>
</motion.div>
<motion.div variants={fadeInUp3}>
<h2>
We design and build beautiful brands, apps, websites and
videos for startups and tech companies.
</h2>
</motion.div>
<div className="row">
<motion.div variants={fadeToRight}>
<Button url="/test" name="Započni projekt" />
</motion.div>
<motion.div variants={fadeToLeft}>
<Button className="secondary" url="/" name="Naši radovi" />
</motion.div>
</div>
</div>
</header>
<div className="container--services">
<motion.div
ref={ref}
animate={controls}
initial="hidden"
transition={{ duration: 0.6 }}
variants={{
visible: { opacity: 1, y: 0 },
hidden: { opacity: 0, y: 200 }
}}
>
<h1>Našim klijentima pružamo ono što im je najpotrebnije</h1>
</motion.div>
<div className="columnController">
<div className="column">
<motion.div
ref={ref}
animate={controls}
initial="hidden"
transition={{ duration: 0.6, delay: 0.3 }}
variants={{
visible: { opacity: 1, y: 0 },
hidden: { opacity: 0, y: 200 }
}}
>
<p>
We’re a team of designers and developers who spend our time
solving problems and telling the stories of great companies.
We help you think more deeply about what you’re offering
people through your brand, products and services.
</p>
</motion.div>
<br />
<motion.div
ref={ref}
animate={controls}
initial="hidden"
transition={{ duration: 0.6, delay: 0.6 }}
variants={{
visible: { opacity: 1, y: 0 },
hidden: { opacity: 0, y: 200 }
}}
>
<p>
Our mission is to work with companies who want to change the
game. We want to help amazing tech startups get from zero to
one and create beautiful things that make life better.
</p>
</motion.div>
</div>
<div className="column">
<motion.div
ref={ref}
animate={controls}
initial="hidden"
transition={{ duration: 0.6, delay: 0.9 }}
variants={{
visible: { opacity: 1, y: 0 },
hidden: { opacity: 0, y: 200 }
}}
>
<p>
Our mission is to work with companies who want to change the
game. We want to help amazing tech startups get from zero to
one and create beautiful things that make life better.
</p>
</motion.div>
</div>
</div>
<motion.div
ref={ref}
animate={controls}
initial="hidden"
transition={{ duration: 0.6, delay: 0.9 }}
variants={{
visible: { opacity: 1, y: 0 },
hidden: { opacity: 0, y: 200 }
}}
>
<Button url="/test" name="Pogledaj detaljnije" />
</motion.div>
</div>
<div className="container--services">
<motion.div
ref={ref}
animate={controls}
initial="hidden"
transition={{ duration: 0.6 }}
variants={{
visible: { opacity: 1, y: 0 },
hidden: { opacity: 0, y: 200 }
}}
>
<h1>Našim klijentima pružamo ono što im je najpotrebnije</h1>
</motion.div>
</div>
</section>
</Layout>
</motion.div>
);
};
export default index;

Related

How to trigger framer motion animation onClick

I am applying a slide in animation using framer motion which slides in the User Card below on page render, but I want to trigger the animation on click of a button
Code below
const wrapperVariants = {
hidden: {
opacity: 0,
x: '100vw'
},
visible: {
opacity: 1,
x: 0,
transition: { type: 'spring', delay: 0.1 }
},
exit: {
x: "-100vh",
transition: { ease: 'easeInOut' }
}
};
<motion.div
variants={wrapperVariants}
initial="hidden"
animate="visible"
exit="exit"
>
<UserCard />
</motion.div>
<button onClick={() => {
I want to Trigger the Animation on button click not just on render
}}></button>
You can use animation control hook in framer motion and for your case it would be sth like this :
const wrapperVariants = {
hidden: {
opacity: 0,
x: '100vw',
},
visible: {
opacity: 1,
x: 0,
transition: { type: 'spring', delay: 0.1 },
},
exit: {
x: '-100vh',
transition: { ease: 'easeInOut' },
},
};
const controls = useAnimationControls();
<motion.div variants={wrapperVariants} initial="hidden" animate={controls} exit="exit">
<UserCard />
</motion.div>
<button onClick={() => controls.start('visible')}></button>
You could do something like
const [clicked, setClicked] = useState(false)
<motion.div
variants={wrapperVariants}
initial="hidden"
animate= {clicked ? 'visible' : 'hidden'}
exit="exit"
>
<button onClick={() => setClicked(!clicked)}></button>

React / Framer Motion - why are my child elements not staggered?

I'm building a balance overview within a financial services application. The content is rendered by passing props into a single <BalanceEntry> component and then mapping all available entries into the page.
Using Framer Motion, I want to stagger the entry animation of each rendered <BalanceEntry> as a child by 0.2 seconds.
<div className="pt-16">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, x: 1 }}
transition={{
delay: 0.3,
default: { ease: 'easeInOut' },
staggerChildren: 0.2,
}}
>
{TransactionData.map((item) => {
return (
<div key={item.id}>
<motion.div variants={BalanceEntryVariants}>
<BalanceEntry
transactionDate={item.transactionDate}
transactionType={item.transactionType}
transactionID={item.transactionID}
transactionAmount={item.transactionAmount}
transactionIcon={item.transactionIcon}
/>
</motion.div>
</div>
);
})}
</motion.div>
</div>
);
I have created two motion.divs based on multiple suggestions here on SO to achieve the staggering, but I still fail to have each BalanceEntry component animated with staggering, instead they are always animated as a group:
The variants are setup like this:
const BalanceEntryVariants = {
hidden: {
opacity: 0,
y: -20,
},
visible: {
opacity: 1,
x: 1,
transition: {
ease: 'easeInOut',
},
},
};
Where is my mistake to render each individual BalanceEntry staggered happening?
It seems that this can be solved by defining a variant for the parent motion.div as well, perhaps try something like:
const parent = {
visible: {
opacity: 1,
y: -20,
transition: {
when: 'beforeChildren',
staggerChildren: 0.2,
delay: 0.3,
},
},
hidden: {
opacity: 0,
x: 1,
transition: {
when: 'afterChildren',
},
},
};
Add the variant to parent motion.div:
<motion.div initial="hidden" animate="visible" variants={parent}>
...
</motion.div>
Here is a minimized example running on: stackblitz
There might be other issues as well (not sure about the purpose of y: -20 and x: 1), but hopefully this would still help.

How can I stop all the videos in the array playing at the start?

So I am using React Player. I have set up a toggle button so that I can play and pause the video. This all works fine. What I would like to do is have the first video from an array of three videos play as soon as the page loads. The problem I am having is that all the videos play. Even though you can only actually see the first video playing, if I hit pause then I can hear the audio from the other videos. What should I do to fix this?
import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { wrap } from "popmotion";
import ReactPlayer from "react-player";
export default function VideoSlider(props) {
const { videos } = props;
const [[page, direction], setPage] = useState([0, 0]);
const videoIndex = wrap(0, videos.length, page);
const paginate = (newDirection) => {
setPage([page + newDirection, newDirection]);
};
let timeout;
const handleClick = (direction) => {
paginate(direction);
};
console.log("VIDEO", videos[videoIndex]);
const [playing, setPlaying] = React.useState(true);
const play = () => setPlaying(true);
const pause = () => setPlaying(false);
return (
<motion.div className="videpSliderWrapper">
<AnimatePresence initial={false} custom={direction}>
<ReactPlayer
playing={playing}
onPlay={play}
onPause={pause}
className="videoSlider"
key={page}
url={videos[videoIndex]}
custom={direction}
initial={{
opacity: 0,
transition: {
duration: 0.5,
ease: "easeInOut",
},
}}
animate={{
opacity: 1,
transition: {
duration: 0.5,
ease: "easeInOut",
},
}}
exit={{
opacity: 0,
transition: {
duration: 0.5,
ease: "easeInOut",
},
}}
width="100%"
height="auto"
></ReactPlayer>
</AnimatePresence>
<motion.div
className="blankIcon"
initial={{ opacity: 0 }}
whileHover={{ opacity: 1 }}
whileTap={{ opacity: 1 }}
onClick={() => setPlaying(!playing)}
>
{playing ? (
<div className="pauseIcon"></div>
) : (
<div className="playIcon"></div>
)}
</motion.div>
<motion.div
className="next"
whileHover={{
backgroundColor: "#ffffff",
transition: {
duration: 0.5,
ease: "easeInOut",
},
}}
onClick={() => handleClick(1)}
></motion.div>
<motion.div
className="prev"
whileHover={{
backgroundColor: "#ffffff",
transition: {
duration: 0.5,
ease: "easeInOut",
},
}}
onClick={() => handleClick(-1)}
></motion.div>
</motion.div>
);
}

Framer Motion Loader animation

I'm new to reactjs, I made a preloader animation using Framer motion, but I want to add animation like fade out when loading finishes, or some smooth transition between load and showing my page
This is my Loader.js component
import React from "react";
import { motion } from "framer-motion";
const style = {
width: 20,
height: 20,
opacity: 1,
margin: 8,
borderRadius: 0,
display: "inline-block",
background: "#c81c60",
}
const variants = {
start: {
scale: 0.2,
rotate: 0,
},
end: {
scale: 1,
rotate: 360,
},
}
export default function Loader(props) {
return (
<div className="loader">
<motion.div
style={style}
variants={variants}
initial={"start"}
animate={"end"}
transition={{
repeat: "Infinity",
repeatType: "reverse",
ease: "anticipate",
duration: 1,
delay: 0
}}
/>
<motion.div
style={style}
variants={variants}
initial={"start"}
animate={"end"}
transition={{
repeat: "Infinity",
repeatType: "reverse",
ease: "anticipate",
duration: 1,
delay: 0.2
}}
/>
<motion.div
style={style}
variants={variants}
initial={"start"}
animate={"end"}
transition={{
repeat: "Infinity",
repeatType: "reverse",
ease: "anticipate",
duration: 1,
delay: 0.4
}}
/>
<motion.div
style={style}
variants={variants}
initial={"start"}
animate={"end"}
transition={{
repeat: "Infinity",
repeatType: "reverse",
ease: "anticipate",
duration: 1,
delay: 0.6
}}
/>
<motion.div
style={style}
variants={variants}
initial={"start"}
animate={"end"}
transition={{
repeat: "Infinity",
repeatType: "reverse",
ease: "anticipate",
duration: 1,
delay: 0.8
}}
/>
</div>
)
}
and here is where I'm showing it
import React, {useState, useEffect, useRef} from 'react'
import Navbar from '../Navbar'
import { HeroContainer, HeroBg } from './HeroElements'
import Sidebar from '../Navbar/Sidebar'
import {
useViewportScroll,
motion,
useTransform,
useMotionValue, AnimatePresence
} from 'framer-motion';
import Jobs from '../Jobs';
import { useInView } from 'react-intersection-observer';
import Footer from '../Footer';
import TypeIt from "typeit-react";
import Loader from '../Loader';
const SuperStrong = () => {
return <span style={{ color: "white" }}>your </span>;
};
const parallaxData = [
{
start: 0,
end: 500,
properties: [
{
startValue: 1,
endValue: 2,
property: 'scale',
},
],
},
];
const HeroSection = () => {
const [loading, setLoading] = useState(false);
useEffect(()=>{
setLoading(true)
setTimeout(() => {
setLoading(false);
}, 2000);
}, [])
const variants = {
visible: { opacity: 1, scale: 1, y: 0 },
hidden: {
opacity: 0,
scale: 0.65,
y: 50
}
};
const { scrollY } = useViewportScroll();
const y1 = useTransform(scrollY, [0, 300], [0, 200]);
const y2 = useTransform(scrollY, [0, 300], [0, -100]);
const [ref, inView, entry] = useInView({
/* Optional options */
threshold: 1,
triggerOnce: false
});
const [isOpen, setIsOpen]= useState(false);
const toggle = () => {
setIsOpen(!isOpen);
}
const fadeInDown = {
initial: {
y:-60,
opacity: 0,
},
animate: {
y: 0,
opacity: 1,
transition: {
duration: 0.5,
delay: 1.5,
ease: "easeInOut",
},
},
};
const fadeInUp = {
initial: {
x:0,
opacity: 0,
},
animate: {
x: 15,
opacity: 1,
transition: {
duration: 1,
delay: 0.5,
ease: "easeInOut",
},
},
};
const fadeIn = {
initial: {
x:140,
opacity: 0,
},
animate: {
x: 70,
opacity: 1,
transition: {
duration: 1,
delay: 0.5,
ease: "easeInOut",
},
},
};
return (
<>
{loading ?
(
<Loader loading={loading} />
) : (
<>
<HeroContainer>
<HeroBg>
<Sidebar isOpen={isOpen} toggle={toggle} />
<Navbar toggle={toggle} />
<div className="maintext" style={{position:'relative'}}>
<TypeIt >
<h1>transforming <SuperStrong></SuperStrong>digital workspace</h1>
</TypeIt>
</div>
</HeroBg>
</HeroContainer>
<Jobs></Jobs>
</>
)}
</>
)
}
export default HeroSection
I'm really having trouble making some nice animation when loading finishes, can anybody help me please?

StaggerChildren is not working with Framer Motion

When my app loads initially all the list items are animating together instead of the staggering effect. I am also using AnimatePresence for exit animations when my todo is deleted from state.
For Framer Motion I am using below variants
const listItemContainerVariant = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: { staggerChildren: 1 }
}
};
const listItemVariant = {
hidden: { x: -500 },
show: { x: 0, transition: { type: "spring", stiffness: 120 } },
exit: { scale: 1.1, x: 500, opacity: 0 },
hover: { scale: 1.1 }
};
I am using useState for managing my todos:
const [todos, setTodos] = useState(
localStorage.todos ? JSON.parse(localStorage.getItem("todos")) : []
);
Below is my JSX Code
<motion.ul
id="todo-list"
variants={listItemContainerVariant}
initial="hidden"
animate="show"
>
<AnimatePresence>
{todos.map((todo, index) => (
<motion.li
variants={listItemVariant}
initial="hidden"
animate="show"
exit={"exit"}
whileHover="hover"
key={todo.id}
className="list-item"
>
<span className="list-item-text">{todo.text}</span>
<button onClick={() => handleEdit(todo)} className="edit-btn">
<span role="img" aria-label="emoji">
✏️
</span>
</button>
<button
onClick={() => handleDelete(todo.id)}
className="delete-btn"
>
<span role="img" aria-label="emoji">
🗑
</span>
</button>
</motion.li>
))}
</AnimatePresence>
</motion.ul>
I figured out that for staggerChildren to work I had to take off the exit and hover properties from listItemVariant and add it separately in the motion.li component. This little fix got it working.
const listItemVariant = {
hidden: { x: -500 },
show: { x: 0, transition: { type: "spring", stiffness: 120 } }
};
<motion.li variants={listItemVariant}
exit={{ scale: 1.1, x: 500, opacity: 0 }}
whileHover={{
scale: 1.1
}}
key={todo.id}
className="list-item"
>

Resources