简单的css in js动画

616 阅读3分钟

CSS IN JS动画那些事

先来了解一下重绘与回流

回流与重绘

回流必将引起重绘,重绘不一定回流

浏览器工作原理

browser-work.png

浏览器用户界面

user-interface.png

  • 重绘 外观发生变化,元素可见性,不会改变布局
  • 回流 重新计算元素的位置和几何形状

css 动画

先回顾一下css的transition和animation属性

  • transition属性

    • transition-property: string
    • transition-duration: number
    • transition-timing-function: cubic-bezier | 'ease' | 'ease-in' | 'ease-in-out' | 'ease-out' | steps | step-start | step-middle | step-end
    • transition-delay: number
  • animation属性

    • animation-duration: number
    • animation-timing-function: ^
    • animation-delay: number
    • animation-iteration-count: infinite
    • animation-direction: alternate | alternate-reverse | reverse
    • animation-fill-mode: backwards | forwards | both | none
    • animation-play-state: paused | running
    • animation-name: string

js 动画

接下来介绍一下js和css结合的动画

DOM事件中有: transitionstart, transitionend, animationstart, animationend 事件

:::tip 思考 enter active leave 三种状态 :::

抛出问题:

  1. 我们是否用过display切换block和none两种状态,想实现点动画过渡效果,但是display是立即消失立即隐藏?
  2. 你可能会想到用settimeout设置一个时间来控制显示隐藏,而中间恰好利用这段时间来进行动画过渡的操作?
  3. 那如何在中间这段时间加上动画呢?

从以下一个例子中来讲:

<div id="container"></div>
<button>点我显示/隐藏</button>
<style>
*{
    margin: 0; padding: 0;
}
.container {
    width: 100px; height: 100px; background-color: orangered;
}
.enter {
    opacity: 0;
}
.active {
    transform: opacity 0.5s;
}
.leave {
    opacity: 1;
}
</style>
<!-- js css -->
<script>
    const container = document.getElementById('container'),
        button = document.getElementsByTagName('button')[0];
</script>
<!-- 片段一 -->
<script>
    // 我们一般控制显示隐藏的操作 1
    button.addEventListener('click', function(e) {
        if(container.style.display === 'none') {
            container.style.display = '';
        } else {
            container.style.display = 'none';
        }
    });
</script>
<!-- 片段2 -->
<script>
    // 上述是不是没有看到过渡效果呢 没错
    // 接下来引入transitionend事件 (监听 transfom 的 duration 时间)
    // 1. 判断是否支持 transition
    let div = document.createElement('div'), isSupport;
    if(webkitTransition in div.style) isSupport = true;

    // 我们重新写一下 2
    // 我们介入三种状态 enter active leave
    button.addEventListener('click', function(e) {
        if(container.style.display === 'none') {
            container.style.display = '';
            enter(container);
        } else {
            // container.style.display = 'none';
            leave(container);
        }
    });

    function enter(node) {
        // enter
        node.classList.add('enter');
        // active
        node.classList.add('active');
        node.addEventListener('transitionend', function() {
            node.style.display = '';
        });
    }

    function leave(node) {
        // leave
        node.classList.add('leave');
        // active
        node.classList.add('active');
        node.addEventListener('transitionend', function() {
            node.style.display = 'none';
        });
    }
</script>
<!-- 片段三 -->
<script>
    // 此时运行代码会发现还是没有过渡效果 难道就没有办法了吗?
    // 我们思考一下:当我们加入enter 和 active 类的时候 同时添加 我们并没有触发 transitionstart 事件
    // 接下来重新改一下代码 3
    function enter(node) {
        // enter
        node.classList.add('enter');
        // 这里强制触发一下重绘 这时候你会发现效果大有不同
        window.requestAnimationFrame(function enterRaf(){
            // active
            node.classList.add('active');
            node.addEventListener('transitionend', function() {
                node.style.display = '';
            });
        })
    }

    function leave(node) {
        // leave
        node.classList.add('leave');
        // 同理
        window.requestAnimationFrame(function leaveRaf(){
            // active
            node.classList.add('active');
            node.addEventListener('transitionend', function() {
                node.style.display = 'none';
            }); 
        })
    }
</script>

总结

  1. 其实上述代码的关键在于切换不同的状态类名,在重绘一次中是不能完成动画过渡的,需要强制引发重绘

  2. 引发回流(回流必定重绘)的方法有:requestAnimationFrame, dom获取(scrollHeight, offsetHeight等等)

提出问题

  1. 🏀加入transitionend事件,其实会发现问题,多次点击之后事件会重复监听,这时就要移除事件绑定?该如何处理?
  // 把处理封装成一个函数
  function end(node) {
      node.style.display = 'none';
      node.removeEventListener('transitionend');
  }
  node.addEventListener('transitionend', end.bind(null, node));
  1. 🏀这还没有完 如果我们连续点击了多次 那该如何处理?

  2. 🏀用了transitionstart, transitionend事件之后,animationstart, animationend事件又能做出什么样的动画?

END