I'm trying to make my input appears with an animation making its width size going from 0 to 50%. For this, I've used framer-motion. I've managed to make the animation but by default it's growing from the left to the right. Is there any way I could make it change to have it growing from the right to the left ?
sandbox simple reproduction
import { motion } from "framer-motion";
import { useState } from "react";
export default function App() {
const [toggle, setToggle] = useState(false);
return (
<div>
<button onClick={() => setToggle(!toggle)}>toggle input</button>
<div style={{ marginTop: 25 }}>
{toggle && (
<motion.input
initial={{ width: "0%" }}
animate={{ width: "50%" }}
transition={{ duration: 1, origin: 1 }}
/>
)}
</div>
</div>
);
}
Pretty sure it's not the best way of doing this but I think changing the following should fix your problem. Added x with 50vw = 50% of viewport width (0vw = 0% width) for the initial "position" and for animate x with 0 for the ending position as well as width of 50vw.
<motion.input
initial={{ width: "0vw", x: "50vw" }}
animate={{ width: "50vw", x: 0 }}
transition={{ duration: 1, origin: 1 }}
/>
You can find more details about it on the API Documentation of the motion component under Value type conversion https://www.framer.com/docs/component/##value-type-conversion.
Related
So, I have been trying to use Framer Motion for my React project. I basically want to animate the height from 0 to "auto", when the div gets rendered.
I tried the below code, but the height is not getting animated
<motion.div
initial={{ height: 0 }}
animate={{ height: "auto" }}
transition={{ duration: 0.5 }}
key={searchQuery?.length}
>
When I replaced height with width, the animation works fine, but can't figure out why the height is not getting animated. And I was unable to find any proper documentation regarding this.
Here is the CodeSandbox Link for demo.
Fixed versinn
What was wrong?
Your conditional rendering logic was in the wrong place, AnimatePresence works only if its direct child disappears.
exit prop was missing
key prop has to be stable, it can't be the length of the string
overflow: hidden have to be added so the children are invisible
Final code:
export default function App() {
const ref = useRef(null);
const [isActive, setIsActive] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div>
<input
placeholder={"Enter Keyword"}
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value);
}}
/>
<AnimatePresence>
{searchQuery?.length >= 1 && (
<motion.div
style={{ overflow: "hidden" }}
initial={{ height: 0 }}
animate={{ height: "auto" }}
transition={{ duration: 0.5 }}
exit={{ height: 0 }}
key={"container"}
>
{dataList?.map((listItem) => (
<div
style={{
padding: "1rem",
color: "#E090EE",
borderBottom: "1px solid #E1E1E1",
}}
>
{listItem.activity_title}
</div>
))}
</motion.div>
)}
</AnimatePresence>
</div>
</div>
);
}
I'm trying to animate an image gallery with framer-motion.
I'm using AnimatePresence to detect the value change and animate accordingly.
When I click any links on the same page, for example to a related product, the page renders twice.
Without AnimatePresence it works fine. What am I doing wrong here?
This version, without motion works fine, and causes no errors ( but obvs doesn't animate.. )
// useSate - Change Image
const [currentImage, setCurrentImage] = useState(0);
const galleryImages = [
image_one.url,
image_two.url,
image_three.url,
image_four.url,
]
// Click Handler
<div onClick={ () => { setCurrentImage( 1 ) } }></div>
// Gallery Image Container
<div style={{ backgroundImage: "url(" + galleryImages[currentImage] + ")" }}></div>
This version causes the links on the page to trigger a 'double reload', but the gallery works..
<AnimatePresence>
<motion.div
initial={{ opacity: 0, scale: 1.05 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
duration: .4,
ease: "easeInOut"
}}
style={{ backgroundImage: "url(" + galleryImages[currentImage] + ")" }}
key={ currentImage }
>
</motion.div>
</AnimatePresence>
I've also tried using mode="exit" (/exitBeforeEnter), and useEffect.
I am trying to use whileInView in order to animate some elements on scroll in react.
here is the codesandbox link: https://bz6msx.csb.app/
if you go and check the code then you see that while using whileInView it does not trigger the animation.
is there anything am I missing?
Thanks
I think you received an answer to this, here.
That is not bug. whileInView property is associated with Intersection
Observer API. In your CodeSandBox , h1 tag has initial state with x:
100vh. h1 tag not visible in viewport. only visible element activate
whileInView animation.
There are some tricks.
set 100vw to 95vw. Then element is visible.
<div className="App">
<motion.h1
initial={{
x: "95vw" // set 100vw to 95vw
}}
// This is not working
whileInView={{
x: 0,
transition: {
duration: 1,
type: "spring",
stiffness: 50
}
}}
>
Motion
</motion.h1>
</div>
Set WhileInView Property parent tag.
import "./styles.css";
import { motion } from "framer-motion";
import { useState } from "react";
export default function App() {
const [isInView, setIsInView] = useState(false);
return (
<motion.div
whileInView={() => {
// when element in viewport , set IsInView true!
setIsInView(true);
return {};
}}
className="App"
>
<motion.h1
initial={{
x: "100vw"
}}
// This is working
animate={
isInView && {
x: 0,
transition: {
duration: 1,
type: "spring",
stiffness: 50
}
}}
>
Motion
</motion.h1>
</motion.div>
);
}
In my react app i need to switch between components like in a carousel. I found this example to build an image carousel only using framer motion: https://codesandbox.io/s/framer-motion-image-gallery-pqvx3?file=/src/Example.tsx:1715-1725
I want to adapt this to switching between components. At the moment my page looks something like this:
const variants = {
enter: (direction: number) => {
return {
x: direction > 0 ? 100 : -100,
opacity: 0,
}
},
center: {
zIndex: 1,
x: 0,
opacity: 1,
},
exit: (direction: number) => {
return {
zIndex: 0,
x: direction < 0 ? 100 : -100,
opacity: 0,
}
},
}
const Page = () => {
const [[page, direction], setPage] = useState([0, 0])
const paginate = (newDirection: number) => {
setPage([page + newDirection, newDirection])
}
return (
<motion.div
key={page}
custom={direction}
variants={variants}
initial="enter"
animate="center"
exit="exit"
>
<!-- my components, between which I want to switch, should appear here -->
</motion.div>
)
}
how would I have to build the logic to be able to switch dynamic between my components (slides)? In the codesandbox example the images were changed via an array:
const imageIndex = wrap(0, images.length, page);
<motion.img key={page} src={images[imageIndex]} />
How could i do that to switch between jsx elements?
Edit
The answer from Joshua Wootonn is correct, but you need to add the custom prop also to the TestComp to get the animation working with dynamic variants like this:
const TestComp = ({ bg }: { bg: string }) => (
<motion.div
custom={direction}
variants={variants}
initial="enter"
animate="center"
exit="exit"
transition={{
x: { type: "spring", stiffness: 100, damping: 30 },
opacity: { duration: 0.2 },
}}
className="absolute w-full h-full"
style={{
background: bg,
}}
/>
)
A couple of things were missing from the above answer to get exit animations working.
If you want exit animations to work within AnimationPresense you need to set keys on its children
<AnimatePresence initial={false} custom={direction}>
{page === 0 && <TestComp key="0" bg="rgb(171, 135, 255)" />}
{page === 1 && <TestComp key="1" bg="rgb(68, 109, 246)" />}
{page === 2 && <TestComp key="2" bg="rgb(172, 236, 161)" />}
</AnimatePresence>
If you want to animate something in while something is still animating out without having massive content shifting, you need to take them out of the flow. (use absolute positioning and wrap with relatively positioned container)
<div style={{ position: "relative", height: "300px", width: "300px" }}>
<AnimatePresence initial={false} custom={direction}>
...
</AnimatePresence>
</div>
and on the child components
height: 100%;
width: 100%;
position: absolute;
Working codesandbox: https://codesandbox.io/s/framer-motion-carousel-animation-wetrf?file=/src/App.tsx:658-708
Your components should return <motion.div> (or <motion.section>, <motion.span> etc.).
And in the page component you should use <AnimatePresence /> component (like in the example):
<AnimatePresence initial={false} custom={direction}>
{COMPONENTS}
</AnimatePresence>
Then you have to decide which component will appear:
{page === 0 && <ComponentOne />}
{page === 1 && <ComponentTwo/>}
{page === 2 && <ComponentThree/>}
The animations you can control with variants.
You can see a quick demo here: https://codesandbox.io/s/quizzical-hypatia-7wqjc?file=/src/App.tsx
I am using react-spring to try and animate in AJAX content as it is loaded.
I have a container component that I sometimes want to animate to 'auto' from 0 and sometimes I want to animate to 100% depending on a prop that is passed in.
I have a const that I set that is then passed into a calculatedHeight property in the Transition component. I then use this to set the height property in the mounted child component's style property.
const Container = ({ data, children, stretchHeight }) => {
const loaded = data.loadStatus === 'LOADED';
const loading = data.loadStatus === 'LOADING';
const animationHeight = stretchHeight ? '100%' : 'auto';
return (
<div
className={classnames({
'data-container': true,
'is-loading': loading,
'is-loaded': loaded,
'stretch-height': stretchHeight
})}
aria-live="polite"
>
{loading &&
<div style={styles} className='data-container__spinner-wrapper'>
<LoadingSpinner />
</div>
}
<Transition
from={{ opacity: 0, calculatedHeight: 0 }}
enter={{ opacity: 1, calculatedHeight: animationHeight }}
leave={{ opacity: 0, calculatedHeight: 0 }}
config={config.slow}
>
{loaded && (styles => {
return (
<div style={{ opacity: styles.opacity, height: styles.calculatedHeight }}>
{children}
</div>
)
}
)}
</Transition>
</div>
)
}
The problem is that this causes a max callstack exceeded error as I don't think react-spring can understand the '100%' string value, only 'auto'.
Is there a work around for this?
The problem is that you switch types, you go from 0 to auto to 0%. It can interpolate auto, but that gets interpolated as a number, you're going to confuse it by mixing that number with a percentage.
PS. Maybe you can trick a little using css: https://codesandbox.io/embed/xolnko178q
Thanks to #hpalu for helping me realise what the issue was:
The problem is that you switch types, you go from 0 to auto to 0%. It
can interpolate auto, but that gets interpolated as a number, you're
going to confuse it by mixing that number with a percentage.
To resolve this I created consts for both my start and end points.
const containerHeightAnimationStart = stretchHeight ? '0%' : 0;
const containerHeightAnimationEnd = stretchHeight ? '100%' : 'auto';
I then used these in the animation:
<Transition
native
from={{ opacity: 0, height: containerHeightAnimationStart }}
enter={{ opacity: 1, height: containerHeightAnimationEnd }}
leave={{ opacity: 0, height: containerHeightAnimationStart }}
>
{loaded && (styles => {
return (
<animated.div style={styles}>
{children}
</animated.div>
)
}
)}
</Transition>
from & to need same unit (number or string)
const [percentage, setPercentage] = useState(100);
// wrong
const animationState2 = useSpring({
from:{width: 0},
to: {width: `${percentage}%`}
});
// right
const animationState2 = useSpring({
from:{width: '0%'},
to: {width: `${percentage}%`}
});