简体   繁体   中英

How can I fix my Framer Motion animation in React?

I've been attempting to recreate a similar UI that TypeForm has for creating questions.

If you notice in their editor, the right hand side scrolls like a page up and down when you use the arrows. 在此处输入图片说明

With my application, I want it to scroll in the same way but by clicking the questions on the left instead

It almost works, but theres a slight bug in the animation when clicking a question in the reverse order for the first time - it overlaps - and thats the problem. I'm trying to make it so that the questions that animate up and down never overlap each other, can you help?

Currently the way it works is by changing the initial , animate and exit values for the animation depending on whether you've selected a 'previous' question or not

在此处输入图片说明

Here is a link to a codesandbox as I can't get the snippet below to run correctly...

 import React, { useState, useEffect, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion" import uuid from 'uuid/v4'; import './styles.css'; const App = () => { const [display, setDisplay] = useState(false); const [questions, setQuestions] = useState([{id:uuid(), q: '', active:true}]); const [isPrevious, setIsPrevious] = useState(false); const [prev, setPrev] = useState(0); const toggleMenu = () => setDisplay(!display); const selectType = ()=> { setDisplay(false); setPrev(questions.findIndex(x=>x.active === true)) setQuestions(prevState => { let updated = prevState.map(question=>{ question.active = false; return question }) updated.push({id:uuid(), q: '', active:true}) return updated }) } useEffect(()=>{ setPrev(questions.findIndex(x=>x.active === true)) },[questions]) const updateQuestions = ({id, q}) => setQuestions(prevState => prevState.map((question, index) => { question.active = false; if(question.id === id) { console.log({'prev': prev, 'current': index, 'isPrev?': isPrevious}) if(prev > index) { setIsPrevious(true) } else { setIsPrevious(false) } question.q = q question.active = true } return question })) const removeQuestion = (id) => setQuestions(prevState => prevState.filter(question => question.id !== id)) return ( <> <Navbar /> <main className="flex items-stretch justify-between"> <div className="w-1/2"> {questions.map(question=> <Qshort key={question.id} removeQuestion={removeQuestion} data={question} updateQuestion={updateQuestions}/> )} <div className="relative mt-10 px-4"> <button onClick={toggleMenu} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Add your first question</button> <div className={(display ? "flex " : "hidden ") + "absolute bg-white p-3 flex-col shadow-md w-1/2 rounded-lg"}> <button onClick={() => selectType("1")} className="my-3 mt-0 p-3 hover:bg-gray-200 rounded">Short question</button> </div> </div> </div> <div className="rightSide relative w-1/2 bg-gray-200 flex flex-col items-center justify-center"> <AnimatePresence finishBeforeExit> {questions.length && <motion.div transition={{ duration: 1, type: "tween" }} key={questions[questions.findIndex(x=>x.active)].id} initial={{opacity:0, bottom:isPrevious ? '100%' : '-50%'}} animate={{opacity:1, bottom:'50%'}} exit={{opacity:0, bottom:isPrevious ? '-20%' : '100%'}} className="absolute flex flex-col w-64 w-2/3" > <p className="text-2xl pl-3 text-gray-600">{questions[questions.findIndex(x=>x.active)].q}</p> <input placeholder="Type your answer here...." type="text" className="mt-3 w-full p-3 bg-transparent border-b-4 text-2xl" /> </motion.div> } </AnimatePresence> {questions.length && <button className="absolute mb-3 mr-3 bottom-0 right-0 bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-6 rounded">Next step</button> } </div> </main> </> ); }; const Qshort = ({updateQuestion,removeQuestion,data}) => { return ( <div className={(data.active ? 'border-green-500 ':'border-transparent')+" border relative bg-gray-300 py-8 pb-12 pl-4 mb-2"}> <input onFocus={(e)=>updateQuestion({id:data.id, q:e.target.value})} onChange={(e)=>updateQuestion({id:data.id, q:e.target.value})} type="text" className="w-full bg-transparent" placeholder="Type your question here..." /> <button onClick={()=>removeQuestion(data.id)} className="absolute bottom-0 right-0 mr-3 mb-3 text-xs text-blue-500 hover:text-blue-700">Remove</button> </div> ); }; const Navbar = () => { return ( <nav className="relative flex items-center justify-between flex-wrap bg-blue-500 p-6"> <div className="flex items-center flex-shrink-0 text-white mr-6"> <span className="font-semibold text-xl tracking-tight">Aquire</span> </div> <div className="block lg:hidden"> <button className="flex items-center px-3 py-2 border rounded text-teal-200 border-teal-400 hover:text-white hover:border-white"> <svg className="fill-current h-3 w-3" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" > <title>Menu</title> <path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" /> </svg> </button> </div> </nav> ); }; const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/uuid.v4@1.0.0/index.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/framer-motion@0.6.6/dist/framer-motion.js"></script> <div id="root"></div>

I never used framer motion before. It is very interesting, kind of cool. But I think you could not update an element exit property once you setted. There is a chance that this issue will be addressed in a later release.

I refactored your problem to react-spring. I am more familiar with and I think it is more mature. It is working with this library. I do not know if changing libraries an option for you in this project.

You can define the transition this way:

const transitions = useTransition(
  questions.filter(x => x.active),
  item => item.id,
  {
    from: { opacity: 0, bottom: isPrevious.current ? "100%" : "-50%" },
    enter: { opacity: 1, bottom: "50%" },
    leave: { opacity: 0, bottom: isPrevious.current ? "-20%" : "100%" }
  }
);

And this is the way you use it.

{questions.length &&
  transitions.map(({ item, props, key }) => (
    <animated.div
      className="absolute flex flex-col w-64 w-2/3"
      key={key}
      style={props}
    >
      <p className="text-2xl pl-3 text-gray-600">{item.q}</p>
      <input
        placeholder="Type your answer here...."
        type="text"
        className="mt-3 w-full p-3 bg-transparent border-b-4 text-2xl"
      />
    </animated.div>
  ))
}

I have to change the isPrevious to ref to be sync with the other states.

const isPrevious = useRef(false);

https://codesandbox.io/s/condescending-montalcini-dywpu

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM