滚动小球的实现,只需要考虑enter效果即可,用emit和on派发和监听事件,cartcontrol.vue派发事件{this.$emit('cart-add', event.target)};,goods.vue监听事件cart-add,并添加监听函数cartAdd<cartcontrol v-on:cart-add="cartAdd" :food="food"></cartcontrol>,在监听函数cartAdd中拿到shopcart的DOM对象 this.$refs.shopcart.drop(target);,返回shopcart.vue编写drop()获取下落小球的初始位置,并实现beforeEnter,enter和afterEnter方法,设置动画

1)在shopcart中定义小球,ball-container中存放小球,小球的状态存放在一个数组中,初始状态都是隐藏的

 data() {
            return {
                balls: [{ //每一个成员都用来维护当前小球的状态,初始状态都是隐藏的
                    show: false
                },
                { 
                    show: false
                },
                {  
                    show: false
                },
                {  
                    show: false
                },
                {  
                    show: false
                }],
                //添加一个变量,用来存贮已经下落的小球
                dropBalls: [],
                fold: true //购物车详情列表默认折叠
            };
        }
 <div class="ball-container">
                <div v-for="ball in balls" :key="ball.id">
                        <transition name="drop" @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
                            <div v-show="ball.show" class="ball">
                                <div class="inner inner-hook">
                                </div>
                            </div>
                        </transition>
                </div> 
            </div>

2)为小球添加样式,ball位于购物车图片的上方.ball position:fix z-index:200并设置缓动函数: transition 

  设置inner,inner代表内层的一个小球,并设置缓动函数

3)此处没有设置enter和leave的动画,是因为要动态计算cartcontrol中加号的位置(即起始点宽高的位置,left或者是top的值),首先要拿到cartcontrol中加号这个元素。因为在cartcontrol点击加号会出发事件,我们可以在cartcontral中添加一个派发事件,将它的DOM对象传出来,即在点击事件中添加分发事件addCart(event) {this.$emit('cart-add', event.target)};

 methods: {
        addCart(event) { 
            //解决PC端双点击的问题
            if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
                return;
            }
            //console.log('click');//点击不生效,不要忘了在foodScroll中添加click: true 
            if (!this.food.count) {
                 //food.count是原json中不存在的属性,不能直接添加
                //this.food.count = 1;
                Vue.set(this.food, 'count', 1);//给this.food增加一个count属性,并初始化为1
            } else {
                this.food.count++;
            }
//设置滚动对象时,点击加号,设置一个派发事件,将DOM对象传出去,将target(DOM)作为cart.add事件的对象传入
//$emit, $on, $off 分别来分发、监听、取消监听事件:
            this.$emit('cart-add', event.target);
        },
        decreaseCart(event) {
            //解决PC端双点击的问题
            if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
                return;
            }
            if (this.food.count) {
                this.food.count--;
            }
        }
    }
};

4)之后在父组件goods中对cart-add进行监听并添加处理事件cartAdd(传入target),<cartcontrol v-on:cart-add="cartAdd" :food="food"></cartcontrol>,cartAdd函数添加在methods中

 <div class="cartcontrol-wrapper">
                <cartcontrol v-on:cart-add="cartAdd" :food="food"></cartcontrol> <!-- 传入food!!!-->
               </div>
 cartAdd(target) {
     //拿到traget(DOM对象)之后,将其传入shopcart组件中drop(target){}方法,
     //此处用this.$refs调用子组件,访问DOM时用的是ref="menuWrapper"
    this.$nextTick(() => { //回调函数异步执行,两个动画效果就不会卡顿了
      this.$refs.shopcart.drop(target);
    });
   }

goods父组件访问子组件shopcart的方法,不要忘了添加ref

<shopcart ref="shopcart" :select-foods="selectFoods" :delivery-price="seller.deliveryPrice" :min-price="seller.minPrice"></shopcart>

5)此处回到shopcart.vue编写drop(el){}方法,drop方法对应name="drop"的transition,并实现1)中的beforeEnter,enter和afterEnter方法,并在css中添加transition

        data() {
            return {
                //添加一个变量,用来存贮已经下落的小球
                dropBalls: [],
                fold: true //购物车详情列表默认折叠
            };
        }
 methods: {
            drop(el) {
                //console.log(el); //验证是否能正确输出
                //遍历balls,拿到第一个show为false的球,做一个动画
                for (let i = 0; i < this.balls.length; i++) {
                    let ball = this.balls[i];
                    if (!ball.show) { //show为false的球
                        ball.show = true; //小球下落
                        ball.el = el;//保留当前的DOM对象,用来计算位置
                        this.dropBalls.push(ball); //dropBalls存的是已经下落的小球,后续要对已经下落的小球进行处理
                        return;
                    }
                }
            },
       //定义三个钩子函数实现动画
            beforeEnter(el) { //el为当前执行transition动画的DOM对象
            //先找到所有为true的小球(连续点击的情况)
                let count = this.balls.length;
                while (count--) {
                    let ball = this.balls[count];
                    if (ball.show) { //这个是要运动的小球true
                        let rect = ball.el.getBoundingClientRect();//获得元素相当于视口的位置
                        let x = rect.left - 32;
                        let y = -(window.innerHeight - rect.top - 22);
                        el.style.display = ''; //v-show默认display:none,设置为空,让它显示
                        //外层元素是纵向的动画,内层元素是横向的动画
                        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(el) {
                /* 触发浏览器重绘,重绘之后才可以设置transform*/
                /* 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) { //动画完成
                let ball = this.dropBalls.shift();//删除并返回第一个ball
                if (ball) {
                    ball.show = false; //重置ball.show的状态
                    el.style.display = 'none';
                }
            },
   }

CSS:在运动的时候拉一个贝塞尔曲线

 .ball-container
            .ball
                position fixed //相对于视口做布局
                left 32px
                bottom 22px
                z-index 200
                transition: all 0.6s cubic-bezier(0.49, -0.29, 0.75, 0.41)
                .inner
                    width 16px
                    height 16px
                    border-radius 50%
                    background rgb(0,160,220)
                    transition all 0.4s linear  //x轴做一个线性的过渡即可

在购物车中有商品时,点击购物车会出现购物车详情,会有一个购物车列表,商品右侧有加减号组件,右上方有清空组件,购物车详情页有固定高度,超过固定高度时就会滚动,不满足固定高度时,只能被自己的高度撑开

首先,在shopcart.vue中添加购物车详情页的DOM结构,v-show控制购物车的展示和收起状态,listShow默认为false,购物车主要分为两块,list-header(购物车标题+清空字块)和购物车商品的详情列表list-content,遍历的是selectFoods,li分为左右两侧,左侧为商品名称和价格,右侧为加减号组件

<transition name="fade">
                    <div class="shopcart-list" v-show="listShow">
                        <div class="list-header">
                            <h1 class="title">购物车</h1>
                            <span class="empty" @click="empty">清空</span>
                        </div>
                        <div class="list-content" ref="listContent">
                            <ul>
                                <li class="food" v-for="food in selectFoods" :key="food.id">
                                    <span class="name">{{food.name}}</span>
                                    <div class="price">
                                        <span>¥{{food.price * food.count}}</span>
                                    </div>
                                    <div class="cartcontrol-wrapper">
                                        <!-- 记得将cartcontrol组件import到shopcart中,并注册components-->
                                        <cartcontrol :food = "food"></cartcontrol>
                                    </div>
                                </li>
                            </ul>
                        </div>
                    </div>
            </transition>

定义变量表示购物车的收起和展开状态

       data() {
            return {
                //添加一个变量,用来存贮已经下落的小球
                dropBalls: [],
                fold: true //定义收起和展开状态,购物车详情列表默认折叠
            };
        }
            listShow() {
                // 没有商品时为折叠状态
                if (!this.totalCount) {
                    this.fold = true;// true时为折叠状态
                    return false; //不做切换
                }
                // 有商品的时候以变量show做状态切换
                let show = !this.fold; // fold为true则show为false,fold为false,则show为true
                return show;
            }
        }

然后给contentbang绑定一个详情界面

   <div class="content" @click="toggleList"> <!--  详情界面-->
         toggleList() {
                if (!this.totalCount) { //购物车没有商品的时候不可点击 
                    return;
                }
                this.fold = !this.fold; //当前是收起状态就展开,展开状态就收起
            }

接下来写css样式

.shopcart-list
            position absolute
            top 0
            left 0
            z-index -1
            width 100%
            transform translate3d(0, -100%, 0) //整个列表相对于当前自身的高度做一个偏移
            &.fade-enter-active, &.fade-leave-active 
                transition: all 0.5s linear 
                transform translate3d(0, -100%, 0) //每个表项相对于当前自身的高度做一个偏移
            &.fade-enter, &.fade-leave-active
                transform translate3d(0, 0, 0)
            .list-header
                height 40px
                line-height 40px
                padding 0 18px
                background #f3f5f7
                border-bottom 1px solid rgba(7, 17, 27, 0.1)
                .title
                    float left
                    font-size 14px
                    color rgb(7,17,27)
                .empty
                    float right
                    font-size 12px
                    color rgb(0,160,220)
            .list-content
                padding 0 18px
                max-height 217px
                overflow hidden
                background  #ffffff
                .food
                    position relative
                    padding 12px 0
                    box-sizing border-box
                    border-1px(rgba(7,17,27,0.1))
                    .name
                        line-height 24px
                        font-size 14px
                        color rgb(7,17,27)
                    .price 
                        position absolute
                        right 90px
                        bottom 12px
                        line-height 24px
                        font-size 14px
                        font-weight 700
                        color rgb(240, 20, 20)
                    .cartcontrol-wrapper
                        position absolute
                        right 0
                        bottom 6px

列表生成,还有几个问题没有解决,首先,购物车详情列表和主页面之间有一个半透明的背景色没有实现,还有就是清空按钮没有实现,加减号组件没有实现点击。

首先看加减号不能点击的原因,我们是在betterScroll派发一个事件的时候进行的点击,所以需要betterScroll跟购物车列表相关联,当视口超过固定宽度时进行滚动

首先,引入betterScroll,

import BScroll from 'better-scroll';

并在listShow中进行初始化,之后在列表展示的时候才需要给列表做初始化

 listShow() {
                // 没有商品时为折叠状态
                if (!this.totalCount) {
                    this.fold = true;// true时为折叠状态
                    return false; //不做切换
                }
                // 有商品的时候以变量show做状态切换
// fold为true(折叠)则show为false(折叠),fold为false(展开),则show为true(展开);
// 这样,show就跟v-show的true和false状态同步了,变为true时展开,false时折叠
                let show = !this.fold; 
                if (show) { // true为展示状态
                    this.$nextTick(() => {
                         if (!this.scroll) { // 没有scroll的时候才需要new,否则只需要刷新一下即可
                            this.scroll = new BScroll(this.$refs.listContent, {
                            click: true
                            });
                          } else {
                           this.scroll.refresh();
                        }
                    });
                }
                return show;
            }
        }

之后我们点击的时候,betterScroll就会自动派发一个事件,实现点击和动画

之后,实现click清空事件

                    <div class="list-header">
                            <h1 class="title">购物车</h1>
                            <span class="empty" @click="empty">清空</span>
                        </div>

将selectFoods中的food.count设为0即可

         empty() {
                this.selectFoods.forEach((food) => { //遍历food,将food的count都置零
                    food.count = 0;
                });
            }

还要添加一个背景,半透明模糊是相对于整个 屏幕 做定位的,所以要和shopCart  平级

        <transition name="fade">
                <!-- listShow表示当list详情列表显示的时候mask才显示 -->
                <div class="list-mask" v-show="listShow" @click="hideList()"></div>
        </transition>

写样式:相对于窗口固定布局,宽高都是100%,设定z-index要小于shopcart的z-index,之后添加缓动

    .list-mask
        position fixed 
        top 0
        left 0
        width 100%
        height 100%
        z-index 40 //z-index要小于shopcart的index
        backdrop-filter blur(10px) // 模糊效果
        -webkit-backdrop-filter blur(10px)
        opacity 1
        background rgba(7, 17, 27, 0.6)
        &.fade-enter-active, &.fade-leave-active  
            opacity 1
            transition: all 0.5s //设置缓动效果
            background rgba(7, 17, 27, 0.6)
        &.fade-enter, &.fade-leave-active 
            opacity 0
            background rgba(7, 17, 27, 0)

点击背景的时候也可以收起列表,添加hideList()方法

         hideList() { 
                this.fold = true; //点击mark层,购物车详情列表被收回
            }

做点击结算:click pay函数

                <!-- 点击去结算,结算过后因为事件冒泡的原因(pay按钮是content详情界面的子节点),详情列表会被展开,用stop.prevent阻止事件冒泡-->
                <div class="content-right" @click.stop.prevent="pay">
                    <div class="pay" :class="payClass"> <!-- 在这里做一个状态的判断-->
                        {{payDesc}}
                    </div>
                </div>

 

            pay() { //点击去结算
                if (this.totalPrice < this.minPrice) {
                    return;
                }
                window.alert(`支付¥${this.totalPrice}元`);
                //window.alert('支付' + this.totalPrice + '元');
            }

点击结算按钮,购物车详情页被展开是因为事件的冒泡缘故,所以要阻止冒泡-----content-right冒泡到content上的缘故,导致content出现了切换购物车详情页的情况

    <div class="content" @click="toggleList">
 <div class="content-right" @click.stop.prevent="pay">

接下来实现购物车详情页

Logo

前往低代码交流专区

更多推荐