Как исправить анимацию Framer Motion в React? - PullRequest
1 голос
/ 03 февраля 2020

Я пытался воссоздать аналогичный пользовательский интерфейс, который TypeForm имеет для создания вопросов.

Если вы заметили в их редакторе, правая сторона прокручивается как страница вверх и вниз когда вы используете стрелки. enter image description here

С моим приложением я хочу, чтобы оно прокручивалось таким же образом, но вместо этого нажимая на вопросы слева

Это почти работает, но есть Небольшая ошибка в анимации при первом нажатии на вопрос в обратном порядке - он перекрывается - и это проблема. Я пытаюсь сделать так, чтобы вопросы, которые оживляют вверх и вниз, никогда не пересекались друг с другом, можете ли вы помочь?

В настоящее время это работает путем изменения initial, animate и exit значения для анимации в зависимости от того, выбрали ли вы «предыдущий» вопрос или нет

enter image description here

Вот ссылка на codesandbox , так как я не могу заставить приведенный ниже фрагмент работать правильно ...

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>

1 Ответ

1 голос
/ 04 февраля 2020

Я никогда не использовал фреймерное движение раньше. Это очень интересно, круто. Но я думаю, что вы не могли обновить свойство выхода элемента, как только вы установили. Существует вероятность того, что эта проблема будет решена в более позднем выпуске.

Я реорганизовал вашу проблему до реакции весна. Я более знаком с, и я думаю, что это более зрелый. Это работает с этой библиотекой. Я не знаю, является ли изменение библиотек одним из вариантов для вас в этом проекте.

Вы можете определить переход следующим образом:

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%" }
  }
);

И вот как вы его используете.

{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>
  ))
}

Мне нужно изменить isPrevious на ref, чтобы быть синхронизированным c с другими состояниями.

const isPrevious = useRef(false);

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

...