在写Web端商城项目的时候, 我们经常会写到购物车
当点击加入购物车的时候, 不止是数据的加减, 我们还想做一个动画效果, 这样的交互效果, 客户体验会好一点
我们开始实现这种效果吧
我们先来画个页面
页面画好之后, 我们创建两个变量, 一个是队列数组, 一个是状态, 用于停止执行
data() { return { animationQueue: [], isAnimating: false, }; },
现在, 我们写点击里面的逻辑, 点击之后, 我们传入一个索引字段, 用于后面获取点击按钮的位置, 将当前要执行的索引传入队列中, 如果isAnimating为false, 我们就执行动画
handleClick(index) { this.animationQueue.push(index); if (!this.isAnimating) { this.$nextTick(this.runAnimation(index)); } },
执行动画中, 我们先来做一个判断, 如果执行队列没有数据, 我们就不再往下执行
if (this.animationQueue.length === 0) { this.isAnimating = false; return; }
如果有数据, 我们需要变更状态为true
this.isAnimating = true;
随后获取图片,按钮和购物车icon的大小及其位置, 这里我们使用getBoundingClientRect来获取大小位置
https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect
getBoundingClientRectApi
let productImage = null; const targetObj = this.$refs.targetObj.getBoundingClientRect(); const targetX = targetObj.left + targetObj.width / 2; const targetY = targetObj.top + targetObj.height / 2; let productRect = null; if (index === 1) { productImage = this.$refs.productImage; productRect = this.$refs.btnOne.getBoundingClientRect(); } const startX = productRect.left + productRect.width / 2; const startY = productRect.top + productRect.height / 2;
随后, 克隆图片的节点, 并追加样式, 使其定位在按钮附近
const clone = productImage.cloneNode(true); Object.assign(clone.style, { position: "fixed", left: `${startX - 40}px`, top: `${startY}px`, width: `${65}px`, height: `${65}px`, pointerEvents: "none", zIndex: 9999, transform: "translate(0, 0) scale(0.3)", transition: "none", }); document.body.appendChild(clone);
随后, 我们创建几个变量, 分别是动画的总时长, 开始时间, 开始坐标到结束坐标的直线距离, 抛物线最大高度(避免抛物线过高)
const duration = 800; const startTime = Date.now(); const distance = Math.sqrt( Math.pow(targetX - startX, 2) + Math.pow(targetY - startY, 2) ); const parabolaHeight = Math.min(200, distance / 2);
我们使用浏览器原生动画API, 传入一个回调函数, 让浏览器下次重绘之前执行我们提供的函数
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame
requestAnimationFrame
request AnimationFrame(animate); const animate = () => {}
现在, 我们写这个回调函数, 在回调函数里面, 动画未结束时, 我们计算了x,y坐标, y坐标计算使其先上升后下降, 不断地递归计算进行平移, 当动画结束时, 我们移除元素, 将状态初始化即可
const animate = () => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); if (progress < 1) { const x = startX + (targetX - startX) * progress - 30; const y = startY + (targetY - startY) * progress - parabolaHeight * (4 * progress * (1 - progress)) - 30; clone.style.transform = `translate(${x - startX}px, ${ y - startY }px) scale(${1 - progress * 0.2})`; clone.style.opacity = 1; requestAnimationFrame(animate); } else { clone.remove(); this.isAnimating = false; } };
这样, 我们的一个小功能就做好了, 看下效果
今天我们就讲解到这里