Firefox中JavaScript Animation API与transform动画异常问题排查
问题描述
我使用Web Animations API实现3D卡片动画,需求是让卡片上移并轻微向左/向右旋转。该效果在Chrome中正常运行,但在Firefox中,向右旋转的动画组(Animate up right和Animate down right)会出现异常——动画开始时卡片会突然重置到中心位置,再执行后续动画。
我尝试过移除commitStyles()调用且不手动给元素添加transform样式,能修复示例中的问题(但首次播放动画仍有异常),但实际项目中无法采用这种方式,且即使不手动添加样式,bug依然存在。
我推测Firefox推断的起始关键帧与预期不符,尝试过给每个动画设置起始关键帧,但不符合我的使用场景——我需要能够在任意时刻动态中断动画并播放反向动画。也尝试过在中断动画时调用commitStyles(),但没有效果。
奇怪的是向左旋转的动画完全正常,希望能找出问题的确切原因,避免以后再踩坑。
复现代码
HTML
<div class="card-container"> <div class="card"> <div class="front"> <br><br> front </div> <div class="back"> back </div> </div> </div> <button onclick="moveup()">Animate up right</button> <button onclick="reverse()">Animate down right</button> <button onclick="moveup_left()">Animate up left</button> <button onclick="reverse_left()">Animate down left</button> <p>Up right and down right are glitching in firefox</p>
CSS
.card-container { border: 1px solid red; width: 95px; height: 140px; position: absolute; top: 80px; left: 50px; align-items: center; justify-content: center; display: flex; cursor: pointer; user-select: none; } .card { border: 1px solid blue; position: relative; width: 70px; height: 96px; transform-style: preserve-3d; top: 10px; left: 10px; transition: 1s all; backface-visibility: hidden; } .back { position: absolute; width: 70px; height: 96px; backface-visibility: hidden; background-color: green; } .front { position: absolute; width: 70px; height: 96px; backface-visibility: hidden; background-color: red; transform: rotateY(180deg); }
JS
const anim = document.getElementsByClassName("card")[0].animate( [ { transform: `rotateY(180deg) rotateZ(-6deg)` } ], { duration: 0, fill: `forwards`, easing: `ease` } ); anim.finished.then(() => { anim.commitStyles(); }); function moveup() { document.getElementsByClassName("card")[0].animate( [ { transform: `translateX(-5px) translateY(-20px) rotateY(180deg) rotateZ(-6deg)` } ], { duration: 400, fill: `forwards`, easing: `ease` } ); } function moveup_left() { document.getElementsByClassName("card")[0].animate( [ { transform: `translateX(-5px) translateY(-20px) rotateY(180deg) rotateZ(6deg)` } ], { duration: 400, fill: `forwards`, easing: `ease` } ); } function reverse() { document.getElementsByClassName("card")[0].animate( [ { transform: `translateX(0px) translateY(0px) rotateY(180deg) rotateZ(-6deg)` } ], { duration: 400, fill: `forwards`, easing: `ease` } ); } function reverse_left() { document.getElementsByClassName("card")[0].animate( [ { transform: `translateX(0px) translateY(0px) rotateY(180deg) rotateZ(6deg)` } ], { duration: 400, fill: `forwards`, easing: `ease` } ); }
问题根源分析
这个问题的核心是Firefox对Web Animations API中transform属性的起始值推断逻辑与Chrome存在差异,尤其是当动画仅定义结束关键帧时:
- 初始动画的
commitStyles()影响:你通过duration为0的动画设置初始transform并调用commitStyles(),将动画最终状态同步到元素计算样式中。但Firefox在解析后续动画的起始状态时,对包含负角度旋转(rotateZ(-6deg))的transform复合值存在解析偏差,错误地将起始状态判定为元素默认的无变换状态,而非commitStyles()同步的状态;而正角度旋转(rotateZ(6deg))的transform未触发该解析bug。 - transform复合值的解析优先级:不同浏览器对transform复合值中“缺失分量”的补全逻辑不同。Chrome会继承元素当前的transform状态作为动画起始值,而Firefox在负角度旋转+位移的组合场景下,会错误重置未明确声明的变换分量。
修复方案
方案1:动态获取当前状态作为起始关键帧
通过获取元素当前的计算样式transform作为动画起始帧,既满足动态中断/反向的需求,又能避免Firefox的推断错误:
function getCurrentTransform(el) { return window.getComputedStyle(el).transform; } function moveup() { const card = document.getElementsByClassName("card")[0]; card.animate( [ { transform: getCurrentTransform(card) }, // 明确起始状态 { transform: `translateX(-5px) translateY(-20px) rotateY(180deg) rotateZ(-6deg)` } ], { duration: 400, fill: `forwards`, easing: `ease` } ); } // 同理修改reverse()、moveup_left()、reverse_left()函数
方案2:直接设置初始transform样式
将初始的duration为0的动画替换为直接修改元素的style.transform,跳过Web Animations API的样式同步环节,让浏览器对当前状态的解析更一致:
const card = document.getElementsByClassName("card")[0]; card.style.transform = `rotateY(180deg) rotateZ(-6deg)`;
方案3:统一transform属性顺序
确保所有动画的transform属性顺序完全一致(比如遵循translateX -> translateY -> rotateY -> rotateZ的固定顺序),Firefox对顺序一致的transform复合值解析更稳定,减少因顺序差异导致的状态推断错误。
总结
Firefox的这个bug本质是Web Animations API对transform状态推断的兼容性问题,核心解决思路是让动画的起始状态明确化——要么动态获取当前状态作为起始帧,要么直接修改元素样式而非依赖动画同步状态。向左旋转动画正常是因为Firefox对正角度旋转的transform解析逻辑未触发该特定bug。
内容的提问来源于stack exchange,提问作者Wilson

