// 指定爲 Flex 佈局 display: flex;
// 主要屬性 flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] flex屬性是flex-grow, flex-shrink 和 flex-basis的簡寫,默認值爲0 1 auto。後兩個屬性可選。 flex-grow屬性定義項目的放大比例,默認爲0,即若是存在剩餘空間,也不放大 flex-shrink屬性定義項目的縮小比例,默認爲1,即若是空間不足,該項目將縮小,flex-shrink屬性爲0,其餘項目都爲1,則空間不足時,前者不縮小 flex-basis屬性定義了在分配多餘空間以前,項目佔據的主軸空間(main size)。瀏覽器根據這個屬性,計算主軸是否有多餘空間。它的默認值爲auto,即項目的原本大小,設爲跟width或height屬性同樣的值(好比350px),則項目將佔據固定空間
flex : 等分 內容縮放 展位空間; flex : 0 0 80px
<template lang="html"> <span class="iconMap" :class="iconClassMap[iconType]"></span> </template>
export default { props: { // 圖標類型 iconType: Number }, created() { // 數組類名 this.iconClassMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee'] } }
import iconMap from '../iconMap/iconMap' // 注意路徑寫法
// 註冊組件 components: { iconMap }
<ul> <li v-for='(item,index) in goods' class="menu-item"> <span class="text"> // json 數據 根據 type 判斷 是否有圖標 <iconMap v-show="item.type>0" :iconType="item.type"></iconMap> {{item.name}} </span> </li> </ul>
npm install better-scroll
import BScroll from 'better-scroll'
(1)原理:父容器wrapper,它具備固定的高度,當它的第一個子元素content 的高度超出了wrapper的高度,咱們就能夠滾動內容區了,若沒有超出則不能滾動了。vue
(2)better-scroll 的初始化ios
better-scroll 的初始化時機很重要,由於它在初始化的時候,會計算父元素和子元素的高度和寬度,來決定是否能夠縱向和橫向滾動。所以,咱們在初始化它的時候,必須確保父元素和子元素的內容已經正確渲染了。若是子元素或者父元素 DOM 結構發生改變的時候,必須從新調用 scroll.refresh() 方法從新計算來確保滾動效果的正常。因此 better-scroll 不能滾動的緣由多半是初始化 better-scroll 的時機不對,或者是當 DOM 結構發送變化的時候並無從新計算 better-scroll。git
(3)better-scroll 結合 Vuegithub
Vue.js 提供了咱們一個獲取 DOM 對象的接口—— vm.$refs。在這裏,咱們經過了 this.$refs.wrapper 訪問到了這個 DOM 對象,而且咱們在 mounted 這個鉤子函數裏,this.$nextTick 的回調函數中初始化 better-scroll 。由於這個時候,wrapper 的 DOM 已經渲染了,咱們能夠正確計算它以及它內層 content 的高度,以確保滾動正常。ajax
這裏的 this.$nextTick
是一個異步函數,爲了確保 DOM 已經渲染,底層用到了 MutationObserver 或者是 setTimeout(fn, 0)。其實咱們在這裏把 this.$nextTick 替換成 setTimeout(fn, 20) 也是能夠的(20 ms 是一個經驗值,每個 Tick 約爲 17 ms),對用戶體驗而言都是無感知的。npm
(4)異步數據的處理json
在咱們的實際工做中,列表的數據每每都是異步獲取的,所以咱們初始化 better-scroll 的時機須要在數據獲取後,代碼以下:axios
<template> <div class="wrapper" ref="wrapper"> <ul class="content"> <li v-for="item in data">{{item}}</li> </ul> </div> </template> <script> import BScroll from 'better-scroll' export default { data() { return { data: [] } }, created() { requestData().then((res) => { this.data = res.data this.$nextTick(() => { this.scroll = new Bscroll(this.$refs.wrapper, {}) }) }) } } </script>
這裏的 requestData 是僞代碼,做用就是發起一個 http 請求從服務端獲取數據,而且這個函數返回的是一個 promise(實際項目中咱們可能會用 axios 或者 vue-resource)。咱們獲取到數據的後,須要經過異步的方式再去初始化 better-scroll,由於 Vue 是數據驅動的, Vue 數據發生變化(this.data = res.data)到頁面從新渲染是一個異步的過程,咱們的初始化時機是要在 DOM 從新渲染後,因此這裏用到了 this.$nextTick,固然替換成 setTimeout(fn, 20) 也是能夠的。數組
注意:這裏爲何是在 created 這個鉤子函數裏請求數據而不是放到 mounted 的鉤子函數裏?由於 requestData 是發送一個網絡請求,這是一個異步過程,當拿到響應數據的時候,Vue 的 DOM 早就已經渲染好了,可是數據改變 —> DOM 從新渲染仍然是一個異步過程,因此即便在咱們拿到數據後,也要異步初始化 better-scroll。
藉助ref屬性用來綁定某個dom元素,或者來講來綁定某個組件,而後在函數內用this.$refs.menuwrapper獲取到dom。
說明:若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素; 若是用在子組件上,引用就指向組件實例:
<div class="menu-wrapper" ref='menuWrapper'> </div>
<div class="foods-wrapper" ref="foodsWrapper"></div>
(1) dom結構徹底加載完再調用_initScroll()方法纔會生效
(2) 由於要監聽內容區域的高度,因此初始化應在created過程當中去監聽dom結構是否徹底加載,這裏是在$nextTick對象中進行觸發檢測
ES6語法格式:this.$nextTick(() => {})
created (){ // 在實例建立完成後被當即調用 $el 屬性目前不可見。 axios.get('static/data.json').then((result) => { this.goods=result.data.goods //dom結構加載結束 this.$nextTick(() => { this._initScroll(); // 初始化scroll }) }) }
(3) 在methods方法裏面定義一個_initScroll的函數,主要用來對左右兩側dom結構進行初始化
methods:{ // 用來對左右兩側dom結構進行初始化 _initScroll (){ // 實例化 better-scroll 插件,傳入要滾動的DOM 對象 this.meunScroll=new BScroll(this.$refs.menuWrapper,{ click:true }); this.foodScroll=new BScroll(this.$refs.foodsWrapper,{ click:true }); } }
說明:vue中更改數據,DOM會跟着作映射,但vue更新DOM是異步的,用 $nextTick ()來確保Dom變化後能調用到_initScroll()方法。調用_initScroll()方法能計算內層ul的高度,當內層ul的高度大於外層wrapper的高度時,能夠實現滾動。
此時倆側能夠分別滾動了!
(4) 實現左右聯動
原理:咱們計算出右側實時變化的y值,落到哪個區間,咱們就顯示那一個區間。首先咱們要計算總體區間的一個高度,而後分別計算第一個區間的高度,第二個區間的高度,以此類推。而後將區間數存入一個定義好的數組。當咱們在滾動的時候實時拿到y軸的高度,而後對比在哪個區間,這樣咱們就會獲得一個區間的索引值去對應左側的菜品類別,最後咱們用一個vue的class去綁定高亮文本。
1.定義一個方法在_initScroll
下面,做爲計算高度的方法叫作_calculateHeight () ,再定義一個listHeight:[]數組,存放獲取到的每一塊foods類的高度。而後經過給每一個li 定義類名來供js 選擇 從而計算出高度存放到listHeight數組裏。
// 經過 方法 計算foods內部每個塊的高度,組成一個數組listHeight。 // 每一個li 定義一個類food-list-hook 經過獲取該類 來計算 每一塊的高度 存到數組listHeight裏 _calculateHeight (){ // 獲取 li 經過food-list-hook let foodList=this.$refs.foodsWrapper.querySelectorAll(".food-list-hook"); let height=0;// 初始化高度 this.listHeight.push(height) // 把第一個高度存入數組 //經過循環foodList下的dom結構,將每個li的高度依次送入數組 for(let i = 0 ,l = foodList.length ; i < l ; i++){ let item=foodList[i]; //每個item都是剛纔獲取的food的每個dom height += item.clientHeight; //獲取每個foods內部塊的高度 this.listHeight.push(height) // 將獲取的值存放到數組裏 } }
2.咱們獲取到區間高度數組後,咱們要實時獲取到右側的y值,和左側的索引值作一個對比,定義一個scrollY變量用來存放實時獲取的y值。bs插件爲咱們提供了一個實時獲取y值的方法,咱們在初始化this.foodScroll的時候加一個·屬性probeType: 3,其做用就是實時獲取y值,至關於探針的做用。
goods: [],// goods json 數組 listHeight: [],// 存放 foods 內部的每一塊的高度 scrollY:0
this.foodScroll=new BScroll(this.$refs.foodsWrapper,{ click:true, //探針做用,實時監測滾動位置 probeType: 3 });
3.咱們再添加一個方法this.foodScroll.on('scroll',(pos) => {}),做用是實時滾動的時候把獲取到的位置給暴露出來。代碼以下。
//結合BScroll的接口使用,監聽scroll事件(實時派發的),並獲取鼠標座標,當滾動時能實時暴露出scroll this.foodScroll.on("scroll",(pos) =>{ // 回調函數 //scrollY接收變量 this.scrollY=Math.abs(Math.round(pos.y)) //滾動座標會出現負的,而且是小數,因此須要處理一下,實時取得scrollY // console.log(pos.y) })
4.定義一個計算屬性computed,獲取到food滾動區域對應的menu區域的子塊的索引i值,從而定位到左側邊欄的位置。
computed:{ currentIndex (){ //計算到達哪一個區域的區間的時候的對應的索引值 // 利用 listHeight 存放 每一塊 對應的高度 for (let i=0,l=this.listHeight.length; i<l ; i++){ let menuHeight_fir = this.listHeight[i] // 當前menu 子塊區域的 高度 let menuHeight_sec = this.listHeight[i + 1] // 下一個menu 子塊區域的 高度 // 當滑到底部時,menuHeight_sec 爲 underfined, // 須要肯定滑到倆個高度區間 if( !menuHeight_sec || (this.scrollY > menuHeight_fir && this.scrollY < menuHeight_sec) ){ return i; } } }, }
獲取到i後,,而後經過設置一個class來作樣式切換變化 :class="{'current':currentIndex === index}"
,當currentIndex和menu-item對應的index相等時,設置current的樣式。這樣就能夠實現左右聯動了。
<li v-for='(item,index) in goods' class="menu-item" :class="index === currentIndex?'menu-item-selected':'menu-item'"> ...
在樣式裏提早設好 選中和正常的樣式
5.最後實現左側點擊的功能。在左側的li下綁定一個selectMenu的點擊事件,並傳入索引值,這樣咱們就能夠知道點擊的是哪個li
<li v-for='(item,index) in goods' class="menu-item" @click="selectMenu(index,$event)" :class="index === currentIndex?'menu-item-selected':'menu-item'"> ...
selectMenu (index, event){ // 點擊左側 ,右側響應 this.foodScroll.scrollTo(0, -this.listHeight[index], 300) }
scrollTo(x, y, time, easing) 滾動到某個位置,x,y 表明座標,time 表示動畫時間,easing 表示緩動函數 scroll.scrollTo(0, 500)
6.關於在selectMenu中點擊事件
在selectMenu中點擊,在pc界面會出現兩次事件,在移動端就只出現一次事件的問題
緣由 : better-scroll 會監聽事件(例如touchmove,click之類),而且阻止默認事件(prevent stop),而且他只會監聽移動端的,pc端的沒有監聽
在pc頁面上 better-scroll 也派發了一次click事件,原生也派發了一次click事件
// better-scroll 的事件,有_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…}
解決 : 針對better-scroll 的事件,有_constructed: true,因此作處理,return掉非better-scroll 的事件
selectMenu(index, event){ if (!event._constructed) { //去掉自帶的click事件點擊,即pc端直接返回 return; } let foodList=this.$refs.foodsWrapper.querySelectorAll(".food-list-hook"); // 得到監聽元素 let el = foodList[index]; // 得到 當前 監聽元素的高度 this.foodScroll.scrollToElement(el, 300); //相似jump to的功能,經過這個方法,跳轉到指定的dom }