better-scroll

来源:互联网 发布:防辐射服 知乎 编辑:程序博客网 时间:2024/05/20 10:14

模仿了一个饿了吗页面先上图,看一看better-scroll的效果

better-scroll

如果用overflow:auto或scroll属性,这样的页面就会出现两个滚动条,用户体验会有折扣,所以给介绍一款插件better-scroll,附上github资源链接。这款插件是基于iscroll插件做的重新封装,改善了一些bug,增加了一些拓展功能(插件作者并不是本人,望周知),有兴趣的小伙伴们可以自行下载体验体验。那么今天就用bs插件配合vue框架对这个页面重构一下,主要实现以下几点功能。

- 1、左右侧页面滑动,并且不显示滚动条。 
- 2、根据左侧的商品类别对应到右侧相应的选择区间,并且高亮标题。 
- 3、根据右侧用户滑动的区间,能够对应到左侧的商品类别,并且高亮选择标题。 
ps:除了用到bs插件的一些基础功能以外,还有一些vue的基础知识。

前期准备

  • 我引入了reset.css对页面的样式做一个算是初始化吧,然后引入js脚本有vue.js,vue-resource.js,bscroll.js,可以进入vue官网下载。

页面布局

页面布局和样式就不浪费时间了,直接上代码了

样式代码
[v-cloak] {            display: none;        }        .goods {            position: absolute;            width: 100%;            top: 174px;            bottom: 46px;            display: flex;            overflow: hidden;        }        .goods .menu-wrapper {            flex: 0 0 80px;            width: 80px;            background: #f3f5f7;        }        .goods .menu-wrapper .current {            position: relative;            z-index: 10;            margin-top: -1px;            background: #FFFFFF;            font-weight: 700;            font-size: 14px;        }        .goods .menu-wrapper .menu-item {            display: table;            height: 54px;            width: 80px;            line-height: 14px;            padding: 0 12px;            border-bottom: 1px solid rgba(7, 17, 27, .1);            box-sizing: border-box;        }        .goods .menu-wrapper .menu-item .icon {            display: inline-block;            width: 12px;            height: 12px;            margin-right: 2px;            -webkit-background-size: 12px 12px;            background-size: 12px 12px;            background-repeat: no-repeat;            vertical-align: top;        }        .goods .menu-wrapper .menu-item .text {            display: table-cell;            width: 56px;            vertical-align: middle;            font-size: 12px;            text-align: center;        }        .goods .menu-wrapper .menu-item .decrease {            background-image: url(img/decrease_2@2x.png);        }        .goods .menu-wrapper .menu-item .discount {            background-image: url(img/decrease_2@2x.png);        }        .goods .menu-wrapper .menu-item .guarantee {            background-image: url(img/decrease_2@2x.png);        }        .goods .menu-wrapper .menu-item .invoice {            background-image: url(img/decrease_2@2x.png);        }        .goods .menu-wrapper .menu-item .special {            background-image: url(img/decrease_2@2x.png);        }        .goods .foods-wrapper {            flex: 1;        }        .goods .foods-wrapper .title {            padding-left: 14px;            height: 26px;            line-height: 26px;            border-left: 2px solid #d9dde1;            font-size: 12px;            color: rgb(147, 153, 159);            background: #F3F5F7;        }        .goods .foods-wrapper .current {            color: #42B983;            font-size: 14px;            transition: all .5s;            line-height: 27px;        }        .goods .foods-wrapper .food-item {            display: flex;            margin: 18px 0 18px 0;            border-bottom: 1px solid rgba(7, 17, 27, .1);            padding-bottom: 18px;        }        .goods .foods-wrapper .food-item:last-child {            border-bottom: 0px solid rgba(7, 17, 27, .1);            margin-bottom: 0;        }        .goods .foods-wrapper .food-item .icon {            flex: 0 0 57px;            margin-right: 10px;            margin-left: 10px;        }        .goods .foods-wrapper .food-item .content {            position: relative;            flex: 1;        }        .goods .foods-wrapper .food-item .content .name {            margin: 2px 0 8px 0;            height: 14px;            line-height: 14px;            font-size: 14px;            color: rgb(7, 17, 27);        }        .goods .foods-wrapper .food-item .content .desc {            margin-bottom: 8px;            line-height: 10px;            font-size: 10px;            color: rgb(147, 153, 159);        }        .goods .foods-wrapper .food-item .content .extra {            font-size: 10px;            color: rgb(147, 153, 159);            line-height: 10px;        }        .goods .foods-wrapper .food-item .content .extra .count {            margin-right: 12px;        }        .goods .foods-wrapper .food-item .content .price {            font-weight: 700;            line-height: 24px;        }        .goods .foods-wrapper .food-item .content .price .now {            margin-right: 8px;            font-size: 14px;            color: rgb(240, 20, 20);        }        .goods .foods-wrapper .food-item .content .price .old {            text-decoration: line-through;            font-size: 10px;            color: rgb(147, 153, 159);        }        .goods .foods-wrapper .food-item .content .cartcontrol-wrapper {            position: absolute;            right: 6px;            bottom: 12px;        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
页面布局代码
<div class="goods" v-cloak>            <div class="menu-wrapper" ref="menuwrapper">                <ul>                    <!--当currentIndex与index相等的时候,设置高亮-->                    <li v-for="(item,index) in goods" class="menu-item" :class="{'current':currentIndex === index}" @click="selectMenu(index,$event)" v-cloak>                        <span class="text">                                <span v-show="item.type>0" class="icon" :class="classMap[item.type]" v-cloak></span> {{item.name}}                        </span>                    </li>                </ul>            </div>            <div class="foods-wrapper" ref="foodwrapper">                <ul>                    <!--food-list-hook用于dom操作,获取整体容器的高度-->                    <li v-for="(item,index) in goods" class="food-list food-list-hook" v-cloak>                        <h2 class="title" :class="{'current':currentIndex === index}">{{item.name}}</h2>                        <ul>                            <li @click="selectfood(food,$event)" v-for="food in item.foods" class="food-item">                                <div class="icon">                                    <img :src="food.icon" />                                </div>                                <div class="content">                                    <h2 class="name">{{food.name}}</h2>                                    <p class="desc">{{food.description}}</p>                                    <div class="extra">                                        <span class="count">月售{{food.sellCount}}</span>                                        <span>好评率{{food.rating}}%</span>                                    </div>                                    <div class="price">                                        <span class="now">{{food.price}}</span>                                        <span v-show="food.oldPrice" class="old">{{food.oldPrice}}</span>                                    </div>                                </div>                            </li>                        </ul>                    </li>                </ul>            </div>        </div>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

js代码

  • 首先在data内定义几个变量
goods:[],  listHeight:[],  scrollY:0,
  • 1
  • 2
  • 3
  • 在created中请求准备好的json数据
this.$http.get('./data.json').then((res) => {                    if(res.status === ERR_OK) {                        res = res.body.goods;                        this.goods = res;                    }                })
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 首先我们已经引入了bs插件,我们先让左右两侧的被隐藏的部分滚动起来,在methods方法里面定义一个_initScroll的函数,主要用来对左右两侧dom结构进行初始化。用better-scroll的方法初始化需要滚动的dom结构,vue为我们提供了一个方法可以便利的获取到dom结构,我们在需要获取dom结构的父容器内添加ref="foodwrapper" ,然后在函数内用this.$refs.menuwrapper获取到dom。
  • 然后在ajax内执行_initScroll() 函数,这个时候需要注意两点,第一使用bs插件的时候子容器的高度一定要大于父容器的高度,才会产生滚动效果。第二,我们要等dom结构完全加载结束在调用_initScroll()方法才会生效,vue的作者也为我们提供了方法,来判断dom结构是否完全加载this.$nextTick(() => {})click: true属性用来设置可以进行点击事件。
  _initScroll() {                    this.meunScroll = new BScroll(this.$refs.menuwrapper, {                        click: true                    });                    this.foodScroll = new BScroll(this.$refs.foodwrapper, {                        click: true                    });                }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 这时候created应改为
created() {                this.classMap = ['decrease', 'discount', 'guarantee', 'invoice', 'special'];                this.$http.get('./data.json').then((res) => {                    if(res.status === ERR_OK) {                        res = res.body.goods;                        this.goods = res;                        //dom结构加载结束                        this.$nextTick(() => {                            this._initScroll();                        })                    }                });            },  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 下面实现左右联动并且实现文本的高亮,左右联动的基本原理其实我们计算出右侧实时变化的y值,落到哪一个区间,我们就显示那一个区间。首先我们要计算整体区间的一个高度,然后分别计算第一个区间的高度,第二个区间的高度,以此类推。然后将区间数存入一个定义好的数组。当我们在滚动的时候实时拿到y轴的高度,然后对比在哪一个区间,这样我们就会得到一个区间的索引值去对应左侧的菜品类别,最后我们用一个vue的class去绑定高亮文本。
  • 定义一个方法在_initScroll下面,作为计算高度的方法叫做_calculateHeight () ,在定义一个listHeight:[]数组,存放获取的高度。我们在定义一个food-list-hook类,用来被js选择。不要忘记在created内调用函数。
_calculateHeight () {        let foodList = this.$refs.foodwrapper.getElementsByClassName('food-list-hook');        let height = 0;        //把第一个高度送入数组        this.listHeight.push(height);        //通过循环foodList下的dom结构,将每一个li的高度依次送入数组        for(let i=0; i<foodList.length; i++){             let item = foodList[i]                height += item.clientHeight            this.listHeight.push(height);        }    },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 我们获取到区间高度数组后,我们要实时获取到右侧的y值,和左侧的索引值做一个对比,定义一个scrollY变量用来存放实时获取的y值。bs插件为我们提供了一个实时获取y值的方法,我们在初始化this.foodScroll的时候加一个·属性probeType: 3,其作用就是实时获取y值,相当于探针的作用。
  • 我们在添加一个方法this.foodScroll.on('scroll',(pos) => {}),作用是实时滚动的时候把获取到的位置给暴露出来。代码如下。
methods: {                _initScroll() {                    this.meunScroll = new BScroll(this.$refs.menuwrapper, {                        click: true                    });                    this.foodScroll = new BScroll(this.$refs.foodwrapper, {                        click: true,                        //探针作用,实时监测滚动位置                        probeType: 3                    });                    //设置监听滚动位置                    this.foodScroll.on('scroll', (pos) => {                        //scrollY接收变量                        this.scrollY = Math.abs(Math.round(pos.y));                    })                },                _calculateHeight() {                    let foodList = this.$refs.foodwrapper.getElementsByClassName('food-list-hook');                    let height = 0;                    //把第一个高度送入数组                    this.listHeight.push(height);                    //通过循环foodList下的dom结构,将每一个li的高度依次送入数组                    for(let i = 0; i < foodList.length; i++) {                        let item = foodList[i]                        height += item.clientHeight                        this.listHeight.push(height);                    }                },            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 定义一个计算属性computed,用来计算左侧对应的i值,从而定位到左侧边栏的位置
computed:{    currentIndex () {        for(let i=0; i<this.listHeight.length; i++){            //判断当currentIndex在height1和height2之间的时候显示            let height1 = this.listHeight[i];            let height2 = this.listHeight[i+1];            //最后一个区间没有height2            if(!height2 || (this.scrollY >= height1 && this.scrollY < height2)){                return i;            }        }        return 0;    },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 获取到i后,在menu-item绑定一个class:class="{'current':currentIndex === index}",当currentIndex和menu-item对应的index相等时,设置current的样式。这样就可以左右联动了。
  • 最后实现左侧点击的功能。在左侧的li下绑定一个selectMenu的点击事件,并传入索引值,这样我们就可以知道点击的是哪一个li
selectMenu (index,event) {//      自己默认派发事件时候(BScroll),_constructed被置为true,但是浏览器原生并没有这个属性        if (!event._constructed){            return;        }        //运用BScroll接口,滚动到相应位置        let foodList = this.$refs.foodwrapper.getElementsByClassName('food-list-hook');        //获取对应元素的列表        let el = foodList[index];              //设置滚动时间        this.foodScroll.scrollToElement(el, 300);    },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 至此,我们就用bs插件完成了这个左右页面联动的效果,代码会上传到github,有兴趣的可以下载下来看一看,大神务喷!
  • 完整的js代码
<script type="text/javascript">        var ERR_OK = 200;        new Vue({            el: '.goods',            data() {                return {                    msg: 'goods',                    goods: [],                    listHeight: [],                    scrollY: 0,                }            },            created() {                this.classMap = ['decrease', 'discount', 'guarantee', 'invoice', 'special'];                this.$http.get('./data.json').then((res) => {                    if(res.status === ERR_OK) {                        res = res.body.goods;                        this.goods = res;                        //dom结构加载结束                        this.$nextTick(() => {                            this._initScroll();                            //计算高度                            this._calculateHeight();                        })                    }                });            },            computed: {                currentIndex() {                    for(let i = 0; i < this.listHeight.length; i++) {                        //判断当currentIndexheight1height2之间的时候显示                        let height1 = this.listHeight[i];                        let height2 = this.listHeight[i + 1];                        //          console.log('height1:'+height1+','+'height2:'+height2)                        //最后一个区间没有height2                        if(!height2 || (this.scrollY >= height1 && this.scrollY < height2)) {                            return i;                        }                    }                    return 0;                }            },            methods: {                selectMenu(index, event) {                    //      自己默认派发事件时候(BScroll),_constructed被置为true,但是浏览器原生并没有这个属性                    if(!event._constructed) {                        return;                    }                    //运用BScroll接口,滚动到相应位置                    let foodList = this.$refs.foodwrapper.getElementsByClassName('food-list-hook');                    //获取对应元素的列表                    let el = foodList[index];                    this.foodScroll.scrollToElement(el, 300);                },                _initScroll() {                    this.meunScroll = new BScroll(this.$refs.menuwrapper, {                        click: true                    });                    this.foodScroll = new BScroll(this.$refs.foodwrapper, {                        click: true,                        //探针作用,实时监测滚动位置                        probeType: 3                    });                    //设置监听滚动位置                    this.foodScroll.on('scroll', (pos) => {                        //scrollY接收变量                        this.scrollY = Math.abs(Math.round(pos.y));                    })                },                _calculateHeight() {                    let foodList = this.$refs.foodwrapper.getElementsByClassName('food-list-hook');                    let height = 0;                    //把第一个高度送入数组                    this.listHeight.push(height);                    //通过循环foodList下的dom结构,将每一个li的高度依次送入数组                    for(let i = 0; i < foodList.length; i++) {                        let item = foodList[i]                        height += item.clientHeight                        this.listHeight.push(height);                    }                },            }        })    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
完整项目效果演示

better-scroll

原创粉丝点击