偶然看见一篇文章讲了动画,感觉挺有意思,自己做个记录
如果一般做动画都是 利用 命令式
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(){});
类似于这种效果
核心代码
// 原理 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/