饿了么慕课网学习手记(三)加入购物车动画

1.加入购物车按钮组件

将加入购物车按钮部分抽离成一个组件,因为会多次复用到,即:
在这里插入图片描述

//carControl.vue
<template>
<div class="car-control">
	<!-- 动画名称为move -->
	<transition name="move">
		<!-- 数量大于0时(即count属性存在时),减号出现,点击减号触发decreaseCar函数-->
		<div class="car-decrease" v-show="food.count > 0" @click="decreaseCar">
			<span class="icon inner">&#xe600;</span>
		</div>
	</transition>
	<div class="car-count" v-show="food.count > 0">{{food.count}}</div>
	<!-- 点击加号时触发addCar函数 -->
	<div class="car-add" @click="addCar">
		<span class="icon">&#xe728;</span>
	</div>
</div>
</template>

<script>
import Vue from 'vue'

export default {
	props: {
		food: Object,
	},
	methods: {
		addCar (event) {
			if(!this.food.count) {//count不存在时将count设置为1
				Vue.set(this.food, 'count', 1);
			}else{
				this.food.count++;
			}
			// 子组件向父组件传参,event.target是加号的dom
			this.$emit('add', event.target);
		},
		decreaseCar () {
			this.food.count--;
		}
	}
}	
</script>

<style lang="less" scoped>
.car-control {
	.car-decrease {
		display: inline-block;
		padding: 6px;
		// 所有属性都将获得过渡效果,时间为0.4s,直线
		transition: all 0.4s linear;
		// 进入前和离开后的状态(平移)
		transform: translate3d(0, 0, 0);
		opacity: 1;
		font-size: 0;
		// 进入前和离开后的状态(旋转)
		.inner {
			text-align: center;
			font-size: 24px;
			line-height: 24px;
			color: rgb(0, 160, 220);
			transform: rotate(0deg);
		}
		// 在进入后和离开前的状态(平移)
		&.move-enter, &.move-leave-active {
			opacity: 0;
			transform: translate3d(24px, 0, 0);
			// 在进入后和离开前的状态(旋转)
			.inner {
				transform: rotate(180deg);
			}
		}
	}
	.car-count {
		display: inline-block;
		vertical-align: top;
		width: 12px;
		padding-top: 6px;
		line-height: 24px;
		text-align: center;
		font-size: 10px;
		color: rgb(147, 153, 159);
	}
	.car-add {
		display: inline-block;
		padding: 6px;
		font-size: 24px;
		line-height: 24px;
		color: rgb(0, 160, 220);
	}
}
</style>

2.购物车小球动画实现

点击加号时会在该处弹出一个小球,然后抛物线落入购物车中
原代码的思想是正序将小球的show依次变为true,然后倒序遍历,取到第一个为true的小球然后开始动画,这就存在一个bug,当动画的时间变长,五个小球全用完时,第一个小球落地变为false,再次点击时取到的是最后一个小球;现该bug已经改正

//shopCar.vue
<template>
<div>
	<div class="shop-car">
		<!-- 小球动画 -->
		<div class="ball-container" v-for="(ball, index) in balls" :key="index">
			<!-- 过度钩子函数 -->
			<transition
				@before-enter="beforeDrop"
				@enter="dropping"
				@after-enter="afterDrop">
				<div class="ball" v-show="ball.show">
					<div class="inner inner-hook"></div>
				</div>
			</transition>
		</div>
    </div>
</div>

</template>

<script>
import CarControl from '@/components/carControl/carControl'

export default {
	props: {
		deliveryPrice: {
			type: Number,
			default: 0,
		},
		minPrice: {
			type: Number,
			default: 0
		},
		selectFoods: {
			type: Array,
			default () {
				return [];
			}
		}
	},
	data () {
		return {
			// 创建5个小球用于动画,即当动画未完成时调用其他小球完成动画
			balls: [
				{
					show: false,
					num: 1,
				},
				{
					show: false,
					num: 2,
				},
				{
					show: false,
					num: 3,
				},
				{
					show: false,
					num: 4,
				},
				{
					show: false,
					num: 5,
				}
			],
			dropBalls: [],//存储下落的小球
			count: '',//判定是第几个小球
		}
	},
	methods: {
		// 遍历小球,找到一个show属性为false的小球,将其的show设置成true并存入已下落得小球数组里
		drop(el) {
			// console.log(el)
			for(let i = 0; i < this.balls.length ; i ++) {
				let ball = this.balls[i];
				if(!ball.show) {
					ball.show = true;
					ball.el = el;

					this.dropBalls.push(ball);
					this.count = i;
					return;
				}
			}
		},
		// 动画开始时小球的状态
		beforeDrop(el) {
			let ball = this.balls[this.count];
			// 元素相对于视口的距离
			let rect = ball.el.getBoundingClientRect();
			console.log(rect);
			// x,y为初始点与目标点的差值
			let x = rect.left - 32;
			let y = -(window.innerHeight -rect.top -24);
			// el.style.display = '';

			// el (初始位置为 0,0,0)和购物车icon在一起,将小球(el)  放到加号位置去
			//纵向动画
			el.style.webkitTransform = `translate3d(0, ${y}px, 0)`;
			el.style.transform = `translate3d(0, ${y}px, 0)`;
			//横向动画  inner-hook,  仅仅定义类 dom选择器,不做样式
			let inner = el.getElementsByClassName('inner-hook')[0];
			inner.style.webkitTransform = `translate3d(${x}px, 0, 0)`;
			inner.style.transform = `translate3d(${x}px, 0, 0)`;
		},
		// 动画完成时小球的状态
		dropping (el, done) {
			// 手动触发浏览器重绘,便于translate3d,--rf变量不会使用
			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];
				// 小球dom
				inner.style.webkitTransform = 'translate3d(0,0,0)';
				inner.style.transform = 'translate3d(0,0px,0)';
				// done();//进入动画后立即触发
				el.addEventListener("transitionend",done);//完成动画后触发
				
			})
		},
		// 动画完成后的小球状态
		afterDrop (el) {
			// 此轮动画结束后,将此次的 ball 取出 ,ball状态重置,,el display:none
			console.log(this.dropBalls);
			let ball = this.dropBalls.shift();
			if(ball) {
				ball.show = false;
				el.style.display = 'none';
			}
		},
	}
}
</script>

<style lang="less" scoped>
	//只显示相关的一部分
	.ball-container {
		.ball {
			position: fixed;
			left: 32px;
			bottom: 24px;
			z-index: 100;
			//y 轴 贝塞尔曲线
			transition: all 5s cubic-bezier(0.49, -0.29, 0.75, 0.41);
			.inner {
				width: 16px;
				height: 16px;
				border-radius: 50%;
				background-color: rgb(0, 160, 220);
				transition: all 5s linear;
			}
		}
	}
</style>

3.在父组件goods.vue中调用

//展示需要的一部分代码
<template>
<div class="goods">
	<div class="menu-wrapper" ref="menuWrapper">
		<ul>
			<li class="menu-item vux-1px-b" v-for="(item, index) in goods" :class="{'current' : currentIndex == index}"
				@click="selectGood(index, $event)">
				<span class="text">
					<span class="icon" :class="classMap[item.type]" v-show="item.type >= 0"></span>{{item.name}}
				</span>
			</li>
		</ul>
	</div>
	<div class="foods-wrapper" ref="foodsWrapper">
		<ul>
			<li class="food-list" ref="foodList" v-for="(item, index) in goods">
				<div class="title">{{item.name}}</div>
				<ul>
					<li class="food-item " :class='{"vux-1px-b":index!=item.foods.length-1}' v-for="(food,index) in item.foods" :key='index'>
						<div class="icon" @click="seeFood(food, $event)">
							<img width="57" height="57" :src="food.icon">
						</div>
						<div class="content">
							<div class="name">{{food.name}}</div>
							<div class="desc">{{food.description}}</div>
							<div class="extra">
								<span>月售{{food.sellCount}}份</span>
								<span>好评率{{food.rating}}%</span>
							</div>
							<div class="price">
								<span>¥{{food.price}}</span>
								<span class="old-price" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
							</div>
							<div class="car-control-wrapper">
								<CarControl :food="food" @add="addFood"></CarControl>
							</div>
						</div>
					</li>
				</ul>
			</li>
		</ul>
	</div>
	<ShopCar ref="shopCar" :deliveryPrice="seller.deliveryPrice" :minPrice="seller.minPrice" :selectFoods="selectFoods"></ShopCar>
	<Food @add="addFood" :food="seeFoodInfo" ref="food"></Food>	
</div>
</template>

<script>
import BScroll from 'better-scroll'
import ShopCar from '@/components/goods/shopCar'
import CarControl from '@/components/carControl/carControl'
import Food from '@/components/food/food'

export default {
	props: {
		seller: Object,
	},
	components: {
		ShopCar,
		CarControl,
		Food
	},
	data () {
		return {
			goods: [],
			listHeight: [],
			scrollY: 0,
			seeFoodInfo: {},
		}
	},
	created () {
		//获取数据
		this.$http.get('./api/goods').then((res) => {
			this.goods = res.data.data;
			this.$nextTick(() => {
				this._initScroll();
				this._calculateHeight();
			})
		});

		this.classMap = ['decrease','discount','special','invoice','guarantee'];
	},
	methods: {
		/*
		CarControl子组件和Food子组件激发的事件
		 */
		addFood (target) {
			this._drop(target);
		},
		/*
		_drop方法,传入点击的dom对象
		 */
		_drop (target) {
			//调用 shopcar 组件中的 drop 方法,向其传入当前点击的 dom 对象
//    		this.$refs.shopcart.drop(target);
			// 体验优化,异步执行下落动画
			this.$nextTick(() => {
				this.$refs.shopCar.drop(target);
			})
		},
	},
	computed: {
		//通过遍历,选出所有选中的商品
		selectFoods () {
			let foods = [];
			this.goods.forEach((good) => {
				good.foods.forEach((food) => {
					if(food.count) {
						foods.push(food)
					}
				})
			})
			return foods;
		}
	},
}
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值