巧用Promise实现React Todolist拖拽动画
前言
之前学习Vue3时做过一个Todolist但是拖拽方面的功能还没做好,现在初学React再次挑战一下实现拖拽动画。
最终效果预览
流程
- 使用Vite创建项目
npm init vite@latest // 选择react即可
- 创建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([])
- 创建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>
)
}
- 创建Drag组件使用浏览器原生DragEvent事件实现,参考DragEvent - Web API 接口参考 | MDN (mozilla.org),只做拖拽比较简单,只需要获取被拖动的元素的下标,然后监听拖动事件与对应的元素进行交换然后重新渲染即可。
- 拖拽动画 回想刚刚的拖拽思路,如果我们想要给拖拽过程一个动画的话,只要交换前利用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()
}
})
})
}
转载自:https://juejin.cn/post/7059262061208928263