效果
动画
根据 vuetelemetry 官网轮播图的效果复现,在大致有几个需求:
- 点击非中央轮播图,图片滑动。
- 点击中央轮播图触发该轮播图事件(比如弹窗)。
- 图片滑动有惯性(先慢后快),且背景(上一张轮播图)有缩放变大效果。
实现
gsap 介绍
先简单介绍一下 gsap 的动画函数(官方文档):
// 将一个或多个元素 targets 在指定时间 second 内按一定的速率函数变化至某个指定的 options 状态
gsap.to(targets, second, options)
-
targets
:元素节点或者选择器字符串(如.className
) -
second
:动画执行秒数 -
options
:动画末状态的配置对象(所有可用选项详见 CSSPlugin )
例子:
gsap.to(".box", 1, {rotation: 27, x: 100});
↑ 将所有含 .box
类的元素在 1
秒内变化至 transform: translate(100px, 0px) rotate(27deg);
的状态。
核心动画逻辑
在一个含图片 img
的 div
中,靠后的节点会优先显示在上层,也就是我们只需拷贝一个新的包含图片的 div
到原 div
同级即可。
// 在视图中后面,在 node 中前面的节点
const beforeNode = reverse ? slideLeft : slideRight;
// 在视图中前面,在 node 中后面的节点
const afterNode = reverse ? slideRight : slideLeft;
const afterNodeCopy = afterNode.cloneNode(true);
afterNodeCopy.style.transform = reverse
? "translateX(100%)"
: "translateX(-100%)";
beforeNode.parentNode.appendChild(afterNodeCopy);
gsap.to(afterNodeCopy, 1, {
xPercent: reverse ? -100 : 100,
ease: "expo.inOut",
onStart: function() {
_this.noDrag();
gsap.to(beforeNode, 1, {
scale: 1.15,
ease: "expo.inOut",
});
},
onComplete: function() {
beforeNode.parentNode.removeChild(beforeNode);
afterNodeCopy.style.transform = "";
},
});
下面详解这段代码:
// 在视图中后面,在 node 中前面的节点
const beforeNode = reverse ? slideLeft : slideRight;
// 在视图中前面,在 node 中后面的节点
const afterNode = reverse ? slideRight : slideLeft;
const afterNodeCopy = afterNode.cloneNode(true);
↑ 我们事先根据正逆向 reverse
做不同的分辨,从而选择好需要在前和在后的包含 img
的 div
节点,并做拷贝。
afterNodeCopy.style.transform = reverse
? "translateX(100%)"
: "translateX(-100%)";
beforeNode.parentNode.appendChild(afterNodeCopy);
↑ 对于在后的节点(也就是显示在上层),做水平 100%
的平移,并添加入目标 div
同级位置。
gsap.to(afterNodeCopy, 1, {
xPercent: reverse ? -100 : 100,
ease: "expo.inOut",
onStart: function() {
_this.noDrag();
gsap.to(beforeNode, 1, {
scale: 1.15,
ease: "expo.inOut",
});
},
onComplete: function() {
beforeNode.parentNode.removeChild(beforeNode);
afterNodeCopy.style.transform = "";
},
});
执行动画函数,在 1
秒内从原位置以 ease: "expo.inOut"
的动画速率函数,过渡到水平平移 xPercent: 100
的末状态,在启动函数时对背景(此处的前节点)做同速率函数的缩放。
动画完成时,清除前置节点(显示在后层的原节点),并清除新进入的后节点上面的样式。
全局锁
每个动画函数执行时是异步的,为了保证所有节点都完成,需要建立 Promise 进行统一管理:
// 动画执行核心函数
animation(slideLeft, slideRight, reverse) {
return new Promise((resolve, reject) => {
gsap.to(afterNodeCopy, 1, {
......
},
onComplete: function() {
......
resolve();
},
});
}
}
最后使用 Promise.all()
统一管理全局锁。
取消图片可拖动
noDrag() {
document.getElementsByTagName("img").forEach((item) => {
item.onmousedown = (e) => {
e.preventDefault();
};
});
}
原生 js 禁止所有 img
的拖动,并在页面加载后和动画开始执行时各执行一次(此时新节点被加入,要取消该节点可拖动)。
总结
完整代码较多,已经封装为组件,详见: