You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React组件更新CSS变量后Safari未触发动画更新问题

问题:Safari无法同步CSS变量更新到CSS Animations

我在React应用中用CSS Animations实现垂直图片轮播,动画依赖JS通过setProperty设置的CSS变量。轮播容器固定尺寸,包含10个独立动画的幻灯片,SCSS代码如下:

$margin: 25px;
$max-images-in-view: 10;
$speed: 90;
$offset: calc($speed * 1000ms);

.slide {
   
  animation-iteration-count: infinite;
  animation-timing-function: linear;
  animation-duration: #{$speed}s;

  @for $i from 1 through 10 {
    &:nth-child(#{$i}) {
      animation-delay: calc(
        1000ms * $speed/$max-images-in-view * (#{$i} + 1) - $offset
      );
      animation-name: slide-animation-#{$i};
      top: 100%;

      @keyframes slide-animation-#{$i} {
        from {
          top: 100%;
          opacity: 1;
        }
          
        to {
          top: calc(
            100% - $max-images-in-view * (var(--image-reel-image-height) + $margin)
          );
          opacity: 1;
        }
      }
    }
  }
}

--image-reel-image-height变量由React组件在加载和屏幕尺寸变化时设置:

const calculateCSSImageHeight = () => {
  const imgEl = document.getElementsByClassName(Styles.imageContainer).item(0)

  if (isBrowser)
    document.documentElement.style.setProperty(
      '--image-reel-image-height',
      `${imgEl?.clientHeight ?? 0}px`
    )
}

useEffect(() => {
  calculateCSSImageHeight()
  if (typeof window !== 'undefined') window.addEventListener('resize', calculateCSSImageHeight)

  return () => {
    window.removeEventListener('resize', calculateCSSImageHeight)
  }
})

该方案在Chrome中正常,但Safari在CSS变量更新后不会同步更新动画——比如默认--image-reel-height设为300px时,Safari始终按300px执行动画,变量更新后无变化。


原因分析

Safari对CSS变量在@keyframes中的动态更新存在兼容性限制:当动画初始化时,Safari会将@keyframes中引用的CSS变量值固化,后续变量变更不会触发动画关键帧的重新计算。而Chrome等浏览器会实时追踪变量变化并更新动画逻辑,这是两者的行为差异。


解决方案

方案1:重置动画强制Safari重新计算

当CSS变量更新后,通过短暂移除并恢复动画属性,触发Safari重新初始化动画,读取最新的变量值。修改React代码如下:

const calculateCSSImageHeight = () => {
  const imgEl = document.getElementsByClassName(Styles.imageContainer).item(0)

  if (isBrowser) {
    document.documentElement.style.setProperty(
      '--image-reel-image-height',
      `${imgEl?.clientHeight ?? 0}px`
    )
    // 重置动画
    resetSlideshowAnimations()
  }
}

const resetSlideshowAnimations = () => {
  const slides = document.querySelectorAll('.slide')
  slides.forEach(slide => {
    const originalAnimName = slide.style.animationName || getComputedStyle(slide).animationName
    // 临时移除动画
    slide.style.animationName = 'none'
    // 触发重绘(强制浏览器更新DOM状态)
    void slide.offsetWidth
    // 恢复原动画
    slide.style.animationName = originalAnimName
  })
}

useEffect(() => {
  calculateCSSImageHeight()
  window.addEventListener('resize', calculateCSSImageHeight)
  return () => window.removeEventListener('resize', calculateCSSImageHeight)
}, [])

方案2:重构动画逻辑,避免在@keyframes中直接使用变量

将依赖变量的计算移到元素的CSS属性中,让动画仅控制进度而非具体数值,这样变量变化时会直接影响元素属性:

修改SCSS代码:

$margin: 25px;
$max-images-in-view: 10;
$speed: 90;
$offset: calc($speed * 1000ms);

.slide {
  --total-slide-distance: calc($max-images-in-view * (var(--image-reel-image-height) + $margin));
  animation-iteration-count: infinite;
  animation-timing-function: linear;
  animation-duration: #{$speed}s;
  animation-name: slide-animation;
  top: 100%;

  @for $i from 1 through 10 {
    &:nth-child(#{$i}) {
      animation-delay: calc(
        1000ms * $speed/$max-images-in-view * (#{$i} + 1) - $offset
      );
    }
  }
}

// 通用动画,仅控制位移进度
@keyframes slide-animation {
  from {
    transform: translateY(0);
    opacity: 1;
  }
  to {
    transform: translateY(calc(-1 * var(--total-slide-distance)));
    opacity: 1;
  }
}

此方案中,--total-slide-distance绑定在.slide元素上,变量更新时会直接影响transform的计算值,Safari能正确识别这种变化,无需额外重置动画。


内容的提问来源于stack exchange,提问作者BlondeSwan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 13:24:53