效果


动画

根据 vuetelemetry 官网轮播图的效果复现,在大致有几个需求:

  1. 点击非中央轮播图,图片滑动。
  2. 点击中央轮播图触发该轮播图事件(比如弹窗)。
  3. 图片滑动有惯性(先慢后快),且背景(上一张轮播图)有缩放变大效果。

实现

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); 的状态。

核心动画逻辑

在一个含图片 imgdiv 中,靠后的节点会优先显示在上层,也就是我们只需拷贝一个新的包含图片的 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 做不同的分辨,从而选择好需要在前和在后的包含 imgdiv 节点,并做拷贝。

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 的拖动,并在页面加载后和动画开始执行时各执行一次(此时新节点被加入,要取消该节点可拖动)。

总结

完整代码较多,已经封装为组件,详见:

fz6m / vue-gsap-slider-wapper