元素display从none改为可见时动画重播的原因与解决方法
问题解析与解决方案
先看你的代码复现的场景,确实会出现动画两次执行的情况:
JavaScript 代码
$(() => { $('#test').addClass('animate'); setTimeout(() => { $('#test').css({ display: 'none' }); setTimeout(() => { $('#test').css({ display: 'initial' }); }, 1000); }, 1000); });
CSS 代码
#test { position: relative; left: 0; } #test.animate { animation: move 1s ease forwards; } @keyframes move { 100% { left: 100px; } }
HTML 代码
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <img id="test" src="https://cdn4.buysellads.net/uu/1/3332/1524843316-62666.png">
问题1:为什么切换display会触发动画重播?
咱得从浏览器的渲染机制说起:当元素设置为display: none时,它会被完全移出渲染树(简单说就是浏览器不把它当“存在”的元素来处理)。当你把它改回initial/block等可见值时,浏览器需要重新把这个元素加入渲染树,相当于“重新创建”了这个元素的渲染状态——这时候浏览器会重新应用所有绑定的CSS动画规则,哪怕之前已经执行过一次。
而animation-fill-mode: forwards只是让动画结束后保留最后一帧的样式,但它管不了元素重新进入渲染树时的动画触发逻辑。
问题2:满足forwards要求的前提下,怎么避免重播?
给你两个最靠谱的方案,按需选择:
方案1:用visibility替代display控制显示隐藏
这是最优解,因为visibility: hidden只是让元素不可见,但它仍然存在于渲染树中,布局和所有CSS状态都保留着。切换回visibility: visible时,浏览器不需要重建渲染状态,自然不会重新触发动画。
修改后的JS代码:
$(() => { $('#test').addClass('animate'); setTimeout(() => { // 用visibility:hidden替代display:none $('#test').css({ visibility: 'hidden' }); setTimeout(() => { // 用visibility:visible替代display:initial $('#test').css({ visibility: 'visible' }); }, 1000); }, 1000); });
CSS和HTML完全不用改,这样动画只会执行一次,而且forwards的状态也能保留。
方案2:必须用display时,手动锁定动画最终状态
如果你因为布局需求必须用display: none,那可以在元素显示后,直接把动画的最终状态写死在元素上,同时移除动画类(或者禁用动画):
修改后的JS代码:
$(() => { $('#test').addClass('animate'); setTimeout(() => { $('#test').css({ display: 'none' }); setTimeout(() => { $('#test').css({ display: 'initial', // 手动设置动画最后一帧的样式 left: '100px' }).removeClass('animate'); // 移除动画类,避免重播 // 如果之后还需要再次触发动画,只需重新添加.animate类即可 // setTimeout(() => { $('#test').addClass('animate'); }, 1000); }, 1000); }, 1000); });
这个方法的核心是:既然浏览器要重新触发动画,那咱直接跳过动画,让元素一开始就处于forwards的最终状态,同时去掉动画类防止重播。
内容的提问来源于stack exchange,提问作者swinn
相关产品推荐
相关产品推荐

