根据 vuetelemetry 官网轮播图的效果复现,在大致有几个需求:
点击非中央轮播图,图片滑动。
点击中央轮播图触发该轮播图事件(比如弹窗)。
图片滑动有惯性(先慢后快),且背景(上一张轮播图)有缩放变大效果。
实现gsap 介绍先简单介绍一下 gsap 的动画函数(官方文档):
// 将一个或多个元素 targets 在指定时间 second 内按一定的速率函数变化至某个指定的 options 状态 (targets, second, options)
例子:
(".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); (afterNodeCopy, 1, { xPercent: reverse ? -100 : 100, ease: "expo.inOut", onStart: function() { _this.noDrag(); (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 同级位置。
(afterNodeCopy, 1, { xPercent: reverse ? -100 : 100, ease: "expo.inOut", onStart: function() { _this.noDrag(); (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) => { (afterNodeCopy, 1, { ...... }, onComplete: function() { ...... resolve(); }, }); } }
最后使用 Promise.all() 统一管理全局锁。
取消图片可拖动noDrag() { document.getElementsByTagName("img").forEach((item) => { item.onmousedown = (e) => { e.preventDefault(); }; }); }
原生 js 禁止所有 img 的拖动,并在页面加载后和动画开始执行时各执行一次(此时新节点被加入,要取消该节点可拖动)。
总结完整代码较多,已经封装为组件,详见:
fz6m / vue-gsap-slider-wapper