// 指定为 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 }