天天看點

Vue(三)goods元件開發

一、 布局 Flex

Flex 布局,可以簡便、完整、響應式地實作各種頁面布局,Flex 是 Flexible Box 的縮寫,意為"彈性布局",用來為盒狀模型提供最大的靈活性。任何一個容器都可以指定為 Flex 布局。
// 指定為 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           
Flex 文法 Flex 實踐

二、圖示元件

  1. 子元件 iconMap
<template lang="html">
  <span class="iconMap" :class="iconClassMap[iconType]"></span>
</template>

export default {
  props: { // 圖示類型
    iconType: Number
  },
  created() {  //  數組類名
    this.iconClassMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']
  }
}


           
  1. 父元件 goods
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>           

三、better-scroll 應用

類似iscroll 實作滾動效果
  • 安裝
npm install better-scroll           
  • 引入
import BScroll from 'better-scroll'           
  • 說明

(1)原理:父容器wrapper,它具有固定的高度,當它的第一個子元素content 的高度超出了wrapper的高度,我們就可以滾動内容區了,若沒有超出則不能滾動了。

(2)better-scroll 的初始化

better-scroll 的初始化時機很重要,因為它在初始化的時候,會計算父元素和子元素的高度和寬度,來決定是否可以縱向和橫向滾動。是以,我們在初始化它的時候,必須確定父元素和子元素的内容已經正确渲染了。如果子元素或者父元素 DOM 結構發生改變的時候,必須重新調用 scroll.refresh() 方法重新計算來確定滾動效果的正常。是以 better-scroll 不能滾動的原因多半是初始化 better-scroll 的時機不對,或者是當 DOM 結構發送變化的時候并沒有重新計算 better-scroll。

(3)better-scroll 結合 Vue

Vue.js 提供了我們一個擷取 DOM 對象的接口—— vm.$refs。在這裡,我們通過了 this.$refs.wrapper 通路到了這個 DOM 對象,并且我們在 mounted 這個鈎子函數裡,this.$nextTick 的回調函數中初始化 better-scroll 。因為這個時候,wrapper 的 DOM 已經渲染了,我們可以正确計算它以及它内層 content 的高度,以確定滾動正常。

這裡的 

this.$nextTick

 是一個異步函數,為了確定 DOM 已經渲染,底層用到了 MutationObserver 或者是 setTimeout(fn, 0)。其實我們在這裡把 this.$nextTick 替換成 setTimeout(fn, 20) 也是可以的(20 ms 是一個經驗值,每一個 Tick 約為 17 ms),對使用者體驗而言都是無感覺的。

(4)異步資料的處理

在我們的實際工作中,清單的資料往往都是異步擷取的,是以我們初始化 better-scroll 的時機需要在資料擷取後,代碼如下:

<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。

  • 使用

初始化需要滾動的dom結構

借助ref屬性用來綁定某個dom元素,或者來說來綁定某個元件,然後在函數内用this.$refs.menuwrapper擷取到dom。

說明:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素; 如果用在子元件上,引用就指向元件執行個體:

<div class="menu-wrapper" ref='menuWrapper'> </div>
<div class="foods-wrapper" ref="foodsWrapper"></div>
           

在ajax内執行_initScroll() 函數

在此之前我們要做一些準備和注意事項

(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)
           

參考:

vue使用 better-scroll的參數和方法

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
    }           

goods 元件到此差不多了!

原文釋出時間為:2018年01月23日

原文作者:

前端喵

本文來源:

開源中國

如需轉載請聯系原作者

繼續閱讀