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


    先上效果: 

    实现思路和流程:

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

     基础页面布局:

    1. <div class="list">
    2. <div draggable="true" class="list_item">1div>
    3. <div draggable="true" class="list_item">2div>
    4. <div draggable="true" class="list_item">3div>
    5. <div draggable="true" class="list_item">4div>
    6. <div draggable="true" class="list_item">5div>
    7. div>
    8. <style>
    9. .list{
    10. width: 750px;
    11. margin: 40px auto;
    12. }
    13. .list_item{
    14. width: 100%;
    15. border-radius: 8px;
    16. height: 52px;
    17. margin-bottom: 12px;
    18. background: rgba(40,142,145,0.9);
    19. color: white;
    20. line-height: 52px;
    21. padding-left: 16px;
    22. font-size: 18px;
    23. box-sizing: border-box;
    24. cursor: move;
    25. /*user-select: none;*/
    26. }
    27. .moving{
    28. background: transparent;
    29. color: transparent;
    30. border: 1px dashed #ccc;
    31. }
    32. 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位置,只不过没有过渡效果,生硬的闪现),添加过渡效果,完成动画
    

    完整代码:

    1. const list = document.querySelector('.list')
    2. const item = document.querySelectorAll('.list_item')
    3. let sourceNode;
    4. list.ondragstart = e =>{
    5. sourceNode = e.target
    6. record(item)
    7. setTimeout(()=>{
    8. e.target.classList.add('moving')
    9. },0)
    10. e.dataTransfer.effectAllowed = 'move'
    11. }
    12. list.ondragover = e => {
    13. e.preventDefault()
    14. }
    15. list.ondragenter = e =>{
    16. e.preventDefault()
    17. if(e.target === list || e.target === sourceNode){
    18. return false
    19. }
    20. const children = Array.from(list.children)
    21. const sourceIndex = children.indexOf(sourceNode)
    22. const targetIndex = children.indexOf(e.target)
    23. if(sourceIndex < targetIndex){
    24. list.insertBefore(sourceNode,e.target.nextElementSibling)
    25. }else {
    26. list.insertBefore(sourceNode,e.target)
    27. }
    28. last([e.target,sourceNode])
    29. }
    30. list.ondragend = e =>{
    31. e.target.classList.remove('moving')
    32. }
    33. function record(eleAll) {
    34. for( let i = 0;i < eleAll.length; i++ ) {
    35. const { top,left } = eleAll[i].getBoundingClientRect()
    36. eleAll[i]._top_ = top
    37. eleAll[i]._left_ = left
    38. }
    39. }
    40. function last(eleAll) {
    41. for( let i = 0;i < eleAll.length; i++ ) {
    42. const dom = eleAll[i]
    43. const { top,left } = dom.getBoundingClientRect()
    44. if(dom._left_) {
    45. dom.style.transform = `translate3d(${ dom._left_ - left }px, ${ dom._top_ - top }px,0px)`
    46. let rafId = requestAnimationFrame(function() {
    47. dom.style.transition = 'transform 300ms ease-out'
    48. dom.style.transform = 'none'
    49. })
    50. dom.addEventListener('transitionend', () => {
    51. dom.style.transition = 'none'
    52. cancelAnimationFrame(rafId)
    53. })
    54. }
    55. }
    56. }

  • 相关阅读:
    一个ES设置操作引发的“血案”
    前端代码静态检测工具汇总
    接口测试 Mock 实战(二) | 结合 jq 完成批量化的手工 Mock
    进程与计划任务
    PyQt5 不规则窗口的显示
    贝锐蒲公英异地组网方案,如何阻断网络安全威胁?
    web端生成pdf,前端生成pdf导出并自定义页眉页脚
    C# 进行 Starlink 仿真02:先搞个小型 Walker 星座 ===> 创建“十字形”星间链路(升轨、降轨采用不同颜色)
    【LeetCode:1488. 避免洪水泛滥 | 有序表 & 哈希表】
    白 - 权限提升和漏洞利用技巧
  • 原文地址:https://blog.csdn.net/benlalagang/article/details/127882687