补间动画

191 阅读1分钟

偶然看见一篇文章讲了动画,感觉挺有意思,自己做个记录

如果一般做动画都是 利用 命令式

 const el = xxx
 el.style.transform.translateX = "100px"
 el.style.transformDuation = '500ms'
 xxxx

类似于这种,但是这种 命令式动画现在 有了其他的替代品----> MDN

这种利用声明式 进行动画管理 官方例子:


var nommingCake = document.getElementById('eat-me_sprite').animate(
[
  { transform: 'translateY(0)' },
  { transform: 'translateY(-80%)' }
], {
  fill: 'forwards',
  easing: 'steps(4, end)',
  duration: aliceChange.effect.timing.duration / 2
});

// 暂停蛋糕的动画,以避免动画立即播放。
nommingCake.pause();

// 该函数会在用户点击时触发
var growAlice = function() {

  // Play the cake's animation.
  nommingCake.play();

}

重点在于 .animate,可以利用这个 API,只需要填入 开始值 和 终点值,动画会根据 options 选项进行自动 渲染 大大提高了代码可读性,可移植性

el.animate(
[
  { transform: 'translateY(0)' },
  { transform: 'translateY(-80%)' }
], {
  fill: 'forwards',
  easing: 'steps(4, end)',
  duration: aliceChange.effect.timing.duration / 2
})

可以利用这个做一些动画

FLIP  意思是  First, Last, Invert, Play.

// Get the first position.
var first = el.getBoundingClientRect();

// Move it to the end.
el.classList.add('totes-at-the-end');

// Get the last position.
var last = el.getBoundingClientRect();

// Invert.
var invert = first.top - last.top;

// Go from the inverted position to last.
var player = el.animate([
  { transform: `translateY(${invert}px)` },
  { transform: 'translateY(0)' }
], {
  duration: 300,
  easing: 'cubic-bezier(0,0,0.32,1)',
});

// Do any tidy up at the end
// of the animation.
player.addEventListener('finish',
    function(){});

做一个 Vue 的动画

image.png 类似于这种效果

核心代码

   // 原理 DOM 状态(位置信息)改变了,而浏览器还没渲染。
   // Vue 内部会把本次 DOM 更新的渲染函数先放到 microTask队列中,此时的队列是[changeDOM]。
   // 调用了 nextTick(callback) 后,这个callback函数也会被追加到队列中,此时的队列是 [changeDOM, callback]。

// 主要是利用 开始获取元素位置, 在update函数里 对数据做一些操作
// 这个时候并不能获取最新的 DOM,在 this.nextTick 获取最新DOM,然后与 最开始的 DOM做一些运算
          scheduleAnimation(update) {
            // 获取旧图片的位置
            const prevImgs = this.$refs.imgs.slice();

            const prevSrcRectMap = createSrcRectMap(prevImgs);
            // 类似于这种数据格式
            // {
            //   https://pic1.zhimg.com/v2-8d8_r.jpg: {left: 1084.5, top: 54, img: img.img}
            // }

            // 更新数据
            update();

            // DOM更新后
            this.$nextTick(() => {
              // 一直操作的是原来存在的 图片,对于新加的 图片只是简单的 unshift 到后面
              const currentSrcRectMap = createSrcRectMap(prevImgs);
              /*
                  元素没变 key值没变, left 值变了,已经是最新的 DOM了
             https://pic1.zhimg.com/v2-8d_r.jpg: {left: 959.5, top: 274, img: img}
             注意: 这个地方有 个 img: img
              **/

              Object.keys(prevSrcRectMap).forEach((src) => {
                const currentRect = currentSrcRectMap[src];

                const prevRect = prevSrcRectMap[src];

                /*
              尺寸已经变化 img: img.img
                left: 709.5
                top: 54
              */
                const invert = {
                  left: prevRect.left - currentRect.left,
                  top: prevRect.top - currentRect.top,
                };

                const keyframes = [
                  {
                    transform: `translate(${invert.left}px, ${invert.top}px)`,
                  },
                  { transform: "translate(0, 0)" },
                ];
                const options = {
                  duration: 300,
                  easing: "cubic-bezier(0,0,0.32,1)",
                };

                const animation = currentRect.img.animate(keyframes, options);
              });
            });
          },

打乱图片功能

shuffle() {
     this.scheduleAnimation(() => {
         this.imgs = shuffle(this.imgs);
      });
 }

新增图片

add(){
this.scheduleAnimation(() => {
     this.imgs = newData.concat(this.imgs);
   })
}

生成Map 对象

 function createSrcRectMap(imgs) {
        return imgs.reduce((prev, img) => {
          const rect = img.getBoundingClientRect();
          const { left, top } = rect;
          prev[img.src] = { left, top, img };
          return prev;
        }, {});
  }

/note:git 地址 github.com/tangtts/ani… 2022.6.9/