基于vue2.0实现购物车小球飞入的动画

本文详细介绍如何在Vue项目中实现购物车商品添加动画效果,通过监听购物控制组件的'+'号位置,使小球模型沿抛物线轨迹飞入购物车图标,涉及事件监听、动画控制及父子组件通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

菜鸡第一次写博客,看懂了的同学记得点个小星星哦

这篇文章是我在慕课网上看vue仿造饿了么外卖APP的项目教程中购物车小球飞入动画所得出的总结,老师是采用vue1.0开发,所以有很多的属性和接口在vue2.0版本以上已经舍弃,所以照着老师做也不一定能跑出来,一直报错。

废话不多说,先献上效果图:
在这里插入图片描述
那么,首先我们先捋顺一下思路
1.必须实时监听购物控制组件cartcontrol “+” 号相对于视口屏幕的位置
2.在动画开始之前将小球模型移动到 “+” 号的位置,动画执行时掉落到购物车logo处并消失
3.小球是做抛物线运动,我们可以定义两层盒子,其中外层盒子做y轴运动,内层盒子做x轴运动

具体实现:
1.首先要从cartcontrol中把 “+” 号这个DOM对象传递给父组件goods,再从父组件中传递给购物车组件shopcart

this.$emit
this.$refs
cartcontrol子组件
goods父组件
cartshop子组件

在vue2.0中
可以通过this.$emit来向父组件触发一个自定义事件,同时可将参数传递给父组件。

可以通过设置ref,同时通过this.$refs与子组件取得通信

cartcontrol–html

	<template>
	    <div class="cartcontrol">
	        <transition name="fade">
	            <div class="cart-decrease icon-remove_circle_outline" @click="decreaseCart" v-show="food.count>0"></div>
	        </transition>
	        <div class="cart-count" v-show="food.count>0">{{food.count}}</div>
	        <div class="cart-add icon-add_circle" @click="addCart"></div>
	    </div>
	</template>

cartcontrol–js

	 methods: {
            addCart(event) {
                // 检测事件派发是否来自于better-scroll,
                // 不是则return掉,避免PC端点击的时候触发两次点击事件
                if (!event._constructed) {
                    return
                }
                // 当给一个观测对象添加一个它不存在的属性时,直接赋值是不可以的
                // 需要使用Vue.set设置这个属性
                if (!this.food.count) {
                    Vue.set(this.food, 'count', 1)
                } else {
                    this.food.count++
                }
                // 向父组件触发一个自定义的cart-add事件,并将事件对象传递给父组件
                this.$emit('cart-add', event.target)
            }

goods–html

<div class="cartcontrol-wrapper">
 	<!--在父组件监听到子组件触发的cart-add事件-->
    <cartcontrol :food="food" @cart-add="handlecartAdd"></cartcontrol>
</div>

<!-- 记得在shopcart组件中添加ref属性-->
<shopcar ref="shopcar" :selectFoods="selectFoods" :deliveryPrice="seller.deliveryPrice" :minPrice="seller.minPrice"></shopcar>

goods–js

methods: {
	// 在goods.vue定义_drop方法将cartcontrol传递过来的target对象再传递给shopcar
	_drop(target) { 
		// 使用nextTick优化动画体验
		this.$nextTick(() => {
		  // 通过$ref属性访问shopcar子组件的drop方法
		  this.$refs.shopcar.drop(target)
		})
	},
	// 点击加号按钮触发事件
	handlecartAdd(target) {
		this._drop(target) // 调用_drop方法
	}
}

shopcart-html(新增小球模型盒)

<div class="ball-container">
       <div v-for="(ball,index) in balls" :key="index">
           <transition @before-enter="handleBeforeEnter"
                       @enter="handleEnter"
                       @after-enter="handleAfterEnter"
                       name="drop">
               <div class="ball" v-show="ball.show"> <!--外层盒子-->
                   <div class="inner inner-hook"></div> <!--内层盒子-->
               </div> 
           </transition>
       </div>
 </div>

shopcart-js

data() {
   return {
      // 使用balls存放5个小球,这些小球的默认状态都是不显示
      // 为什么是5个 不是3个 4个
      // 这里的意思是在一瞬间可以点多少次'+'号,5个差不多就够了,3个太少了,作为多年的程序员来说瞬间点3下轻而易举
       balls: [{show: false}, {show: false}, {show: false}, {show: false}, {show: false}],
       dropBalls: [] // 存放掉落的小球
   }
},
methods: {
	 // 当触发drop方法时小球开始掉落
          drop(el) {
              console.log(this.dropBalls)
              for (let i = 0; i < this.balls.length; i++) {
                  let ball = this.balls[i]
                  if (!ball.show) { // 当小球显示状态为隐藏时
                    ball.show = true
                    ball.el = el // 将cartControl传过来的对象挂载到ball的el属性上
                    this.dropBalls.push(ball)
                    return
                  }
              }  
          },
          // beforeEnter在动画运行之前把小球移到到 ‘+’ 号位置,
          // 从左下角移动到右上,所以x是正数,y是负数
          handleBeforeEnter: function(el) {
              let count = this.balls.length
              while (count--) {
                  let ball = this.balls[count]
                  if (ball.show) {
                      //  getBoundingClientRect()获取小球相对于视窗的位置,屏幕左上角坐标为0,0
                      let rect = ball.el.getBoundingClientRect() 
                      // 小球x方向位移= 小球距离屏幕左侧的距离-外层盒子距离水平的距离
                      let x = rect.left - 32
                      // 负数,因为是从左上角向下
                      let y = -(window.innerHeight - rect.top - 22)
                      el.style.display = ''
                      el.style.webkitTransform = `translate3d(0,${y}px,0)`
                      el.style.transform = `translate3d(0,${y}px,0)`
                      // 获取内层盒子
                      let inner = el.getElementsByClassName('inner-hook')[0]
                      // 设置内层盒子,即小球水平方向的距离
                      inner.style.webkitTransform = `translate3d(${x}px,0,0)` 
                      inner.style.transform = `translate3d(${x}px,0,0)`
                  }
              }
          },
          // enter
          handleEnter: function(el, done) {
              /* eslint-disable no-unused-vars */
              // 触发浏览器重绘
              let rf = el.offsetHeight
              this.$nextTick(() => {
                  el.style.webkitTransform = 'translate3d(0, 0, 0)'
                  el.style.transform = 'translate3d(0, 0, 0)'
                  let inner = el.getElementsByClassName('inner-hook')[0]
                  inner.style.webkitTransform = 'translate3d(0, 0, 0)'
                  inner.style.transform = 'translate3d(0, 0, 0)'
                  // Vue为了知道过渡的完成,必须设置相应的事件监听器。
                  // 如果没有这一句那将不会执行handleAfterEnter
                  el.addEventListener('transitionend', done) 
              })
          },
          // AfterEnter
          handleAfterEnter: function(el) {
              // 完成一次动画就删除一个dropBalls的小球
              let ball = this.dropBalls.shift()
              if (ball) {
                  ball.show = false
                  //如果没有这一句,小球到达终点后过一小段时间后才消失
                  //具体原因也是搞不清楚,上面也已经false掉了
                  el.style.display = 'none'
              }
          }
}

shopcart–stylus

.ball-container
     .ball
       position: fixed 
       left: 32px
       bottom: 22px
       z-index: 999
       &.drop-enter-active, &.drop-leave-active
       /*贝塞尔曲线,可以上官网设置https://cubic-bezier.com/*/
         transition: all .8s cubic-bezier(0.49,-0.49,0.75,0.41)
         .inner
           width: 16px
           height: 16px
           border-radius: 50%
           background: rgb(0,160,220)
           transition: all .8s
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值