思路:
1.因页面分组件分的比较细,由图可知是组件5到组件4的联动. 如果利用组件间通信需要 子组件5 -->组件3-->所有组件的父组件-->组件4, 层级略显复杂,所以使用了vuex状态管理管理数据.
2.动画处理:
利用vue的 transition 标签来处理动画.
小球运动的轨迹是一条抛物线, 可使X轴做匀速运动, Y轴贝塞尔曲线, 可以考虑设定两个嵌套的DOM来控制运行轨迹.外层控制Y轴动画, 内层控制X轴动画.
因小球落点是同一个位置, 可以将小球设定到落点位置, 动态获取初始值来决定小球的运动轨迹.
3.如果连续点击小球, 则页面上可能显示多个小球, 所以需要设置多个小球保证页面上可以显示多个小球.
结构图:
html (Ball.vue) :
<div class="ball-container">
<transition v-for="(ball,index) in balls" :key="index" name="drop" @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
<div class="ball" v-show="ball.show" >
<div class="inner inner-hook"></div>
</div>
</transition>
</div>
css (Ball.vue):
.ball-container .ball position fixed left .64rem bottom .44rem z-index 200 width .32rem height .32rem border-radius 50% // background rgb(0, 160, 220) &.drop-enter-active transition: all .4s cubic-bezier(0.49, -0.29, 0.75, 0.41) .inner width .32rem height .32rem border-radius 50% background rgb(0, 160, 220) transition: all .4s linear
js (Ball.vue)
computed: { ...mapState({ balls: state => state.balls.balls, dropBall: state => state.balls.dropBall }) }, methods: { ...mapMutations(['changeShow', 'changeDropBall']), beforeEnter (el) { let count = this.balls.length while (count--) { let ball = this.balls[count] if (ball.show) { //getBoundingClientRect返回值是一个 DOMRect 对象, 此包含了一组用于描述边框的只读属性——left、top、right和bottom,单位为像素。 //除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。 let rect = ball.el.getBoundingClientRect() let x = rect.left - 32 let y = -(window.innerHeight - rect.top - 22) //小球外层控制y轴的运动轨迹,translate3d()可以开启硬件加速 el.style.display = '' el.style.webkitTransform = `translate3d(0, ${y}px, 0)` el.style.transform = `translate3d(0, ${y}px, 0)` //内层小球控制x轴运动轨迹,内外层运动方式是不一样的, y轴贝塞尔曲线, x轴匀速. let inner = el.getElementsByClassName('inner-hook')[0] inner.style.webkitTransform = `translate3d(${x}px, 0, 0)` inner.style.transform = `translate3d(${x}px, 0, 0)` } } }, enter (el) { // 触发浏览器重绘 /* 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)` }) }, afterEnter (el) { // 删除数组第一个元素, 并返回第一个元素,因对象都是指向地址,所以操作dropBall数组也就操作了balls数组 let ball = this.dropBall.shift() if (ball) { ball.show = false el.style.display = 'none' } } }
store里面的moudle (balls.js):
const balls = { state: { balls: [{ show: false }, { show: false }, { show: false }, { show: false }, { show: false }], dropBall: [] }, mutations: { changeShow (state, {index, isShow, el}) { state.balls[index].show = isShow state.balls[index].el = el }, changeDropBall (state, ball) { state.dropBall.push(ball) } } } export default balls
增加商品数量(即点击+号)触发小球动画的页面 (Cartcontrol.vue):
computed: { ...mapState({ balls: state => state.balls.balls }) }, methods: { ...mapMutations(['changeShow', 'changeDropBall']), addCart (event) { for (let i = 0; i < this.balls.length; i++) { if (!this.balls[i].show) { this.changeShow({index: i, isShow: true, el: event.target}) this.changeDropBall(this.balls[i]) return } } } }
此DOME是根据参考黄轶老师的仿饿了吗课程改编而来~