Web Animation性能优化:从EffectTiming到动画合成

0 阅读7分钟

🌟 前言

现代 Web 开发中,流畅的动画效果对用户体验至关重要。Web Animation API 提供了浏览器原生的 JavaScript 动画控制接口,相比传统方案,它提供更精细的控制、更优的性能和更强的组合能力。

Web Animation API 主要 API

API描述用途
Element.animate()创建并返回一个 Animation 对象用于在元素上创建动画
Animation动画对象的构造函数表示单个动画实例
KeyframeEffect关键帧效果对象的构造函数定义动画的关键帧和效果
AnimationTimeline动画时间线接口管理动画的时间控制
AnimationEffect动画效果接口定义动画效果的基本结构
Document.getAnimations()获取文档中所有动画返回文档中所有活动动画的数组
Animation.play()播放动画启动动画播放
Animation.pause()暂停动画暂停当前动画
Animation.reverse()反向播放动画以相反方向播放动画
Animation.seek()跳转到指定时间点设置动画播放位置
Animation.finish()完成动画快速完成动画播放
Animation.cancel()取消动画停止并取消动画
Animation.updatePlaybackRate()更新播放速率改变动画播放速度
Animation.playbackRate获取或设置播放速率控制动画播放速度
Animation.startTime获取或设置开始时间控制动画开始时间
Animation.currentTime获取或设置当前时间获取或设置动画当前位置
Animation.effect获取或设置动画效果获取或设置动画效果对象
Animation.timing获取或设置动画计时配置动画的计时属性
Animation.pending判断动画是否挂起检查动画是否处于挂起状态
Animation.playState获取动画播放状态获取动画当前状态
Animation.ready动画准备就绪的 Promise当动画准备好时触发
Animation.finished动画完成的 Promise当动画完成后触发
Animation.onfinish动画完成事件回调处理动画完成事件
Animation.oncancel动画取消事件回调处理动画取消事件
Animation.onremove动画移除事件回调处理动画移除事件

unsetunset🏗️ 一、核心概念解析unsetunset

1. 基础动画实现

const element = document.getElementById('myElement');

// 基础动画示例
const animation = element.animate(
  [
    { opacity0, transform'translateY(-50px)' }, 
    { opacity1, transform'translateY(0)' }
  ],
  {
    duration800,      // 动画时长(ms)
    easing: 'ease-out',  // 缓动函数
    fill: 'forwards'    // 保持结束状态
  }
);

// 播放动画
animation.play();

2. 关键帧序列详解

// 多关键帧序列示例(包含偏移设置)
const complexAnimation = element.animate(
  [
    { transform'scale(1)', backgroundColor'blue', offset0 },
    { transform'scale(1.5)', backgroundColor'purple', offset0.3 },
    { transform'scale(1.2)', backgroundColor'red', offset0.7 },
    { transform'scale(1)', backgroundColor'green', offset1 }
  ],
  {
    duration2000,
    iterations: Infinity,
    direction'alternate'
  }
);

unsetunset⚙️ 二、精细控制技术unsetunset

1. 动画控制函数

// 创建动画
const bounceAnimation = element.animate(
  [{ transform'translateY(0)' }, { transform'translateY(-100px)' }],
  { duration1000 }
);

// 控制方法
const playBtn = document.getElementById('play');
playBtn.addEventListener('click'() => {
  if (bounceAnimation.playState === 'paused') {
    bounceAnimation.play();
  } else {
    bounceAnimation.pause();
  }
});

// 跳转到特定时间点
document.getElementById('jump').addEventListener('click'() => {
  bounceAnimation.currentTime = 300// 跳转到300ms位置
});

// 速率控制
document.getElementById('speedUp').addEventListener('click'() => {
  bounceAnimation.playbackRate *= 1.5// 加速50%
});

// 结束后重置
bounceAnimation.onfinish = () => {
  element.animate(
    [{ transform'translateY(0)' }],
    { duration300fill'forwards' }
  );
};

2. 动画事件监听

const fadeInAnimation = element.animate(
  [{ opacity: 0 }, { opacity: 1 }],
  { duration: 1500 }
);

// 事件监听
fadeInAnimation.onstart = () => {
  console.log('动画开始');
};

fadeInAnimation.onfinish = () => {
  console.log('动画完成');
  element.textContent = '动画完成!';
};

fadeInAnimation.oncancel = () => {
  console.warn('动画被取消');
};

fadeInAnimation.onremove = () => {
  console.log('动画被移除');
};

unsetunset🚀 三、高级动画技术unsetunset

1. 复合动画:序列执行

// 动画1:平移
const moveRight = element.animate(
  [
    { transform'translateX(0)' },
    { transform'translateX(200px)' }
  ],
  { duration1000 }
);

// 动画1结束后触发动画2
moveRight.onfinish = () => {
  // 动画2:旋转
  const rotate = element.animate(
    [
      { transform'translateX(200px) rotate(0deg)' },
      { transform'translateX(200px) rotate(360deg)' }
    ],
    { duration800 }
  );

  // 动画2结束后触发动画3
  rotate.onfinish = () => {
    // 动画3:返回原点
    element.animate(
      [
        { transform'translateX(200px) rotate(360deg)' },
        { transform'translateX(0) rotate(0deg)' }
      ],
      { duration1200, easing'cubic-bezier(0.68, -0.55, 0.27, 1.55)' }
    );
  };
};

2. 复合动画:并行执行

// 同时执行多个动画
const parallelAnimations = [
  element.animate(
    [{ transform'scale(1)' }, { transform'scale(1.5)' }],
    { duration1500, easing'ease-in-out' }
  ),
  element.animate(
    [{ backgroundColor'#3498db' }, { backgroundColor'#e74c3c' }],
    { duration1500 }
  ),
  element.animate(
    [{ borderRadius'0px' }, { borderRadius'50%' }],
    { duration1500 }
  )
];

// 监听所有动画完成
Promise.all(parallelAnimations.map(anim => anim.finished)).then(() => {
  console.log('所有并行动画完成');
});

3. 时间轴协调

// 创建时间轴
const timeline new DocumentTimeline();

// 动画A:移动
const moveAnim = element1.animate(
  [{ transform'translateX(0)' }, { transform'translateX(200px)' }],
  {
    duration1000,
    timeline  // 指定时间轴
  }
);

// 动画B:在动画A后立即开始(延迟1秒)
const colorChange = element2.animate(
  [{ backgroundColor'blue' }, { backgroundColor'red' }],
  {
    duration1000,
    timeline,
    delay1000  // 1秒延迟
  }
);

// 控制整个时间轴
const playTimeline = document.getElementById('playTimeline');
playTimeline.addEventListener('click', () => {
  moveAnim.play();
  colorChange.play();
});

4. 路径跟随动画

// 使用路径数据创建动画
const path = document.getElementById('motionPath');
const moveAlongPath = element.animate(
  [
    { offsetDistance0, offsetPath: `path('${path.getAttribute('d')}')` },
    { offsetDistance'100%' }
  ],
  {
    duration3000,
    iterations: Infinity,
    direction'alternate',
    easing'ease-in-out'
  }
);

unsetunset📊 四、高级应用示例unsetunset

1. 无限循环背景动画

function createBackgroundAnimation() {
  const colors = ['#ff9ff3''#feca57''#ff6b6b''#48dbfb''#1dd1a1'];
  const container = document.getElementById('bgContainer');

  colors.forEach((color, i) => {
    const dot = document.createElement('div');
    dot.className = 'bg-dot';
    dot.style.backgroundColor = color;
    container.appendChild(dot);
  
    // 对每个点创建随机动画
    dot.animate(
      [
        { 
          transform`translate(${Math.random() * 100}vw, ${Math.random() * 100}vh) scale(0)` 
        },
        { 
          transform`translate(${Math.random() * 100}vw, ${Math.random() * 100}vh) scale(1)`,
          opacity0.7
        }
      ],
      {
        durationMath.random() * 5000 + 3000,
        direction'alternate',
        iterationsInfinity,
        easing'cubic-bezier(0.445, 0.05, 0.55, 0.95)'
      }
    );
  });
}

createBackgroundAnimation();

2. 物理效果弹跳动画

const ball = document.getElementById('ball');
const ballAnimation = ball.animate(
  [
    { transform'translateY(0)', easing'ease-in' },
    { transform'translateY(300px)', easing'ease-out', offset0.3 },
    { transform'translateY(100px)', easing'ease-in', offset0.5 },
    { transform'translateY(280px)', easing'ease-out', offset0.7 },
    { transform'translateY(150px)', easing'ease-in', offset0.8 },
    { transform'translateY(300px)', easing'ease-out' }
  ],
  {
    duration2000,
    iterations: Infinity,
    composite'add'  // 与其他变换叠加
  }
);

3. 3D 变换动画

const cube = document.getElementById('cube');

// 创建3D旋转动画
cube.animate(
  [
    { 
      transform'rotateX(0deg) rotateY(0deg) rotateZ(0deg)',
      filter'blur(0px)'
    },
    { 
      transform'rotateX(180deg) rotateY(180deg) rotateZ(0deg)',
      filter'blur(2px)'
    },
    { 
      transform'rotateX(360deg) rotateY(360deg) rotateZ(180deg)',
      filter'blur(0px)'
    }
  ],
  {
    duration5000,
    iterations: Infinity,
    easing'cubic-bezier(0.215, 0.610, 0.355, 1.000)'
  }
);

// 添加透视效果
cube.parentElement.style.perspective = '1000px';

4. 滚动驱动动画

// 创建滚动驱动的时间轴
const scrollTimeline new ScrollTimeline({
  scrollSource: document.scrollingElement,
  orientation'block',
  timeRange1000
});

// 元素随着滚动变化
const scrollingElement = document.getElementById('scroll-element');
scrollingElement.animate(
  [
    { opacity0, transform'scale(0.5)' },
    { opacity1, transform'scale(1)' }
  ],
  {
    duration1000,
    timeline: scrollTimeline
  }
);

unsetunset🎯 五、性能优化技巧unsetunset

1. GPU 加速最佳实践

// 使用 transform 和 opacity 属性可触发 GPU 加速
element.animate(
  [    { transform: 'translate3d(0, 0, 0) scale(1)', opacity: 1 },    { transform: 'translate3d(100px, 100px, 0) scale(1.5)', opacity: 0.7 }  ],
  { duration: 1000 }
);

// 避免触发重排的属性
// 推荐使用:transformopacityfilter
// 尽量避免:widthheightpaddingmargin

2. 高效动画组合

// 使用 GroupEffect 进行复杂动画编排
const fadeIn new KeyframeEffect(
  element, 
  [{opacity:0}, {opacity:1}],
  {duration1000}
);

const moveRight new KeyframeEffect(
  element,
  [{transform:'translateX(0)'}, {transform:'translateX(200px)'}],
  {duration1000}
);

// 串行动画
const sequence new SequenceEffect([fadeIn, moveRight]);

// 并行动画
const parallel new GroupEffect([fadeIn, moveRight]);

// 创建动画
const sequentialAnimation new Animation(sequence, document.timeline);
const parallelAnimation new Animation(parallel, document.timeline);

unsetunset✅ 六、完整示例unsetunset

<!DOCTYPE html>
<html>
<head>
  <style>
    .box {
      width100px;
      height100px;
      backgroundlinear-gradient(135deg#3498db#9b59b6);
      border-radius8px;
      display: flex;
      justify-content: center;
      align-items: center;
      color: white;
      font-weight: bold;
      position: relative;
      will-change: transform, opacity;
      margin40px auto;
    }
  
    .ball {
      width40px;
      height40px;
      position: absolute;
      top: -60px;
      leftcalc(50% - 20px);
      background#e74c3c;
      border-radius50%;
      will-change: transform;
    }
  
    .controls {
      display: grid;
      grid-template-columnsrepeat(31fr);
      gap10px;
      max-width600px;
      margin20px auto;
    }
  
    button {
      padding12px;
      border: none;
      background#3498db;
      color: white;
      border-radius4px;
      cursor: pointer;
      transition: background 0.2s;
    }
  
    button:hover {
      background#2980b9;
    }
  
    button.pause {
      background#e67e22;
    }
  
    button.reset {
      background#e74c3c;
    }
  </style>
</head>
<body>
  <div class="box" id="animatedBox">
    Web Animations
    <div class="ball" id="ball"></div>
  </div>

  <div class="controls">
    <button id="play">播放</button>
    <button id="pause" class="pause">暂停</button>
    <button id="reverse">反向</button>
    <button id="speedUp">加速</button>
    <button id="slowDown">减速</button>
    <button id="reset" class="reset">重置</button>
    <button id="combo1">基础动效</button>
    <button id="combo2">复杂动效</button>
    <button id="combo3">弹跳小球</button>
  </div>

  <script>
    const box = document.getElementById('animatedBox');
    const ball = document.getElementById('ball');
  
    // 创建基础动画
    const boxAnimation = box.animate(
      [
        { transform'rotate(0deg)'boxShadow'0 0 0 rgba(0,0,0,0.1)' },
        { transform'rotate(360deg)'boxShadow'0 10px 20px rgba(0,0,0,0.3)' }
      ],
      {
        duration2000,
        iterationsInfinity,
        easing'ease-in-out'
      }
    );
    boxAnimation.pause();
  
    // 创建小球弹跳动画
    const ballAnimation = ball.animate(
      [
        { transform'translateY(0)' },
        { transform'translateY(100px)'offset0.3 },
        { transform'translateY(30px)'offset0.6 },
        { transform'translateY(80px)'offset0.8 },
        { transform'translateY(60px)'offset0.9 },
        { transform'translateY(70px)'offset1 }
      ],
      {
        duration1000,
        iterationsInfinity,
        direction'alternate'
      }
    );
    ballAnimation.pause();
  
    // 控制函数
    document.getElementById('play').addEventListener('click'() => {
      boxAnimation.play();
      ballAnimation.play();
    });
  
    document.getElementById('pause').addEventListener('click'() => {
      boxAnimation.pause();
      ballAnimation.pause();
    });
  
    document.getElementById('reverse').addEventListener('click'() => {
      boxAnimation.playbackRate *= -1;
      ballAnimation.playbackRate *= -1;
    });
  
    document.getElementById('speedUp').addEventListener('click'() => {
      boxAnimation.playbackRate *= 1.5;
      ballAnimation.playbackRate *= 1.5;
    });
  
    document.getElementById('slowDown').addEventListener('click'() => {
      boxAnimation.playbackRate *= 0.75;
      ballAnimation.playbackRate *= 0.75;
    });
  
    document.getElementById('reset').addEventListener('click'() => {
      boxAnimation.cancel();
      ballAnimation.cancel();
    });
  
    // 组合动画示例
    document.getElementById('combo1').addEventListener('click'() => {
      const colorAnimation = box.animate(
        [
          {background'linear-gradient(135deg, #3498db, #9b59b6)'},
          {background'linear-gradient(135deg, #9b59b6, #e74c3c)'}
        ],
        {duration1500iterations2}
      );
    });
  
    document.getElementById('combo3').addEventListener('click'() => {
      // 创建小球路径动画
      const moveBall = ball.animate(
        [
          { transform'translateX(0)' },
          { transform'translateX(150px)' },
          { transform'translateX(0)' }
        ],
        { 
          duration2000,
          easing'ease-in-out',
          iterationsInfinity
        }
      );
    });
  </script>
</body>
</html>

本指南展示了 Web Animation API 的强大功能,通过掌握这些技术,您可以创建高性能、流畅的 Web 动画体验。在线示例:[v0.kestrel-task.cn/11.html]