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?
Related
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>
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>
);
}
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"
>
When you press the Circle button, the Box moves to the right and disappears from the screen.
Additional information (FW/tool version, etc.)
react
scss
Typescript
framer-motion
import "./style.scss";
import React, { FunctionComponent, useState } from "react";
import { useMotionValue, useTransform } from "framer-motion";
import { SwipeBox } from "./SwipeBox";
const App: FunctionComponent = () => {
const [cards, setCards] = useState([
const onClic = () => {
animateCardSwipe({ x: -1400, y: 0 });
}; <div
style={{
width: "400px",
height: "300px",
background: `${card.background}`
}}
>
) : (
</div>
);
};
export default App;
The problem is that the animation is happening on mount and you're updating state twice inside the animateCardSwipe function:
const animateCardSwipe = (animation: { x: number, y: number }) => {
setAnime({ ...anime, animation });
setTimeout(() => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
}, 200);
};
I personally like to use a more imperative approach here for starting the animation using animate:
const animateCardSwipe = (animation: { x: number, y: number }) => {
animate(x, animation.x, {
duration: 1,
onComplete: () => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
},
});
};
This implementation also waits for the animation to complete before rearranging the cards.
Full example:
import React, { FunctionComponent, useState } from "react";
import {
animate,
useAnimation,
useMotionValue,
useTransform,
MotionValue,
motion,
} from "framer-motion";
interface Props {
animate?: { x: number; y: number };
style: {
x?: MotionValue;
y?: MotionValue;
zIndex: number;
rotate?: MotionValue;
};
card: { text: string; background: string };
}
const SwipeBox: React.FunctionComponent<Props> = (props) => {
return (
<motion.div
animate={props.animate}
className="card"
style={{
...props.style,
background: "white",
borderRadius: "8px",
display: "grid",
top: 0,
left: 0,
placeItems: "center center",
position: "absolute",
width: "100%",
}}
transition={{ duration: 1 }}
>
{props.children}
</motion.div>
);
};
const App: FunctionComponent = () => {
const controls = useAnimation();
console.log(controls);
const [cards, setCards] = useState([
{ text: "Up or down", background: "red" },
{ text: "Left or right", background: "green" },
{ text: "Swipe me!", background: "gray" },
{ text: "Swipe me!", background: "purple" },
{ text: "Swipe me!", background: "yellow" },
]);
const x = useMotionValue(0);
const y = useMotionValue(0);
const animateCardSwipe = (animation: { x: number; y: number }) => {
animate(x, animation.x, {
duration: 1,
onComplete: () => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
},
});
};
const [anime, setAnime] = useState<{ animation: { x: number; y: number } }>({
animation: { x: 0, y: 0 },
});
const rotate = useTransform(x, [-950, 950], [-30, 30]);
const onClickLeft = () => {
animateCardSwipe({ x: 1400, y: 0 });
};
const onClickRight = () => {
animateCardSwipe({ x: -1400, y: 0 });
};
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<div>
{cards.map((card, index) =>
index === cards.length - 1 ? (
<SwipeBox
animate={anime.animation}
card={card}
key={index}
style={{ x, y, zIndex: index, rotate: rotate }}
>
<div
style={{
width: "400px",
height: "300px",
background: `${card.background}`,
}}
>
{card.text}
</div>
</SwipeBox>
) : (
<SwipeBox
card={card}
key={index}
style={{
zIndex: index,
}}
>
<div
style={{
width: "400px",
height: "300px",
background: `${card.background}`,
}}
>
{card.text}
</div>
</SwipeBox>
)
)}
</div>
<div style={{ zIndex: 999 }}>
<button onClick={onClickRight}>✖️</button>
<button onClick={onClickLeft}>○</button>
</div>
</div>
);
};
export default App;
Codesandbox Demo
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;