《善用回流,事半功倍》一个简单实验,展示回流带来的效果

258 阅读3分钟

如何引起页面回流reflow

  • 页面初次渲染,这是不可避免的一次回流;
  • 添加或删除可见的DOM元素,改变了DOM树的结构;
  • 元素的位置、尺寸、边距、内边距、边框等几何属性发生变化,改变了元素的布局;
  • 元素的内容发生变化,例如文字数量、字体、图片大小等,改变了元素的大小;
  • 元素字体大小发生变化,影响了元素的高度和宽度;
  • 改变浏览器窗口尺寸,例如resize事件发生时,影响了视口的大小;
  • 激活CSS伪类,例如:hover,改变了元素的样式;
  • 设置style属性的值,改变了元素的外观;
  • 查询或调用某些属性或方法,例如offsetWidth, offsetHeight, getComputedStyle, getBoundingClientRect等,因为浏览器为了保证返回值的准确性,会强制刷新渲染队列。

通常在DOM操作中,最常用造成回流的就是获取元素的矩形属性

如:offsetWidth, getBoundingClientRect, getComputedStyle等。 这次就用这些来展示效果。

<style>
    .container {
        height: 180px;
        width: 180px;
    }

    .move {
        height: 100px;
        width: 100px;
        background-color: lightcoral;
        transition: 1s;
    }

    .show {
        transform: translate(100px, 100px);
        opacity: 1;
    }

    .hide {
        opacity: 0;
        transform: translate(0, 0);
    }
</style>

<div class="container">
    <div class="move"></div>
</div>
<button onclick="toggle()">Click</button>
let isHide = true;
const el = document.querySelector('.move');

function toggle() {
    isHide = !isHide;

    // 如果`isHide` 说明要隐藏了
    // 如果`!isHide` 说明要从隐藏状态切换回来
    if (isHide) {
        el.className = 'move hide';
        el.style.display = 'none';
    }
    else {
        el.style.display = 'block';

        // 切换隐藏状态后 立刻触发一次回流`reflow` 可以固定住元素位置
        // 然后再设置样式 就能做到动画效果
        el.getBoundingClientRect();
        el.className = 'move show';
    }
}

首先,给按钮绑定一个切换事件,isHide控制展示。

来看效果

msedge_ErmAqCvTRK.gif

乂!!我退出动画呢??

很多人可能有这种烦恼:

我怎么让元素消失的时候有退出动画呢?

  • settimeout等动画播放完再隐藏吗?
  • 不不不,年轻人,这好吗?这不好。
  • 首先定时器不准,为什么呢??
  1. 定时器当嵌套超过4层时,最小延迟时间是4ms,MDN说的。
  2. 定时器是宏任务,受到同步任务影响,当你同步任务没有执行完毕,定时器就不会执行

那怎么办呀~ 那你能帮帮我吗

这时我们就可以监听动画结束,然后触发相应的事件了,小改造一下

function toggle() {
    isHide = !isHide;

    // 如果`isHide` 说明要隐藏了 监听动画结束后隐藏元素 再取消事件
    // 如果`!isHide` 说明要从隐藏状态切换回来
    if (isHide) {
        el.addEventListener('transitionend', () => {
            el.style.display = 'none';
        }, { 'once': true });

        el.className = 'move hide';
        el.style.display = 'none';
    }
    else {
        el.style.display = 'block';

        el.className = 'move show';
    }
}

这是加了过渡动画监听的动画 来看效果

msedge_8gBKi23jaQ.gif

啊?!!发生肾麽事了?加了过渡怎么所有动画都没了呢?

这是因为,浏览器不会一句一句去执行你的代码,而是把这些能够合并的,放入一个任务队列里,最后一起执行。

所以这里最终show的样式,并没有动画,只应用了transform属性

那么我该怎么做呢?

这时开头主角就出场了,我们手动执行一次回流,让他分别应用上这俩样式,就能实现了。

 function toggle() {
    isHide = !isHide;

    // 如果`isHide` 说明要隐藏了 监听动画结束后隐藏元素 再取消事件
    // 如果`!isHide` 说明要从隐藏状态切换回来
    if (isHide) {
        el.addEventListener('transitionend', () => {
            el.style.display = 'none';
        }, { 'once': true });

        el.className = 'move hide';
        el.style.display = 'none';
    }
    else {
        el.style.display = 'block';

        // 切换隐藏状态后 立刻触发一次回流`reflow` 可以固定住元素位置
        // 然后再设置样式 就能做到动画效果
        el.getBoundingClientRect();
        el.className = 'move show';
    }
}

msedge_nujS78xzZl.gif

湿滑

源码 gitee.com/cjl2385/dig…