Vue (three) goods component development

1. Layout Flex

Flex layout can realize various page layouts easily, completely and responsively. Flex is the abbreviation of Flexible Box, which means "flexible layout", which is used to provide maximum flexibility for the box model. Any container can be specified as a Flex layout.
// 指定为 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 Syntax
Flex Practice

2. Icon component

  1. Subcomponent 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. Parent component 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>

3. Better-scroll application

Similar to iscroll to achieve scrolling effect
  • Install
  npm install better-scroll
  
  • introduce
 import BScroll from 'better-scroll'
  • illustrate

(1) Principle: The parent container wrapper has a fixed height. When the height of its first child element content exceeds the height of the wrapper, we can scroll the content area. If it does not exceed, we cannot scroll.

(2) Initialization of better-scroll

The initialization timing of better-scroll is very important, because when it is initialized, it will calculate the height and width of the parent element and child element to determine whether it can scroll vertically and horizontally. Therefore, when we initialize it, we must ensure that the content of the parent and child elements has been rendered correctly. If the DOM structure of the child element or parent element changes, the scroll.refresh() method must be called to recalculate to ensure the normal scrolling effect. So the reason why better-scroll can't be scrolled is probably that the timing of initializing better-scroll is wrong, or that better-scroll is not recalculated when the DOM structure changes.

(3) better-scroll combined with Vue

Vue.js provides us with an interface to get DOM objects - vm.$refs. Here, we access the DOM object through this.$refs.wrapper, and we initialize better-scroll in the mounted hook function and this.$nextTick callback function. Because at this time, the wrapper's DOM has been rendered, and we can correctly calculate the height of it and its inner content to ensure normal scrolling.

Here this.$nextTickis an asynchronous function. In order to ensure that the DOM has been rendered, the bottom layer uses MutationObserver or setTimeout(fn, 0). In fact, it is also possible to replace this.$nextTick with setTimeout(fn, 20) here (20 ms is an empirical value, and each Tick is about 17 ms), which is insensitive to user experience.

(4) Processing of asynchronous data

In our actual work, the data of the list is often obtained asynchronously, so the timing of initializing better-scroll needs to be after the data is obtained. The code is as follows:

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

The requestData here is pseudo code, the function is to initiate an http request to get data from the server, and this function returns a promise (in the actual project, we may use axios or vue-resource ). After we get the data, we need to initialize better-scroll in an asynchronous way, because Vue is data-driven, Vue data changes (this.data = res.data) to page re-rendering is an asynchronous process, we The initialization timing is after the DOM is re-rendered, so this.$nextTick is used here. Of course, it is also possible to replace it with setTimeout(fn, 20).

Note: Why is the data requested in the created hook function instead of the mounted hook function? Because requestData sends a network request, this is an asynchronous process. When the response data is obtained, Vue's DOM has already been rendered, but the data changes -> DOM re-rendering is still an asynchronous process, so even in our After getting the data, better-scroll is also initialized asynchronously.

  • use
Initialize the dom structure that needs to be scrolled

Use the ref attribute to bind a dom element, or to bind a component, and then use this.$refs.menuwrapper to get the dom in the function.

Note: If used on a normal DOM element, the reference points to the DOM element; if used on a child component, the reference points to the component instance:

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

Execute _initScroll() function inside ajax
Before that, we have to do some preparations and precautions

(1) The _initScroll() method will not take effect until the dom structure is fully loaded

(2) Because the height of the content area needs to be monitored, the initialization should monitor whether the dom structure is fully loaded during the created process. Here is the trigger detection in the $nextTick object

ES6 syntax format:this.$nextTick(() => {})

created (){ // 在实例创建完成后被立即调用   $el 属性目前不可见。
 	axios.get('static/data.json').then((result) => {
 		this.goods=result.data.goods
        //dom结构加载结束
        this.$nextTick(() => {
        	this._initScroll(); // 初始化scroll
      })
  })
}

(3) Define an _initScroll function in the methods method, which is mainly used to initialize the dom structures on the left and right sides

methods:{
 	// 用来对左右两侧dom结构进行初始化
 	_initScroll (){
 		// 实例化 better-scroll 插件,传入要滚动的DOM 对象 
 		this.meunScroll=new BScroll(this.$refs.menuWrapper,{
 			click:true	 				
 		});
 		this.foodScroll=new BScroll(this.$refs.foodsWrapper,{
 			click:true
 		});
 		
 	}
 }
 

Description: When changing data in vue, the DOM will follow the mapping, but vue updates the DOM asynchronously. Use $nextTick () to ensure that the _initScroll() method can be called after the Dom changes. Calling the _initScroll() method can calculate the height of the inner ul. When the height of the inner ul is greater than the height of the outer wrapper, scrolling can be achieved.

Now the two sides can be rolled separately!

(4) Realize left and right linkage

Principle: We calculate the y value of the real-time change on the right side, and which interval falls, we will display that interval. First, we need to calculate a height of the overall interval, and then calculate the height of the first interval, the height of the second interval, and so on. Then store the interval number into a defined array. When we scroll, we get the height of the y-axis in real time, and then compare which range it is in, so that we will get an index value of the range to correspond to the category of the dishes on the left, and finally we use a vue class to bind the height Bright text.

1. Define a method _initScrollbelow, as a method for calculating height called _calculateHeight (), and then define a listHeight:[] array to store the height of each obtained food class. Then, by defining a class name for each li for js to choose, the height is calculated and stored in the listHeight array.


 // 通过 方法 计算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. After we obtain the interval height array, we need to obtain the y value on the right side in real time, compare it with the index value on the left side, and define a scrollY variable to store the y value obtained in real time. The bs plugin provides us with a method to obtain the y value in real time. When we initialize this.foodScroll, we add an attribute probeType: 3. Its function is to obtain the y value in real time, which is equivalent to the role of a probe.

goods: [],//  goods json  数组
listHeight: [],// 存放 foods 内部的每一块的高度
scrollY:0
this.foodScroll=new BScroll(this.$refs.foodsWrapper,{
 	click:true,
 	//探针作用,实时监测滚动位置
    probeType: 3
 });
	 			

3. We add another method this.foodScroll.on('scroll', (pos) => {}), which is used to expose the obtained position when scrolling in real time. code show as below.

//结合BScroll的接口使用,监听scroll事件(实时派发的),并获取鼠标坐标,当滚动时能实时暴露出scroll
 this.foodScroll.on("scroll",(pos) =>{ //  回调函数
 	//scrollY接收变量 
 	this.scrollY=Math.abs(Math.round(pos.y)) //滚动坐标会出现负的,并且是小数,所以需要处理一下,实时取得scrollY
 	// console.log(pos.y)
 })

4. Define a computed property computed to obtain the index i value of the sub-block of the menu area corresponding to the food scrolling area, so as to locate the position of the left sidebar.

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;
 			}
 		}
 	},
 	
 }

After getting i, then change :class="{'current':currentIndex === index}"the style by setting a class. When the currentIndex and the index corresponding to the menu-item are equal, set the current style. In this way, the left and right linkage can be realized.

<li v-for='(item,index) in goods' class="menu-item"  :class="index === currentIndex?'menu-item-selected':'menu-item'">
...

Set the selected and normal styles in advance in the style

5. Finally realize the function of clicking on the left. Bind a click event of selectMenu under the li on the left, and pass in the index value, so that we can know which li is clicked

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

 

Reference: vue uses parameters and methods of better-scroll

6. About click event in selectMenu

Click in selectMenu, there will be two events on the pc interface, and only one event on the mobile terminal

Reason: better-scroll will listen for events (such as touchmove, click, etc.), and prevent the default event (prevent stop), and it will only listen to the mobile side, not the pc side

Better-scroll also dispatches a click event on the pc page, and a click event is also dispatched natively

// 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…}

Solution: For better-scroll events, there is _constructed: true, so do processing and return non-better-scroll events

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
    }

The goods component is almost here!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324375951&siteId=291194637