使用jQuery设置CSS变量实现关键帧动画:Safari及移动端失效问题
我开发了一个以CSS变量作为动画属性的CSS关键帧动画,变量通过jQuery定义。动画在Chrome和Firefox中运行正常,但在Safari及移动端浏览器中,仅未使用该变量的动画部分能正常工作。
我已完成以下调试:
- 在所有浏览器的最新版本中测试
- Safari开发者工具显示变量已正确定义(如1200px)
- 在CSS中硬编码该变量为相同像素值时,动画运行正常
- 在jQuery中硬编码该变量为相同像素值(非计算得出)时,动画仍失效
- 将变量用于元素选择器属性(非关键帧)时,元素可正常定位
- 尝试用
transform: translateY(...)替代top:,结果一致 - 偶尔长时间打开窗口后动画会突然正常,但无法手动复现,无规律可循
问题核心:通过JS(jQuery或原生)定义变量并在关键帧中使用的组合,在Safari中存在兼容性问题。
相关代码
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <div id="main-content"> <div class="item one">Animated Using jQuery Variable</div> <div class="item two">Positioned Using jQuery Variable</div> <div class="item three">Animated Using CSS Variable</div> </div>
CSS
:root { --hardvarheight: 75vh; } #main-content { width: 100%; height: 75vh; } .item { width: 75px; height: 50px; font-size: 80%; } .one { animation: animOne 3s linear infinite alternate; position: absolute; background-color: pink; } .two { position: absolute; top: calc(var(--varheight) / 100 * 50); left: 50vw; background-color: lightblue; } .three { animation: animThree 3s linear infinite alternate; position: absolute; background-color: lightgreen; } @keyframes animOne { 0% {top: calc(var(--varheight) / 100 * 10); left: 40vw;} 50% {top: calc(var(--varheight) / 100 * 20); left: 50vw;} 100% {top: calc(var(--varheight) / 100 * 10);left: 60vw;} } @keyframes animThree { 0% {top: calc(var(--hardvarheight) / 100 * 80); left: 40vw;} 50% {top: calc(var(--hardvarheight) / 100 * 90); left: 50vw;} 100% {top: calc(var(--hardvarheight) / 100 * 80); left: 60vw;} }
JavaScript
function refHeight() { if ($('#main-content').length != 0) { $(':root').css('--varheight', Math.round($('#main-content').outerHeight()) + 'px'); } } window.addEventListener('load', refHeight); window.addEventListener('resize', refHeight);
原因分析
Safari(包括移动端Safari)对CSS关键帧的解析机制与Chrome/Firefox不同:关键帧在页面首次加载时会被解析并固化其中的变量值,后续通过JS动态修改全局(:root)变量时,Safari不会重新计算关键帧内的calc表达式。即使变量在开发者工具中显示正确,关键帧仍会使用初始解析时的变量值(通常是未定义的默认值),导致动画失效。
而Chrome和Firefox会实时监听CSS变量的变化,并自动更新关键帧中的计算结果,因此动画能正常运行。
解决方法
方法1:将变量绑定到动画元素而非全局
把CSS变量直接定义在需要动画的元素上,而非:root,这样Safari会监听元素级变量的变化并更新动画:
修改JavaScript代码:
function refHeight() { if ($('#main-content').length != 0) { const heightVal = Math.round($('#main-content').outerHeight()) + 'px'; // 给目标元素设置变量 $('.one').css('--varheight', heightVal); $('.two').css('--varheight', heightVal); } } window.addEventListener('load', refHeight); window.addEventListener('resize', refHeight);
CSS无需修改,元素级变量的作用域足够覆盖其关联的关键帧。
方法2:通过JS动态生成关键帧
每次高度变化时,用JS重新生成关键帧的CSS内容,强制Safari重新解析关键帧:
function refHeight() { if ($('#main-content').length != 0) { const height = Math.round($('#main-content').outerHeight()); // 创建新的样式标签覆盖原有关键帧 const styleEl = document.createElement('style'); styleEl.textContent = ` @keyframes animOne { 0% {top: calc(${height}px / 100 * 10); left: 40vw;} 50% {top: calc(${height}px / 100 * 20); left: 50vw;} 100% {top: calc(${height}px / 100 * 10); left: 60vw;} } `; // 移除旧的关键帧样式(可选,避免重复) const oldStyle = document.querySelector('#dynamic-anim'); if (oldStyle) oldStyle.remove(); styleEl.id = 'dynamic-anim'; document.head.appendChild(styleEl); } } window.addEventListener('load', refHeight); window.addEventListener('resize', refHeight);
方法3:使用@property显式声明变量(Safari 15.4+)
如果目标浏览器版本不低于Safari 15.4,可通过@property显式声明变量的类型,让Safari监听变量变化并更新关键帧:
在CSS开头添加:
@property --varheight { syntax: '<length>'; inherits: true; initial-value: 0px; }
此方法无需修改JS代码,@property会告诉浏览器变量的类型和行为,使其正确响应变量的动态修改。
内容的提问来源于stack exchange,提问作者Samuel Ayres

