定義了2個wrapper,分別是menu-wrapper和foods-wrapper,對應當前頁面的架構,左右兩邊的區域css
v-if和v-show的選擇使用html
v-for傳遞索引vue
vue傳遞原生事件$eventandroid
使用stylus的mixin處理一些border和img的問題git
創建menu區域和foods區域的Y座標對應關係,實現滾動foods區域會顯示相應的menu區域,點擊某個menu區域就顯示某個固定的foods區域github
flex佈局的使用,實現foods區域的佈局npm
技巧類:對一些須要js操做的class(可是又沒有實際用途的)能夠創建一個相似food-list-hook鉤子類segmentfault
font-size爲0的技術點,處理行內元素的間隙問題api
vue的$nextTick使用數組
vue的$refs的使用
vue的computed屬性使用
vue的class綁定使用
在一些地方里面,使用table的垂直居中會很簡單實現垂直居中
better-scroll的使用
<template> <div class="goods"> //第一個區域,menu區域 <div class="menu-wrapper" ref="menuWrapper"> <ul> //v-for的使用,class綁定,傳遞原生事件 <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex === index}" @click="selectMenu(index,$event)"> <span class="text border-1px"> <!--v-show使用--> <span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span>{{item.name}} </span> </li> </ul> </div> //第二個區域 food區域 //$refs的使用 <div class="foods-wrapper" ref="foodsWrapper"> <ul> //hook鉤子類的使用(food-list-hook) <li v-for="item in goods" class="food-list food-list-hook"> <h1 class="title">{{item.name}}</h1> <ul> <li v-for="food in item.foods" class="food-item"> <div class="icon"> <img width="57" height="57" :src="food.icon"> </div> <div class="content"> <h2 class="name">{{food.name}}</h2> <p class="desc">{{food.description}}</p> <div class="extra"> <span class="count">月售{{food.sellCount}}份</span><span>好評率{{food.rating}}%</span> </div> <div class="price"> <span class="now">¥{{food.price}}</span><span class="old" v-show="food.oldPrice">¥{{food.price}}</span> </div> </div> </li> </ul> </li> </ul> </div> //第三個區域,shopcart區域 <shopcart ref="shopcart" :selectFoods="selectFoods" :deliveryPrice="seller.deliveryPrice" :minPrice="seller.minPrice"></shopcart> //第四個區域,隱藏的食物詳情區域 <food @add="addFood" :food="selectedFood" ref="food"></food> </div> </template>
備註:
v-for使用已經很常見了,不過這裏須要瞭解,vue1和2有區別,如今是用vue2,因此index變量傳遞會變成如今這種模式(item,index) in goods
vue傳遞原生事件使用$event
:class="{'current':currentIndex === index}"
是vue的綁定class的使用方法,經過綁定一個class變量來直接操做,而且這裏的邏輯會跟js代碼裏面對應
經過currentIndex和index作對比,來確認是否添加current類,他們之間的對比關係也就是menu區域和foods區域的顯示區域的對比關係
經過添加current類來實現當前頁面的區域的樣式變化
currentIndex是一個計算屬性,能夠隨時變化而且直接反應到dom上(看js裏面邏輯)
v-show和v-if的區別官網已經說過
1. v-if 是「真正的」條件渲染,由於它會確保在切換過程當中條件塊內的事件監聽器和子組件適當地被銷燬和重建。 2. v-if 也是惰性的:若是在初始渲染時條件爲假,則什麼也不作——直到條件第一次變爲真時,纔會開始渲染條件塊。 通常來講, v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。所以,若是須要很是頻繁地切換,則使用 v-show 較好;若是在運行時條件不太可能改變,則使用 v-if 較好。
$refs
的使用是vue操做dom的一種方式:
ref 被用來給元素或子組件註冊引用信息。引用信息將會註冊在父組件的 $refs 對象上。
若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素; 若是用在子組件上,引用就指向組件實例:
hook鉤子類的使用,須要結合js裏面的語法來看,這個類只是用來操做,不會產生dom的渲染,方便js控制清晰.
<script> import BScroll from 'better-scroll'; //導入better-scroll import shopcart from '../../components/shopcart/shopcart';//導入shopcart購物車組件 import cartcontrol from '../../components/cartcontrol/cartcontrol'; //導入購物組件 import food from '../../components/food/food'; //導入食物詳情組件 const ERR_OK = 0; //常量,方便解耦 export default { props: { seller: { type: Object } }, data(){ return { goods: [], listHeight: [], //用來儲存foods區域的各個區塊的高度(clientHeight) scrollY: 0 //用來存儲foods區域的滾動的Y座標 selectedFood: {} //用來存儲當前已被選擇的food數據,對象保存形式 } }, computed: { currentIndex(){ //計算到達哪一個區域的區間的時候的對應的索引值 for (let i = 0; i < this.listHeight.length; i++) { let height1 = this.listHeight[i]; //當前menu子塊的高度 let height2 = this.listHeight[i + 1]; //下一個menu子塊的高度 //滾動到底部的時候,height2爲undefined,須要考慮這種狀況 //須要肯定是在兩個menu子塊的高度區間 if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)) { return i; //返回這個menu子塊的索引 } } return 0; }, selectFoods() { //自動將全部的goods.food添加一個count屬性,方便作數量運算 let foods = []; this.goods.forEach((good) => { good.foods.forEach((food) => { if (food.count) { foods.push(food); } }); }); return foods; } }, created(){ this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']; this.$http.get('/api/goods').then((response) => { response = response.body; if (response.errno === ERR_OK) { this.goods = response.data; this.$nextTick(() => { //使用$nextTick來等待異步完成以後更新dom this._initScroll(); //綁定滾動dom this._calculateHeight(); //計算foods區域的各個區域的高度 }); } }); }, methods: { selectFood(food, event){ if (!event._constructed) {//忽略掉BScroll的事件 return; } this.selectedFood = food; //寫入當前選擇的food this.$refs.food.show(); //顯示當前選擇的food的詳情頁 }, selectMenu(index, event){ if (!event._constructed) { //忽略掉BScroll的事件 return; } let foodsList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook'); let el = foodsList[index]; //相似jump to的功能,經過這個方法,跳轉到指定的dom this.foodsScroll.scrollToElement(el, 300); }, addFood(target) { this._drop(target); }, _drop(target) { // 體驗優化,異步執行下落動畫 this.$nextTick(() => { this.$refs.shopcart.drop(target); //調用shopcart的下落動畫 }); }, _initScroll(){ //初始化scroll區域 this.menuScroll = new BScroll(this.$refs.menuWrapper, { click: true //結合BScroll的接口使用,是否將click事件傳遞,默認被攔截了 }); this.foodsScroll = new BScroll(this.$refs.foodsWrapper, { probeType: 3 //結合BScroll的接口使用,3實時派發scroll事件 }); //結合BScroll的接口使用,監聽scroll事件(實時派發的),並獲取鼠標座標 this.foodsScroll.on('scroll', (pos) => { this.scrollY = Math.abs(Math.round(pos.y));//滾動座標會出現負的,而且是小數,因此須要處理一下 }) }, //計算foods內部塊的高度 _calculateHeight(){ let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook'); //獲取每個food的dom對象 let height = 0; this.listHeight.push(height); //初始化第一個高度爲0 for (let i = 0; i < foodList.length; i++) { let item = foodList[i]; //每個item都是剛纔獲取的food的每個dom height += item.clientHeight; //主要是爲了獲取每個foods內部塊的高度 this.listHeight.push(height); } } }, components: { shopcart, cartcontrol, food } } </script>
備註:
<food @add="addFood" :food="selectedFood" ref="food">
是經過selectFood方法寫入到vue實例裏面,而後傳給子組件food
<shopcart ref="shopcart" :selectFoods="selectFoods"
這裏selectFoods被自動添加了count屬性,是爲了讓購物車更加簡單的計算已選擇的food
這裏最關鍵的是menu和food兩個區域的對應處理:
在vue實例生命週期的開始created分別加載_initScroll
和_calculateHeight
經過_calculateHeight
計算foods內部每個塊的高度,組成一個數組listHeight
在_initScroll裏面,設置了bscroll插件的一個監聽事件scroll,將food區域當前的滾動到的位置的y座標設置到一個vue實例屬性scrollY this.scrollY = Math.abs(Math.round(pos.y));
經過計算屬性currentIndex,獲取到food滾動區域對應的menu區域的子塊的索引,而後經過設置一個class來作樣式切換變化:class="{'current':currentIndex === index}
,實現聯動
另外當點擊menu 區域的時候,會觸發selectMenu事件,也會根據點擊到的menu子塊的索引而後去觸發food區域滾動到對應的高度區塊區間this.foodsScroll.scrollToElement(el, 300);
這樣完成整個對應.
拋物線小球動畫橫跨多個vue組件(也能夠說是橫跨了多個DOM),整個動畫會在關於購物車添加按鈕的動畫裏面說
better-scroll須要安裝,npm安裝,具體參看better-scroll官網
關於在selectMenu中點擊,在pc界面會出現兩次事件,在移動端就只出現一次事件的問題:
緣由:
bsScrooler會監聽事件(例如touchmove,click之類),而且阻止默認事件(prevent stop),而且他只會監聽移動端的,pc端的沒有監聽
在pc頁面上 bsScroller也派發了一次click事件,原生也派發了一次click事件
//bsScroll的事件,有_constructed: true MouseEvent {isTrusted: false, _constructed: true, screenX: 0, screenY: 0, clientX: 0…} //pc的事件 MouseEvent {isTrusted: true, screenX: -1867, screenY: 520, clientX: 53, clientY: 400…}
解決:
針對bsScroole的事件,有_constructed: true,因此作處理,return掉非bsScroll的事件
<style lang="stylus" rel="stylesheet/stylus"> @import "../../common/stylus/mixin.styl" .goods display: flex position: absolute //根據設計圖實現, top: 174px //去掉header和tab的位置 bottom: 46px //預留購物車位置 width: 100% overflow: hidden .menu-wrapper flex: 0 0 80px //flex佈局預留空間 width: 80px //兼容android瀏覽器,不加的話會沒辦法預留空間 background: #f3f5f7 .menu-item display: table height: 54px width: 56px padding: 0 12px line-height: 14px &.current position: relative z-index: 10 margin-top: -1px background: #ffffff font-weight: 700 .text border-none() .icon display: inline-block vertical-align: top width: 12px height: 12px margin-right: 2px background-size: 12px 12px background-repeat: no-repeat &.decrease bg-image('decrease_3') &.discount bg-image('discount_3') &.guarantee bg-image('guarantee_3') &.invoice bg-image('invoice_3') &.special bg-image('special_3') .text display: table-cell width: 56px vertical-align: middle //table默認支持垂直居中 border-1px(rgba(7, 17, 27, 0.1)) font-size: 12px .foods-wrapper flex: 1 .title padding-left: 14px height: 26px line-height: 26px border-left: 2px solid #d9dde1 font-size: 12px color: rgb(147, 153, 159) background: #f3f5f7 .food-item display: flex margin: 18px padding-bottom: 18px border-1px: (rgba(7, 17, 27, 0.1)) &:last-child border-none() margin-bottom: 0 .icon flex: 0 0 57px margin-right: 10px .content flex: 1 //flex佈局等分剩下空間 .name margin: 2px 0 8px 0 height: 14px line-height: 14px font-size: 14px color: rgb(7, 17, 27) .desc, .extra line-height: 10px font-size: 10px color: rgb(147, 153, 159) .desc margin-bottom: 8px line-height: 12px .extra .count margin-right: 12px .price font-weight: 700 line-height: 24px .now margin-right: 8px font-size: 14px color: rgb(240, 20, 20) .old text-decoration: line-through font-size: 10px color: rgb(240, 20, 20) .cartcontrol-wrapper position: absolute right: 0 bottom: 12px </style>