当前位置: 移动技术网 > IT编程>脚本编程>vue.js > Vue框架之goods组件开发详解

Vue框架之goods组件开发详解

2018年01月30日  | 移动技术网IT编程  | 我要评论
一、 布局 flex flex 布局,可以简便、完整、响应式地实现各种页面布局,flex 是 flexible box 的缩写,意为"弹性布局",用来为盒状模型提供最大的

一、 布局 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

二、图标组件

子组件 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']
 }
}

父组件 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)

参考:

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 组件到此差不多了!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网