記錄 vue + better-scroll 仿美團菜單左右聯(lián)動 + 小球飛入購物車

公司提出需求要做一個點餐系統(tǒng)

需求:

點擊左側(cè)菜單 右側(cè)滑動到相應菜品分類位置蟆沫, 右側(cè)滑動時滑動到哪個分類左側(cè)相應分類高亮并且向中部滾動

使用 vue + better-scroll

1籽暇、項目下載 better-scroll

npm install better-scroll --save

2、在需要頁面或組件引入

import BScroll from 'better-scroll'

因嘴笨直接貼代碼了... DOM 結(jié)構(gòu)

<template>
  <div class="Dishes" id="Dishes">
    <div class="header">
      <div class="search">
        <div class="left">
          <img class="picSea" src="../assets/search.png" alt="">
          <van-cell-group>
            <van-field v-model="value" type="search" placeholder="搜索菜品名" class="searchFood"/>
          </van-cell-group>
        </div>
      </div>
    </div>
    <div :class="[{afterScroll: afterScroll}, 'bb']">
      <div class="set">
        <div class="place">
          <h3>蘇寧廣場店</h3>
          <p>距離您約556.8km</p>
        </div>
        <div class="way" @click="changeWay">
          <p :class="way ? 'active' : ''">堂食</p>
          <p :class="!way ? 'active' : ''">外賣</p>
          <div class="blackBoll" :style="{left: blackBoll}"></div>
        </div>
      </div>
    <div class="advertising">
      <img src="../assets/aaa.png" alt="">
    </div>
    </div>
    <div class="shop" id="shop">
      <!-- 左邊 -->
      <div class="menu-wrapper" :style="{height: clientHeightY + 'px'}">
        <ul>
          <!-- current -->
          <li 
            class="menu-item"
            v-for="(goods,index) in searchgoods" 
            :key="index"
            :class="{active: index === currentIndex}"
            @click="clickList(index)"
            ref="menuList"
            >
            <span>{{goods}}</span>
          </li>
        </ul>
      </div>
      <!-- 右邊 -->
      <div class="shop-wrapper" :style="{height: clientHeightY + 'px'}">
        <ul ref="itemList" class="food-item">
          <li class="shops-li food-list" v-for="(goods, index1) in searchgoods" :key="index1">
            <div class="shops-title">
              <h4>{{goods}}</h4>
            </div>
            <ul>
              <li v-for="(it, ind) in 8" :key="ind">
                <div class="foodImg">
                  <img src="../assets/shopImg.png" alt="">
                </div>
                <div class="foodPrices">
                  <div class="title">店鋪招牌百威啤酒</div>
                  <div class="good">好評  99+</div>
                  <div class="info">
                    <div>
                      <p>¥</p>
                      <p>10</p>
                      <p>/份</p>
                      <p>¥15</p>
                    </div>
                    <div>
                      <i @click="additem" class="iconfont icontianjia"></i>
                    </div>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
    <!-- 購物車 -->
    <div class="footer">
      <div class="ball-container">
        <!--小球-->
        <div v-for="(ball, index) in balls" :key="index">
          <transition
            name="drop"
            @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 class="shoppingCar" @click="showShopCar">
        <i  :class="{addBig: addBig}" class="iconfont icongouwuche"></i>
        <div class="total">
          <div>總計</div>
          <span>¥</span>
          <div class="money">200</div>
        </div>
      </div>
      <div class="submit" @click="submit">
        確定下單
      </div>
    </div>
    <!-- 購物車彈框 -->
    <van-action-sheet v-model="show"  style="z-inde: 5!important;"  @close="onClose">
      <div class="buyCar">
        <div class="title">
          清空購物車
        </div>
        <ul>
          <li v-for="(item, index) in 3" :key="index">
            <div class="shopImg">
              <img src="../assets/shopImg.png" alt="">
            </div>
            <div class="shopInfo">
              <h3>店鋪招牌百威啤酒</h3>
              <p>(約500毫升)+冰塊</p>
            </div>
            <div class="shopNum">
              10
            </div>
          </li>
        </ul>
      </div>
    </van-action-sheet>
  </div>
</template>

js 代碼


<script>
import BScroll from 'better-scroll'
export default {
  data() {
    return {
      addBig: false,
      afterScroll: false,
      oldHeight: 0,
      clientHeightY: 400,
      ortherHeight: 0,
      value: '',
      isScroll: false,
      way: true,
      clientWidth: 0,
      blackBoll: '0px',
      show: false,
      searchgoods: [],
      scrollY: 0, //右側(cè)列表滑動的y軸坐標
      rightLiTops:[], //所有分類頭部位置
      balls: [
        //小球 多設置幾個 因為可能多次點擊
        {
          show: false
        },
        {
          show: false
        },
        {
          show: false
        },
        {
          show: false
        },
        {
          show: false
        },
        {
          show: false
        },
      ],
      dropBalls: []
    }
  },
  computed: {
    //動態(tài)綁定class類名
    currentIndex(index) {
      const {scrollY,rightLiTops} = this;
      return rightLiTops.findIndex((tops,index )=>{
        this._initLeftScroll(index);
        return scrollY >= tops && scrollY < rightLiTops[index + 1]
      })
    }
  },
  created () {
    this.searchgoods = ['aa', 'bb', 'cc', 'dd', 'ff', 'ee', 'ss', 'vggv', 'asd', 'hgt', 'sad', 'asda', 'iojo', 'asd']
  },
  mounted () {
    // 為了滑動區(qū)域高度適應所有機型
    let height = document.body.clientHeight
    let width = document.body.clientWidth
    this.clientWidth = width
    this.clientHeightY = height - width / 750 * 450
    this.oldHeight = this.clientHeightY
    this.ortherHeight = this.clientHeightY + this.clientWidth / 750 * 220
    console.log(this.clientHeightY)
    //監(jiān)聽數(shù)據(jù)
    this.$nextTick(() =>{
      //左右兩邊滾動
      this. _initBScroll();
      //右邊列表高度
      this._initRightHeight()
    })
  },
  methods: {
    additem(event) {
      this.drop(event.target);
      // this.count++;
      setTimeout(() => {
        this.addBig = true
        setTimeout(() => {
          this.addBig = false
        }, 100)
      }, 400)
    },
    drop(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);
          return;
        }
      }
    },
    beforeDrop(el) {
      /* 購物車小球動畫實現(xiàn) */
      let count = this.balls.length;
      while (count--) {
        let ball = this.balls[count];
        if (ball.show) {
          let rect = ball.el.getBoundingClientRect(); //元素相對于視口的位置
          let x = rect.left - 60;
          let y = -(window.innerHeight - rect.top - 44); //獲取y
          el.style.display = "";
          el.style.webkitTransform = "translateY(" + y - 4 + "px)"; //translateY
          el.style.transform = "translateY(" + y + "px)";
          let inner = el.getElementsByClassName("inner-hook")[0];
          inner.style.webkitTransform = "translateX(" + x + "px)";
          inner.style.transform = "translateX(" + x + "px)";
        }
      }
    },
    dropping(el, done) {
      /*重置小球數(shù)量  樣式重置*/
      let rf = el.offsetHeight;
      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)";
      el.addEventListener("transitionend", done);
    },
    afterDrop(el) {
      /*初始化小球*/
      let ball = this.dropBalls.shift();
      if (ball) {
        ball.show = false;
        el.style.display = "none";
      }
    },
    _initBScroll() {
      //左邊滾動
      this.leftBscroll = new BScroll('.menu-wrapper',{click: true});
    
      //右邊滾動
      this.rightBscroll = new BScroll('.shop-wrapper',{
        probeType:3,
        click: true,
        bounce: false
      });
      //監(jiān)聽右邊滾動事件
      this.rightBscroll.on('scroll',(pos) => {
        this.scrollY = Math.abs(pos.y);
        if (this.scrollY >= 30) {
          this.afterScroll = true
          this.clientHeightY = this.ortherHeight
        } else if (this.scrollY >= 3 || this.scrollY === 0) {
          this.afterScroll = false
          this.clientHeightY = this.oldHeight
        }
      })
    },
    
    //求出右邊列表的高度
    _initRightHeight () {
      let itemArray = []; //定義一個偽數(shù)組
      let top = 0;
      itemArray.push(top)
      //獲取右邊所有l(wèi)i的禮
      let allList = this.$refs.itemList.getElementsByClassName('shops-li');
      //allList偽數(shù)組轉(zhuǎn)化成真數(shù)組
      Array.prototype.slice.call(allList).forEach(li => {
        top += li.clientHeight; //獲取所有l(wèi)i的每一個高度
        itemArray.push(top)
      });
      this.rightLiTops = itemArray;
      // console.log(this.rightLiTops)
    },
    //點擊左邊實現(xiàn)滾動
    clickList(index){
      console.log('111')
        this.scrollY = this.rightLiTops[index];
        console.log(this.scrollY)
        this.rightBscroll.scrollTo(0, -this.scrollY, 600,)
    },
    //左右聯(lián)調(diào) 
    _initLeftScroll(index){
      let menu = this.$refs.menuList;
      let el = menu[index];
      this.leftBscroll.scrollToElement(el, 200 , 0, true)
    },
    submit() {
      this.$router.push('/order')
    },
    onClose() {
      this.show = false
    },
    showShopCar() {
      this.show = true
    },
    changeWay() {
      this.way = !this.way
      this.way ? this.blackBoll = '0px' : this.blackBoll = '38px'
    },
    
  }
}
</script>

css 樣式 預編譯語言用的less 適配過rem 基準寬度750

使用的適配插件 postcss-pxtorem


<style scoped  lang="less">

@keyframes changeBig {
  from {
    transform: scale(1.2);
  }
  to {
    transform: scale(1);
  }
}

.addBig {
  animation: changeBig 0.2s;
  // transform: scale(1.2);
  // transition: all 0.1s;
}

.ball{
  position: fixed;
  left: 120px;
  bottom: 60px;
  z-index: 200;
  transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41); /*貝塞爾曲線*/
}
.inner{
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background-color: red;
  transition: all 0.4s linear;
}

.bb {
  height: 220px;
  transition: all 0.5s;
}

.afterScroll {
  opacity: 0;
  transition: all 0.5s;
  height: 0;
}

.Dishes {
  height: 100vh;
  box-sizing: border-box;
  padding-top: 102px;
}
.shop {
  position: relative;
  top: 0;
  padding: 0;
  background: #fff;
  z-index: 10;
}
.menu-wrapper {
  position: absolute;
  left: 0;
  top: 0;
  overflow: hidden;
  height: 800px;
  transition: all 0.5s;
  background: #FFFFFF;
  box-shadow:10px 0 15px -15px rgba(0, 0, 0, 0.5);
  ul {
    padding-bottom: 40px;
    li {
      width: 140px;
      text-align: center;
      line-height: 80px;
    }
    .active {
      background: #FFE103;
    }
  }
}
.shop-wrapper {
  position: absolute;
  left: 140px;
  top: 0;
  overflow: hidden;
  background: #FFFFFF;
  transition: all 0.5s;
  height: 800px;
  .food-item {
    padding-bottom: 80px;
    .shops-li {
      width: 610px;
      // text-align: center;
      // line-height: 800px;
      // height: 800px;
      .shops-title {
        padding: 40px 0 0 40px;
      }
    }
  }
}
.header {
  width: 100%;
  background: #FECF02;
  position: fixed;
  z-index: 5;
  top: 0;
  padding: 0 0 0 60px;
  box-sizing: border-box;
  box-shadow:inset 0px 15px 10px -15px rgba(0, 0, 0, 0.3);
}

.header .search {
  width: 100%;
  height: 102px;
  padding: 16px 0 0 20px;
  margin: 0 auto;
  box-sizing: border-box;
}

.header .search .left {
  width: 580px;
  height: 66px;
  background: #ffffff;
  display: flex;
  justify-content: left;
  border-radius: 33px;
  align-items: center;
}

.header .search .left .searchFood {
  width: 400px;
  line-height: 44px;
}

.header .search .right {
  display: flex;
  width: 180px;
  font-size: 28px;
  justify-content: left;
  align-items: center;
}

.header .search .right i {
  font-size: 10px;
  margin: 8px 0 0 8px;
}

.header .search .picSea {
  width: 40px;
  height: 40px;
  margin-left: 50px;
  /* position: absolute;
  top: 34px;
  left: 130px; */
}

.header .selector ul{
  width: 100%;
  height: 62px;
  display: flex;
  justify-content: space-around;
  font-size: 24px;
}

.header .selector ul li {
  display: flex;
  justify-content: left;
  align-items: center;
}

.header .selector ul li i {
  margin: 6px 0 0 6px;
  font-size: 24px;
}

.set {
  width: 100%;
  height: 110px;
  padding: 0px 84px;
  // padding-top: 110px;
  box-sizing: border-box;
  display: flex;
  justify-content: space-between;
  font-size: 22px;
  align-items: center;
}

.set .place h3 {
  font-size: 26px;
  font-weight: 600;
  margin-bottom: 10px;
}

.set .place p {
  color: #9FA0A0;
}

.advertising img {
  width: 100%;
  height: 110px;
}

.set .way {
  width: 150px;
  height: 50px;
  display: flex;
  line-height: 50px;
  box-sizing: border-box;
  justify-content: space-between;
  border: 1px solid #000000;
  border-radius: 25px;
  font-weight: 600;
  position: relative;
  z-index: 2;
}

.set .way p {
  width: 50%;
  text-align: center;
  position: relative;
  z-index: 3;
}

.blackBoll {
  position: absolute;
  background: #000;
  border-radius: 25px;
  width: 80px;
  height: 50px;
  z-index: 0;
  top: 0;
  transition: all 0.3s;
}

.set .way .active {
  /* width: 60%; */
  border-radius: 25px;
  color: #ffffff;
  font-weight: 600;
}

/* .left_menu {
} */

// .menu-wrapper {
//   width: 142px;
//   position: absolute;
//   top: 330px;
//   /* background: #FECF02; */
//   box-shadow:10px 0 15px -15px rgba(0, 0, 0, 0.3);
// }

// .menu-item {
//   height: 100px;
//   line-height: 100px;
//   text-align: center;
// }

.foods-wrapper {
  width: 608px;
  height: 860px;
  position: absolute;
  right: 0px;
  top: 330px;
  /* background: hotpink; */
}

.food-list h3{
  // line-height: 30px;
  padding-left: 80px;
  color: #666;
  font-size: 24px;
  /* background: #FECF02; */
}

.food-list h3:nth-child(1) {
  margin-top: 20px;
}

.food-list ul:nth-last-child(1) {
  // padding-bottom: 80x;
}

.food-list ul li {
  width: 540px;
  height: 214px;
  margin: 0 auto;
  margin-top: 30px;
  border-radius: 20px;
  box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.1);
  padding: 24px 40px 22px 28px;
  box-sizing: border-box;
  display: flex;
  justify-content: left;
}

.food-list ul li:nth-last-child(1) {
  // margin-bottom: 40px;
}

.food-list ul li .foodImg img {
  width: 168px;
  height: 168px;
  margin-right: 10px;
}

.food-list ul li .foodPrices {
  flex: 1;
  i {
    font-size: 40px;
  }
}

.food-list ul li .foodPrices .title{
  font-size: 24px;
  font-weight: 600;
  line-height: 60px;
}

.food-list ul li .foodPrices .good {
  font-size: 18px;
  color: #9FA0A0;
  margin-bottom: 25px;
}

.food-list ul li .foodPrices .info {
  display: flex;
  justify-content: space-between;
}

.food-list ul li .foodPrices .info div:nth-child(1) {
  height: 30px;
  width: 140px;
  position: relative;
}

.food-list ul li .foodPrices .info div:nth-child(1) p:nth-child(1) {
  font-size: 18px;
  font-weight: 600;
  position: absolute;
  bottom: 0px;
}

.food-list ul li .foodPrices .info div:nth-child(1) p:nth-child(2) {
  font-size: 36px;
  position: absolute;
  bottom: -5px;
  font-weight: 600;
  left: 20px;
}

.food-list ul li .foodPrices .info div:nth-child(1) p:nth-child(3) {
  font-size: 18px;
  position: absolute;
  bottom: 0;
  font-weight: 600;
  left: 68px;
}

.food-list ul li .foodPrices .info div:nth-child(1) p:nth-child(4) {
  font-size: 18px;
  color: #9FA0A0;
  text-decoration:line-through;
  position: absolute;
  bottom: -2px;
  left: 120px;
}

.current {
  background-color: #FECF02;
}

.footer {
  width: 100%;
  height: 133px;
  position: fixed;
  bottom: 0;
  background: #000000;
  display: flex;
  justify-content: space-between;
  z-index: 201500;
}

.footer .shoppingCar {
  display: flex;
  justify-content: center;
  align-items: center;
  flex: 1;
}

.footer .shoppingCar .total {
  color: #ffffff;
  font-size: 24px;
  width: 150px;
  height: 40px;
  display: flex;
  justify-content: left;
  position: relative;
}

.footer .shoppingCar .total div:nth-child(1) {
  margin-right: 10px;
  line-height: 44px;
}

.footer .shoppingCar .total span {
  font-size: 20px;
  position: absolute;
  bottom: 4px;
  left: 60px;
}

.footer .shoppingCar .total .money {
  font-size: 36px;
  position: absolute;
  bottom: 0;
  left: 88px;
}

.footer .shoppingCar i {
  color: #FECF02;
  font-size: 66px;
  margin-right: 28px;
}

.footer .submit{
  width: 290px;
  line-height: 133px;
  background: #FFE103;
  font-size: 36px;
  font-weight: 600;
  text-align: center;
}

.buyCar {
  padding-bottom: 132px;
  box-sizing: border-box;
}

.buyCar .title {
  height: 80px;
  background: #FECF02;
  text-align: right;
  line-height: 80px;
  padding-right: 42px;
  color: #ffffff;
  font-size: 24px;
  font-weight: 600;
}

.buyCar ul {
  padding: 0 42px;
  box-sizing: border-box;
}

.buyCar ul li {
  padding: 20px 0;
  display: flex;
  box-sizing: border-box;
  justify-content: space-between;
  border-bottom: 1px solid #FBF8FB;
}

.buyCar ul li .shopImg img{
  width: 90px;
  height: 90px;
  margin-right: 24px;
}

.buyCar ul li .shopInfo {
  flex: 1;
}

.buyCar ul li .shopInfo h3 {
  font-size: 24px;
  font-weight: 600;
  color: #000000;
  margin-bottom: 16px;
  margin-top: 6px;
}

.buyCar ul li .shopInfo p {
  font-size: 18px;
  color: #9FA0A0;
}

::-webkit-scrollbar {
 
width:0;
 
height:0;
 
color:transparent;
 
}
</style>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戒悠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子舟山,更是在濱河造成了極大的恐慌绸狐,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件累盗,死亡現(xiàn)場離奇詭異寒矿,居然都是意外死亡,警方通過查閱死者的電腦和手機若债,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門符相,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蠢琳,你說我怎么就攤上這事啊终【当” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵蓝牲,是天一觀的道長逛艰。 經(jīng)常有香客問我,道長搞旭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任菇绵,我火速辦了婚禮肄渗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咬最。我一直安慰自己翎嫡,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布永乌。 她就那樣靜靜地躺著惑申,像睡著了一般。 火紅的嫁衣襯著肌膚如雪翅雏。 梳的紋絲不亂的頭發(fā)上圈驼,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音望几,去河邊找鬼绩脆。 笑死,一個胖子當著我的面吹牛橄抹,可吹牛的內(nèi)容都是我干的靴迫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼楼誓,長吁一口氣:“原來是場噩夢啊……” “哼玉锌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疟羹,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤主守,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后阁猜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丸逸,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年剃袍,在試婚紗的時候發(fā)現(xiàn)自己被綠了黄刚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡民效,死狀恐怖憔维,靈堂內(nèi)的尸體忽然破棺而出涛救,到底是詐尸還是另有隱情,我是刑警寧澤业扒,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布检吆,位于F島的核電站,受9級特大地震影響程储,放射性物質(zhì)發(fā)生泄漏蹭沛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一章鲤、第九天 我趴在偏房一處隱蔽的房頂上張望摊灭。 院中可真熱鬧,春花似錦败徊、人聲如沸帚呼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煤杀。三九已至,卻和暖如春沪哺,著一層夾襖步出監(jiān)牢的瞬間沈自,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工凤粗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酥泛,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓嫌拣,卻偏偏與公主長得像柔袁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子异逐,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容