前端技术

Chrome 的 animate 动画导致的内存泄露 bug

毕业之后工作经历主要分为两个阶段,一个是面向 to C 业务,干了差不多 9 年,目前在做 to B 业务,干了差不多 3 年。个人感觉 to B 业务比 to C 业务要难干许多。产品的周期,对产品的兼容性要求远比to C 业务高。to b 业务尤其是管理系统相当复杂。经常需要排查内存泄露问题,在 to C 业务场景,很少去关注这个问题,更多的是关注交互效果。

常见的内存泄露问题,当某个模块,或某个视图销毁时,未销毁绑定的事件,导致内存泄露,未清除的定时器,一些 dom 引用未释放 等导致内存泄露

这种情况很容易排查,在 memory 面板打内存快照,很容易找到泄露点,如果对代码属性,基本上看代码,立马就能够发现问题点。

在上个月,和同事排查一个内存泄露问题,在排查业务方代码以及框架相关代码之后,确认没有任何问题的情况下,通过逐一对比法,编写 demo 确定,该问题是 chrome 浏览器的问题。

复现场景

删除一个正在运行的动画 dom 节点,会留下 detached elements.

<!DOCTYPE html>
<html>

<head>
    <title>方块从左到右动画</title>
    <style>
        .square {
            width: 50px;
            height: 50px;
            background-color: red;
            /* 你可以修改颜色 */
            position: absolute;
            /* 关键:使用 absolute 或 fixed 定位,方便移动 */
            top: 50px;
            /* 初始垂直位置,可以调整 */
            left: 0;
            /* 初始水平位置,从左边开始 */
            animation: moveRight 2s linear infinite;
            /* 应用动画 */
        }

        @keyframes moveRight {
            from {
                transform: translateX(0);
                /* 动画起始位置:不位移 */
            }

            to {
                transform: translateX(200px);
                /* 动画结束位置:向右移动 200px,可以调整 */
            }
        }
        
        /* 添加暂停动画的类 */
        .animation-paused {
            animation-play-state: paused;
        }
    </style>
</head>

<body>

    <div id="animatedSquare" class="square"></div>
    <button id="removeButton">移除方块</button>

    <script>
        let square = document.getElementById('animatedSquare');
        const removeButton = document.getElementById('removeButton');
        
        // 存储事件监听器引用,以便后续移除
        const handleRemove = () => {
            if (square) {
                // 1. 使用animation-play-state来暂停动画
                square.classList.add('animation-paused');
                
                // 2. 给一点时间让浏览器应用暂停状态
                requestAnimationFrame(() => {
                    // 3. 移除元素
                    square.remove();
                    
                    // 4. 清除引用
                    square = undefined;
                    console.log("方块已移除");
                    
                    // 5. 移除事件监听器
                    removeButton.removeEventListener('click', handleRemove);
                });
            }
        };

        removeButton.addEventListener('click', handleRemove);
    </script>
</body>

</html>

这个问题在 chrome 126 到 chrome 135 之间的版本都存在问题,哪怕是 chrome 136 canary 版本

如下视频所示:

跟 chromium 官方沟通(提 issue)

相关 issue 见:https://issues.chromium.org/u/1/issues/400635410

在即将到来的 136.0.7102.0 版本进行修复

发表评论