Flip动画模块

flip动画是借助浏览器异步渲染的特性设计的一种动画思想,通常用在拖拽布局或拖拽树分支当中。

前言

当节点数量或位置发生变化后,此时立即发生回流行为reflow,回流内容之一便是重新获得节点的几何特征,比如相对视窗或浏览器的边距,但是接着浏览器并不是同步渲染节点(这里指的渲染节点是指被用户直接看到节点已经发生变化),而是会异步渲染;而Flip动画是利用浏览器异步渲染的原理来实现的。

Flip动画的基本做法步骤如下:

  1. 首先记录所有关联节点的几何数据(getBoundingClientRect())中上边距和左边距。(first)
  2. 使用append、appendChild或insertBefore等方法改变节点中相对位置。
  3. 再次获得所有关联节点回流后的的几何数据,主要是上边距和左边距。(last)
  4. 计算回流前后的位置差值,使用transform.translate方法让节点再退回最初的位置。(invert)
  5. 执行transition动画,让translate归零。(play)
  6. 动画执行完毕,则所有节点都会回到预想的位置。

Flip动画本质为一个障眼法,但是体验很不错。

简单使用

本Flip模块需要搭配其他模块或组件使用,也可手动编写部分js来实现Flip动画。

基本的做法步骤是:

  1. 创建Flip实例,获得初始位置,通过参数children获得flip节点。
  2. 使用playAll方法来执行动画。

children的写法同getEls函数的第一个参数,可以是一个节点选择器,也可以是节点数组。

  • 输出
  • HTML
  • JS
    • 1
    • 2
    • 3
    • 4
    • 5
  •                 
                    
                
  •                 
                    let btn = document.querySelector('#demo0001btn01'),
                        wrap = document.querySelector('#demo0001'),
                        children = [...wrap.children];
                    btn.onclick = ()=>{
                        //创建Flip实例,获得初始位置
                        let ins = new ax.Flip({children});
                        //随机位置(洗牌)
                        children.sort(()=>Math.random()-0.5);
                        //按新的位置更新DOM
                        wrap.innerHTML = '';
                        wrap.append(...children);
                        //执行动画
                        ins.playAll();
                    }
                    
                

通过拖拽改变一个节点位置

通过拖拽改变节点的位置,也就是我们常说的拖拽布局,我们借助原生的拖拽事件完成本例,关于drag的原生事件可查看MDN

F(first)L(last)I(invert)P(play)这四个步骤缺一不可。

  • 输出
  • HTML
  • JS
    • 1.可拖拽
    • 2.可拖拽
    • 3.可拖拽
    • 4.可拖拽
    • 5.可拖拽
  •                 
                    
                
  •                 
                    let wrap = document.querySelector('#demo0005'),
                        children = [...wrap.children],
                        ins = new ax.Flip({children},false),
                        sourceNode;
                    wrap.ondragstart = (e) => {
                        sourceNode = e.target;
                        e.dataTransfer.effectAllowed = 'move';
                        //初始化,获得flip节点和他们的初始位置(重要!)
                        ins.init();
                    };
                    wrap.ondragover = (e) => {
                        e.preventDefault();
                    };
                    wrap.ondragenter = (e) => {
                        e.preventDefault();
                        //落在wrap节点和自身上则不执行
                        if (e.target === wrap || e.target === sourceNode) return;
                        //动画中则不触发,使用本模块的节点会自动加上flip属性,该属性记录该节点的flip状态(重要!)
                        if (e.target?.flip?.playing) return;
                        //因为每一次拖拽后都会改变子节点的相对位置,所以每次都需要重新获取新顺序的节点数组
                        let children = [...wrap.children],
                            sourceIdx = children.indexOf(sourceNode),
                            targetIdx = children.indexOf(e.target);
                        if (sourceIdx < targetIdx) {
                            //如果e.target.nextElementSibling为null,那么这一句相当于wrap.appendChild(sourceNode)
                            wrap.insertBefore(sourceNode, e.target.nextElementSibling);
                        } else {
                            wrap.insertBefore(sourceNode, e.target);
                        }
                        //经过其他子节点时,执行动画(重要!)
                        ins.playAll();
                    };
                    
                

基础配置

属性 类型 默认值 说明
parent string '' 父容器选择器
children string/Element[] '' 子节点选择器
prevent boolean false 是否禁止play
duration number 300 flip动画时长,单位ms
transition string '' 自定义应用transition动画的的css属性

等待函数

属性 类型 默认值 说明
b4Play function null 所有节点play之前执行

回调函数

属性 类型 默认值 说明
onPlayed function null 单个节点play完成时回调
onPlayedAll function null 所有节点play完成时回调