likes
comments
collection

巧用Promise实现React Todolist拖拽动画

作者站长头像
站长
· 阅读数 17

前言

之前学习Vue3时做过一个Todolist但是拖拽方面的功能还没做好,现在初学React再次挑战一下实现拖拽动画。

最终效果预览

巧用Promise实现React Todolist拖拽动画

流程

  1. 使用Vite创建项目
npm init vite@latest // 选择react即可
  1. 创建APP组件,并使用useState创建3个state
  const [items,setItems] = useState([{count:0,name:'ALL'},{count:0,name:'Done'},{count:0,name:"UNDone"}])
  const [currentBox,setCurrentBox] = useState(0)
  const [todos,setTodos] = useState([])
  1. 创建CheckBox组件和Todos组件
export function Checkbox(props){
  return (
    <div className="check-box">
      {props.items.map((item,index)=><Box changeCurrentBox ={props.changeCurrentBox} active={props.currentBox===index ? 'active':''} key={item.name} index={index} name={item.name} count={item.count} />)}
    </div>
  )
}
export function Box(props){
  const handleChangCurrentBox = ()=>{
    props.changeCurrentBox(props.index)
  }
  return (
    <span onClick={handleChangCurrentBox} className={`${props.active} box`}>{props.name}:{props.count}</span>
  )
}
import { Drag } from "./drag"
export function Todos(props){
  const handleDoneBtnClick = (index)=>{
    props.changeTodos(index,1)
  }
  const handleDelBtnClick = (index)=>{
    props.changeTodos(index,2)
  }
  return (
    <ul>
      <Drag render={props.todos.map((todo,index)=> (props.currentBox===todo.type||props.currentBox===0||props.currentBox===2&&todo.type!==1)&&<li className={`content ${todo.type===1? 'done':""}`} key={index}>
       <span className="doneBtn" onClick={()=>handleDoneBtnClick(index)} >{"✅"}</span>
       {todo.content}
       <span className="delBtn" onClick={()=>handleDelBtnClick(index)} >{"❌"}</span>
       </li>)} setItems={props.setTodos} items={props.todos}>
       </Drag>
    </ul>
  )
}
  1. 创建Drag组件使用浏览器原生DragEvent事件实现,参考DragEvent - Web API 接口参考 | MDN (mozilla.org),只做拖拽比较简单,只需要获取被拖动的元素的下标,然后监听拖动事件与对应的元素进行交换然后重新渲染即可。
  2. 拖拽动画 回想刚刚的拖拽思路,如果我们想要给拖拽过程一个动画的话,只要交换前利用transform属性给拖动元素一个位移即可做一个动画,动画完成后再进行元素的交换,一开始我是想用css3 transition属性做动画,但是实现时发现不好控制动画完成后交换的时机,于是想到使用Promise用js做一个动画效果,这样就能很好的得到动画完成的时机进行拖拽后的元素交换,核心代码如下
// 传入参数 dom元素 距离 和 时间
function animate(elements,distances,duration){
    const start = performance.now()// 获取开始时间
    let isOk = [0,0] // 创建一个数组用来判断动画是否执行完毕(此例同时只会有两个元素的动画) 
    return new Promise((res)=>{
      requestAnimationFrame(function animate(time){
        elements.forEach((element,index)=>{
          const distance = distances[index];
          const progress = (time-start)/duration*distance;
          if(Math.abs(progress)<=Math.abs(distance)){
            draw(progress,element) // 封装的动画函数
          }else{
            isOk[index]=1
          }
        })
        if(isOk.includes(0)){
          requestAnimationFrame(animate)
        }else {
          res()
        }
      })
    })
  }

源码 在线体验