拖拽页面元素+flip动画的案例

先上效果: 

实现思路和流程:

  1. 基础页面布局 给每个拖动元素加上 draggable="true"
  2. ondragstart(开始拖动某个元素时)做出 对应的处理 获得操作的具体元素 给目标元素添加对应的样式 显示透明 增加虚线描边
  3. ondragover 被拖动的元素hover到目标元素上时触发 阻止默认事件-默认不让元素拖动到自身
  4. ondragenter 拖动进行当中 对比 当前拖动的元素和 正在覆盖元素的索引 来判断操作 是上升还是下降
  5. ondragend 在结束时 样式由内部透明 虚线转为 原来的样子
  6. 配置flip动画

 基础页面布局:

<div class="list">
  <div draggable="true" class="list_item">1</div>
  <div draggable="true" class="list_item">2</div>
  <div draggable="true" class="list_item">3</div>
  <div draggable="true" class="list_item">4</div>
  <div draggable="true" class="list_item">5</div>
</div>

<style>
    .list{
      width: 750px;
      margin: 40px auto;
    }
    .list_item{
      width: 100%;
      border-radius: 8px;
      height: 52px;
      margin-bottom: 12px;
      background: rgba(40,142,145,0.9);
      color: white;
      line-height: 52px;
      padding-left: 16px;
      font-size: 18px;
      box-sizing: border-box;
      cursor: move;
      /*user-select: none;*/
    }
    .moving{
      background: transparent;
      color: transparent;
      border: 1px  dashed #ccc;
    }
</style>

实现拖动步骤

获取到总的外容器 便于下面事件委托
const list = document.querySelector('.list')
获取到所有可拖动的元素 用于记录起始位置
const item = document.querySelectorAll('.list_item')
let  sourceNode;  判断当前拖动的是哪个元素
 开始拖动的事件
list.ondragstart = e =>{
  sourceNode = e.target
  record(item)  传入item 记录起始位置
  setTimeout(()=>{
    e.target.classList.add('moving')
  },0)
  e.dataTransfer.effectAllowed = 'move'
}
list.ondragover = e => {
  e.preventDefault()
}
 拖动进行中的事件
list.ondragenter = e =>{
  e.preventDefault()
   托回到原来的位置了就什么也不做
  if(e.target === list || e.target === sourceNode){
    return false
  }
  const children = Array.from(list.children)
  const sourceIndex = children.indexOf(sourceNode)  当前劫持元素的索引值
  const targetIndex = children.indexOf(e.target)   覆盖到谁上面的索引值
  if(sourceIndex < targetIndex){
       父节点.insertBefore(要插入的节点,在谁前面) 从下向上拖动
    list.insertBefore(sourceNode,e.target.nextElementSibling)
  }else {
    list.insertBefore(sourceNode,e.target)
  }
  last([e.target,sourceNode]) 传入改变位置的两个元素 比较差异 执行filp动画
}
 拖动结束的时候取消虚线
list.ondragend = e =>{
  e.target.classList.remove('moving')
}

filp动画的函数

// 记录初始位置
function record(eleAll) {
  for( let i = 0;i < eleAll.length; i++ ) {
    const { top,left } = eleAll[i].getBoundingClientRect()
    eleAll[i]._top_ = top
    eleAll[i]._left_ = left
  }
}

/*  getBoundingClientRect()用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗(可视范围不包含卷去的部分)的位置。*/
/**
 * requestAnimationFrame 比起 setTimeout、setInterval的优势主要有两点:
 1、requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。
 2、在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。

 取消:cancelAnimationFrame(Id)

 * **/




​​​​​​​

// 记录最后的位置 并且执行动画
function last(eleAll) {
  for( let i = 0;i < eleAll.length; i++ ) {
    const dom = eleAll[i]
    const { top,left } = dom.getBoundingClientRect()
    // 新增dom时,逻辑应为 原有dom后移动,新增dom不动,故记录了位置的才添加动画 确定上一步有记录起始位置再进行下一步
    if(dom._left_) {
      // 恢复至开始位置
      dom.style.transform = `translate3d(${ dom._left_ - left }px, ${ dom._top_ - top }px,0px)`
      // play 过程,移除开始位置的设置,添加过渡
      let rafId = requestAnimationFrame(function() {
        //启用tansition,并移除翻转的改变  可以内置样式也可以用 外部类
         //dom.classList.add('active')
        dom.style.transition = 'transform 300ms ease-out'
        dom.style.transform = 'none'
      })
      dom.addEventListener('transitionend', () => {
        dom.style.transition = 'none'
         //dom.classList.remove('active')
        cancelAnimationFrame(rafId)
      })
    }
  }
}
 flip 动画思路
 f - first 记录动画开始前的位置、大小等信息 ( translateY(0px) )
 l - last  记录动画结束时的位置、大小等信息 ( translateY(100px) )
 i - invert 对动画前后数据信息的计算(translateY --> 100px,同时利用translate等操作,将dom恢复到 first位置)
 p - play 开始动画,并移除 i 步骤恢复至 first 的操作,启用tansition,动画就开始了

 整个过程其实就是,先记录好动画前后的dom位置等数据信息
 然后,利用css将dom恢复至初始位置
 最后,移除上一步恢复的状态(此时dom会自动回到last位置,只不过没有过渡效果,生硬的闪现),添加过渡效果,完成动画

完整代码:

const list = document.querySelector('.list')
  const item = document.querySelectorAll('.list_item')
  let  sourceNode; 

  list.ondragstart = e =>{
    sourceNode = e.target
    record(item)
    setTimeout(()=>{
      e.target.classList.add('moving')
    },0)
    e.dataTransfer.effectAllowed = 'move'
  }

  list.ondragover = e => {
    e.preventDefault()
  }


  list.ondragenter = e =>{
    e.preventDefault()
    if(e.target === list || e.target === sourceNode){
      return false
    }
    const children = Array.from(list.children)
    const sourceIndex = children.indexOf(sourceNode) 
    const targetIndex = children.indexOf(e.target) 
    if(sourceIndex < targetIndex){
      list.insertBefore(sourceNode,e.target.nextElementSibling)
    }else {
      list.insertBefore(sourceNode,e.target)
    }
    last([e.target,sourceNode])
  }

  list.ondragend = e =>{
    e.target.classList.remove('moving')
  }


function record(eleAll) {
  for( let i = 0;i < eleAll.length; i++ ) {
    const { top,left } = eleAll[i].getBoundingClientRect()
    eleAll[i]._top_ = top
    eleAll[i]._left_ = left
  }
}



function last(eleAll) {
  for( let i = 0;i < eleAll.length; i++ ) {
    const dom = eleAll[i]
    const { top,left } = dom.getBoundingClientRect()
    if(dom._left_) {
      dom.style.transform = `translate3d(${ dom._left_ - left }px, ${ dom._top_ - top }px,0px)`

      let rafId = requestAnimationFrame(function() {
        dom.style.transition = 'transform 300ms ease-out'
        dom.style.transform = 'none'
      })
      dom.addEventListener('transitionend', () => {
        dom.style.transition = 'none'
        cancelAnimationFrame(rafId)
      })
    }
  }
}

猜你喜欢

转载自blog.csdn.net/benlalagang/article/details/127882687