I am using React-spring for first time. I am trying to use transition hook on a side drawer on my page by toggling a button.
But when I am clicking on that button there is no animation as that side drawer opens instantly, but if I click second time then side drawer is closing with animation.
And also if I click that button before that drawer removed from DOM then slide from left animation is there. I can't figure it out where is the problem. Help me please. Thanks.
Here is my code:
import React, { useState } from "react";
import { useTransition, animated, config } from "react-spring";
const Transform = (props) => {
const myStyle = {
position: "fixed",
left: 0,
top: 0,
zIndex: 100,
backgroundColor: "black",
};
const [drawerIsOpen, setDrawerState] = useState(false);
const closeDrawerHandler = () => {
setDrawerState((v) => !v);
};
const transition = useTransition(drawerIsOpen, {
form: { transform: "translateX(-100%)", opacity: 0 },
enter: { transform: "translateX(0%)", opacity: 1 },
leave: { transform: "translateX(-100%)", opacity: 0 },
config: { duration: 2000 },
// config: config.molasses,
// openDrawerHandler: () => setDrawerState(true),
});
return (
<>
{transition((style, item) =>
item ? (
<animated.aside
className='bg-white h-100 w-70 shadow'
style={{ ...style, ...myStyle }}
onClick={closeDrawerHandler}
>
<nav className='h-100'>
<h2>It's a Side Drawer</h2>
</nav>
</animated.aside>
) : (
""
)
)}
<div className='d-flex justify-content-end'>
<button className='btn btn-primary ' onClick={closeDrawerHandler}>
Toggle Btn
</button>
</div>
</>
);
};
export default Transform;
Image of transition problem
I made spelling mistake. Silly one, I spelt from as form . That's why it was happening.
Related
I don't know why my fade-out effect doesn't work, but at least fade-in works.
When you press the Open Modal button, the modal window is opened with the fade-in effect, but when you close the modal, it is closed without any fade-out effect.
Why does this happen?
How to fix it?
CodeSandbox: https://codesandbox.io/s/modal-window-5g2q8s?file=/src/components/Modal.js:157-937
const transitionStyles = {
entering: { opacity: 0, visibility: "hidden" },
entered: { opacity: 1, visibility: "visible" },
exiting: { opacity: 1, visibility: "visible" },
exited: { opacity: 0, visibility: "hidden" }
};
export const Modal = ({ children, isOpen, onClose }) => {
return ReactDOM.createPortal(
<Transition in={isOpen} timeout={500}>
{(state) => (
<>
<div
className={styles.modalBackground}
style={transitionStyles[state]}
onClick={onClose}
>
{null}
</div>
<div className={styles.modalWindow} style={transitionStyles[state]}>
{children}
</div>
</>
)}
</Transition>,
document.getElementById("portal")
);
};
I'm using react-spring to animate the transition of opening and closing an accordion component that reveals some text. Using this example on the documentation I was able to come up with a simpler version that creates a transition for the height and opacity:
function CollapseListItem({ title, text }: CollapseItemType) {
const [isOpen, setIsOpen] = useState(false);
const [ref, { height: viewHeight }] = useMeasure();
const { height, opacity } = useSpring({
from: { height: 0, opacity: 0 },
to: {
height: isOpen ? viewHeight : 0,
opacity: isOpen ? 1 : 0
}
});
const toggleOpen = () => {
setIsOpen(!isOpen);
};
return (
<>
<button onClick={toggleOpen}>
{title} click to {isOpen ? "close" : "open"}
</button>
<animated.div
ref={ref}
style={{
opacity,
height: isOpen ? "auto" : height,
overflow: "hidden"
}}
>
{text}
</animated.div>
</>
);
}
The issue is that the height transition is only being shown when you close the accordion, when you open the accordion the text suddenly appears, but on the code I can't seem to find why it only works on close, I've tried to hardcode some viewHeight values but I've had no luck.
Here's a code sandbox of what I have
After checking more examples, I realized that I was putting the ref on the wrong component, this change solve the issue:
function CollapseListItem({ title, text }: CollapseItemType) {
const [isOpen, setIsOpen] = useState(false);
const [ref, { height: viewHeight }] = useMeasure();
const props = useSpring({
height: isOpen ? viewHeight : 0,
opacity: isOpen ? 1 : 0
});
const toggleOpen = () => {
setIsOpen(!isOpen);
};
return (
<>
<button onClick={toggleOpen}>
{title} click to {isOpen ? "close" : "open"}
</button>
<animated.div
style={{
...props,
overflow: "hidden"
}}
>
<div ref={ref}>{text}</div>
</animated.div>
</>
);
}
Here's the full solution in case anyone is also trying to build an animated accordion / collapse using spring.
I am making a hamburger menu animation in React using Framer Motion. When I click on the hamburger menu, the side drawer and the navigation items slide in from the left.
When I click on the close menu icon, then the whole side drawer slides out to the left(which means the exit prop on SideDrawer component is working).
What do I want?
When I click on the close icon, I want the navigation items to slide out first and then the side drawer. I have tried adding exit prop to children navigation items. But it does not work.
How can I achieve the desired effect?
Code snippets are as below:
src/App.js
import React, { useState } from "react";
import "./App.css";
import Menu from "./components/Menu";
import SideDrawer from "./components/SideDrawer";
import Overlay from "./components/Overlay";
const App = () => {
const [menuOpen, setMenuOpen] = useState(false);
const handleMenuClick = () => {
setMenuOpen(!menuOpen);
};
return (
<div className="App">
<Menu menuOpen={menuOpen} onMenuClick={handleMenuClick} />
<SideDrawer menuOpen={menuOpen} />
<Overlay menuOpen={menuOpen} />
</div>
);
};
export default App;
src/components/Menu.js
import React, { useState } from "react";
import { motion } from "framer-motion";
const lineOneVariants = {
initial: { rotate: "0deg" },
animate: { y: ".8rem", rotate: "45deg", transformOrigin: "center center" },
};
const lineTwoVariants = {
initial: { opacity: 1 },
animate: { opacity: 0 },
};
const lineThreeVariants = {
initial: { rotate: "0deg" },
animate: { y: "-.8rem", rotate: "-45deg", transformOrigin: "center center" },
};
const Menu = ({ onMenuClick, menuOpen }) => {
return (
<div className="hamburger_menu">
<div className="hamburger_menu-line-container" onClick={onMenuClick}>
<motion.div
variants={lineOneVariants}
initial="initial"
animate={menuOpen ? "animate" : "initial"}
className="hamburger_menu-line-1"
></motion.div>
<motion.div
variants={lineTwoVariants}
initial="initial"
animate={menuOpen ? "animate" : "initial"}
className="hamburger_menu-line-2"
></motion.div>
<motion.div
variants={lineThreeVariants}
initial="initial"
animate={menuOpen ? "animate" : "initial"}
className="hamburger_menu-line-3"
></motion.div>
</div>
</div>
);
};
export default Menu;
src/components/Overlay.js
import React from "react";
import { motion } from "framer-motion";
const overlayVariants = {
initial: { opacity: 0 },
animate: { opacity: 1 },
};
const Overlay = ({ menuOpen }) => {
return (
<motion.div
variants={overlayVariants}
initial="initial"
animate={menuOpen ? "animate" : "initial"}
className="overlay"
></motion.div>
);
};
export default Overlay;
src/components/SideDrawer.js
import React from "react";
import { motion, AnimatePresence } from "framer-motion";
const drawerVariants = {
initial: {
x: "-100vw",
opacity: 0,
},
animate: {
x: 0,
opacity: 1,
transition: {
type: "linear",
ease: "easeInOut",
staggerChildren: 0.1,
delayChildren: 0.15,
},
},
};
const drawerMenuVariants = {
initial: { x: "-5rem", opacity: 0 },
animate: {
x: 0,
opacity: 1,
transition: {
type: "linear",
ease: "easeInOut",
},
},
};
const SideDrawer = ({ menuOpen }) => {
return (
<AnimatePresence>
{menuOpen && (
<motion.div
variants={drawerVariants}
initial="initial"
animate="animate"
exit="initial"
className="sideDrawer"
>
<nav className="sideDrawer-menu">
<ul>
<motion.li
variants={drawerMenuVariants}
whileHover={{ scale: 1.05 }}
>
Register/Login
</motion.li>
<motion.li
variants={drawerMenuVariants}
whileHover={{ scale: 1.05 }}
>
About
</motion.li>
<motion.li
variants={drawerMenuVariants}
whileHover={{ scale: 1.05 }}
>
Projects
</motion.li>
<motion.li
variants={drawerMenuVariants}
whileHover={{ scale: 1.05 }}
>
CV
</motion.li>
</ul>
</nav>
</motion.div>
)}
</AnimatePresence>
);
};
export default SideDrawer;
You need to tell the side drawer to wait until the exit animation for the children finishes before starting its own exit animation.
You can do this by using the when property of the transition. See Orchestration in the docs.
In your case, you'd add it to the initial variant of your drawerVariants, since that's the variant you animate to on exit:
initial: {
x: "-100vw",
opacity: 0,
transition: {
when: "afterChildren"
}
},
You probably also want to add some staggering and easing there if you want to mirror the animate in behavior, but I'll leave that up to you.
I have a div which contains an image and some text on it aligned to center
I need to make a transition with react-spring that when I scroll it should look like the text is coming from -x value to 0 and it has to be very smooth and real looking.
So I looked in to the react-spring documentation and they don't have a rich documentation on these kind of things. Only few examples.
For an example, how can I find other props for a scenario like this?
import {useTransition, animated} from 'react-spring'
const component = () => {
const props = useSpring({opacity: 1, from: {opacity: 0}}) // how can I know other parameters like opcacity, from, to etc...
return (
<animated.div>
{div contents here}
</animated.div>
)
}
And anyone to help me with the left-right transition where text come from left and lands at the center of the above mentioned image WHEN SCROLLING THROUGH?
Thank you.
I think you might be interested in translateX
from left and lands at the center of the above mentioned image
Combine the above with display: flex and align-items: center
Example
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { useSpring, animated } from "react-spring";
const style = {
background: 'url("https://picsum.photos/200/300") center center / cover no-repeat',
padding: '10px',
width: '300px',
height: '200px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}
const textStyle = {
color: 'white',
fontSize: '50px',
background: 'black'
}
const App = props => {
const [isLoaded, setLoaded] = useState(false);
const springProps = useSpring({
opacity: 1,
delay: 700,
reset: isLoaded,
transform: 'translateX(0px)',
from: {
opacity: 0,
transform: 'translateX(-250px)'
} });
useEffect(() => {
fetch("https://picsum.photos/200/300")
.then(pr => {
setLoaded(true);
})
}, [])
return <>{isLoaded ? <div style={style}>
<animated.div style={{...textStyle, ...springProps}}>Some text</animated.div>
</div> : <span></span>}</>
};
WHEN SCROLLING THROUGH?
In this case you would have to use second overload for useSpring, and use destructed set method to update values in onscroll callback
Example
const App = props => {
const [isLoaded, setLoaded] = useState(false);
const [{ param }, set] = useSpring(() => ({ param: 0 }));
const onScroll = () => {
let ratio = window.scrollY / window.innerHeight;
ratio = ratio > 1 ? 1 : ratio;
set({
param: ratio
});
};
useEffect(() => {
window.addEventListener("scroll", onScroll);
fetch("https://picsum.photos/200/300").then(pr => {
setLoaded(true);
});
return () => {
window.removeEventListener("scroll", onScroll);
};
}, []);
return (
<div style={containerStyle}>
{isLoaded ? (
<div style={style}>
<animated.div
style={{
...textStyle,
opacity: param.interpolate({
range: [0, 0.5, 0.75, 1],
output: [0, 0.5, 0.75, 1]
}),
transform: param
.interpolate({ range: [0, 0.5, 1], output: [-50, -25, 0] })
.interpolate(x => `translateX(${x}px)`)
}}
>
Some text
</animated.div>
</div>
) : (
<span />
)}
</div>
);
};
My Menu Drawer is working except for the css transitions. I think whta is happening is, when I change the value of menuOpen (which is a useState), the DOM rerenders and the transition never happens. How do I stop this? I think I need to use the useRef I have already, but not sure how?
My Page Component with a white div that will be the drawer:
import React, { useState, useEffect, useRef } from 'react';
import { Typography } from '#material-ui/core';
import './page.css';
function Page({ someProps }) {
const [ menuOpen, setMenuOpen ] = useState(false);
const menuRef = useRef();
const handleMenuClick = () => {
setMenuOpen(!menuOpen);
console.log('MENU CLICKED!!!!!!!!!!!!!!!!!!!!', menuOpen);
};
const handleClickOutside = (event) => {
console.log('CLICKED!!!!!!!!!!!!!!!!!!!!', event, menuRef.current);
if (menuRef.current && !menuRef.current.contains(event.target) && menuOpen === true) {
setMenuOpen(false);
}
};
useEffect(
() => {
document.addEventListener('click', handleClickOutside, false);
return () => {
document.removeEventListener('click', handleClickOutside, false);
};
},
[ menuOpen ]
);
return (
<Typography className="screen">
<div className="menuButton" onClick={handleMenuClick}>
MENU
</div>
{menuOpen && <div ref={menuRef} className={`menuContainer ${menuOpen === true ? 'isOpen' : ''}`} />}
</Typography>
);
}
export default Page;
My page.css:
.menuContainer {
position: fixed;
top: 0;
left: 0;
width: 250px;
height: 100vh;
background-color: white;
z-index: 1;
transition: margin 1s ease-in;
margin: 0 0 0 -250px;
}
.menuContainer.isOpen {
margin: 0 0 0 0px;
transition: margin 2s;
}