better-scroll在vue中的应用
在我们日常的移动端项目开发中,处理滚动列表是再常见不过的需求了,以滴滴为例,可以是这样竖向滚动的列表,如图所示:
微信 —> 钱包—>滴滴出行”体验效果。
什么是 better-scroll better-scroll 是一个移动端滚动的解决方案,它是基于 iscroll 的重写,它和 iscroll 的主要区别在 这里 。better-scroll 也很强大,不仅可以做普通的滚动列表,还可以做轮播图、picker 等等。
不少同学可能用过 better-scroll,我收到反馈最多的问题是:
不能滚动是现象,我们得搞清楚这其中的根本原因。在这之前,我们先来看一下浏览器的滚动原理:
那么对于 better-scroll 也是一样的道理,我们先来看一下 better-scroll 常见的 html 结构:
<div class="wrapper">
<ul class="content">
<li>...</li>
<li>...</li>
...
</ul>
</div>
固定的高度 。黄色部分为 content,它是父容器的第一个子元素,它的高度会随着内容的大小而撑高。那么,当 content 的高度不超过父容器的高度,是不能滚动的,而它一旦超过了父容器的高度,我们就可以滚动内容区了,这就是 better-scroll 的滚动原理。
import BScroll from 'better-scroll' let wrapper = document.querySelector('.wrapper') let scroll = new BScroll(wrapper, {})
better-scroll 的文档 。
scroll.refresh() 方法重新计算来确保滚动效果的正常。所以同学们反馈的 better-scroll 不能滚动的原因 多半是初始化 better-scroll 的时机不对,或者是当 DOM 结构发送变化的时候并没有重新计算 better-scroll 。
Vue.js 都不陌生,当 better-scroll 遇见 Vue,会擦出怎样的火花呢?
很多同学开始接触使用 better-scroll 都是受到了我的一门教学课程—— 《Vue.js高仿饿了么外卖App》 的影响。在那门课程中,我们把 better-scroll 和 Vue 做了结合,实现了很多列表滚动的效果。在 Vue 中的使用方法如下:
<template>
<div class="wrapper" ref="wrapper">
<ul class="content">
<li>...</li>
<li>...</li>
...
</ul>
</div>
</template>
<script>
import BScroll from 'better-scroll'
export default {
mounted() {
this.$nextTick(() => {
this.scroll = new Bscroll(this.$refs.wrapper, {})
})
}
}
</script>
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),对用户体验而言都是无感知的。
在我们的实际工作中,列表的数据往往都是异步获取的,因此我们初始化 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>
axios 或者 vue-resource )。我们获取到数据的后,需要通过异步的方式再去初始化 better-scroll,因为 Vue 是数据驱动的, Vue 数据发生变化( this.data = res.data
)到页面重新渲染是一个异步的过程,我们的初始化时机是要在 DOM 重新渲染后,所以这里用到了 this.$nextTick
,当然替换成 setTimeout(fn, 20)
也是可以的。
数据改变 —> DOM 重新渲染仍然是一个异步过程 ,所以即使在我们拿到数据后,也要异步初始化 better-scroll。
我们在实际开发中,除了数据异步获取,还有一些场景可以动态更新列表中的数据,比如常见的下拉加载,上拉刷新等。比如我们用 better-scroll 配合 Vue 实现下拉加载功能,代码如下:
<template>
<div class="wrapper" ref="wrapper">
<ul class="content">
<li v-for="item in data">{{item}}</li>
</ul>
<div class="loading-wrapper"></div>
</div>
</template>
<script>
import BScroll from 'better-scroll'
export default {
data() {
return {
data: []
}
},
created() {
this.loadData()
},
methods: {
loadData() {
requestData().then((res) => {
this.data = res.data.concat(this.data)
this.$nextTick(() => {
if (!this.scroll) {
this.scroll = new Bscroll(this.$refs.wrapper, {})
this.scroll.on('touchend', (pos) => {
// 下拉动作
if (pos.y > 50) {
this.loadData()
}
})
} else {
this.scroll.refresh()
}
})
})
}
}
}
</script>
这段代码比之前稍微复杂一些, 当我们在滑动列表松开手指时候, better-scroll 会对外派发一个 touchend 事件,我们监听了这个事件,并且判断了 pos.y > 50(我们把这个行为定义成一次下拉的动作)。如果是下拉的话我们会重新请求数据,并且把新的数据和之前的 data 做一次 concat,也就更新了列表的数据,那么数据的改变就会映射到 DOM 的变化。需要注意的一点,这里我们对 this.scroll
做了判断,如果没有初始化过我们会通过 new BScroll
初始化,并且绑定一些事件,否则我们会调用 this.scroll.refresh
方法重新计算,来确保滚动效果的正常。
scroll 组件的抽象和封装 因此,我们有强烈的需求抽象出来一个 scroll 组件,类似小程序的 scroll-view 组件,方便开发者的使用。
<template> <div ref="wrapper"> <slot></slot> </div> </template>
<script type="text/ecmascript-6"> import BScroll from 'better-scroll' export default { props: { /** * 1 滚动的时候会派发scroll事件,会截流。 * 2 滚动的时候实时派发scroll事件,不会截流。 * 3 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件 */ probeType: { type: Number, default: 1 }, /** * 点击列表是否派发click事件 */ click: { type: Boolean, default: true }, /** * 是否开启横向滚动 */ scrollX: { type: Boolean, default: false }, /** * 是否派发滚动事件 */ listenScroll: { type: Boolean, default: false }, /** * 列表的数据 */ data: { type: Array, default: null }, /** * 是否派发滚动到底部的事件,用于上拉加载 */ pullup: { type: Boolean, default: false }, /** * 是否派发顶部下拉的事件,用于下拉刷新 */ pulldown: { type: Boolean, default: false }, /** * 是否派发列表滚动开始的事件 */ beforeScroll: { type: Boolean, default: false }, /** * 当数据更新后,刷新scroll的延时。 */ refreshDelay: { type: Number, default: 20 } }, mounted() { // 保证在DOM渲染完毕后初始化better-scroll setTimeout(() => { this._initScroll() }, 20) }, methods: { _initScroll() { if (!this.$refs.wrapper) { return } // better-scroll的初始化 this.scroll = new BScroll(this.$refs.wrapper, { probeType: this.probeType, click: this.click, scrollX: this.scrollX }) // 是否派发滚动事件 if (this.listenScroll) { let me = this this.scroll.on('scroll', (pos) => { me.$emit('scroll', pos) }) } // 是否派发滚动到底部事件,用于上拉加载 if (this.pullup) { this.scroll.on('scrollEnd', () => { // 滚动到底部 if (this.scroll.y <= (this.scroll.maxScrollY + 50)) { this.$emit('scrollToEnd') } }) } // 是否派发顶部下拉事件,用于下拉刷新 if (this.pulldown) { this.scroll.on('touchend', (pos) => { // 下拉动作 if (pos.y > 50) { this.$emit('pulldown') } }) } // 是否派发列表滚动开始的事件 if (this.beforeScroll) { this.scroll.on('beforeScrollStart', () => { this.$emit('beforeScroll') }) } }, disable() { // 代理better-scroll的disable方法 this.scroll && this.scroll.disable() }, enable() { // 代理better-scroll的enable方法 this.scroll && this.scroll.enable() }, refresh() { // 代理better-scroll的refresh方法 this.scroll && this.scroll.refresh() }, scrollTo() { // 代理better-scroll的scrollTo方法 this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments) }, scrollToElement() { // 代理better-scroll的scrollToElement方法 this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments) } }, watch: { // 监听数据的变化,延时refreshDelay时间后调用refresh方法重新计算,保证滚动效果正常 data() { setTimeout(() => { this.refresh() }, this.refreshDelay) } } } </script>
有了这一层 scroll 组件的封装,我们来修改刚刚最复杂的代码(假设我们已经全局注册了 scroll 组件)。
<template>
<scroll class="wrapper"
:data="data"
:pulldown="pulldown"
@pulldown="loadData">
<ul class="content">
<li v-for="item in data">{{item}}</li>
</ul>
<div class="loading-wrapper"></div>
</scroll>
</template>
<script>
import BScroll from 'better-scroll'
export default {
data() {
return {
data: [],
pulldown: true
}
},
created() {
this.loadData()
},
methods: {
loadData() {
requestData().then((res) => {
this.data = res.data.concat(this.data)
})
}
}
}
</script>
插件 Vue 化引发的一些思考 这篇文章我不仅仅是要教会大家封装一个 scroll 组件,还想传递一些把第三方插件(原生 JS 实现)Vue 化的思考过程。很多学习 Vue.js 的同学可能还停留在 “XX 效果如何用 Vue.js 实现” 的程度,其实把插件 Vue 化有两点很关键,一个是对插件本身的实现原理很了解,另一个是对 Vue.js 的特性很了解。对插件本身的实现原理了解需要的是一个思考和钻研的过程,这个过程可能困难,但是收获也是巨大的;而对 Vue.js 的特性的了解,是需要大家对 Vue.js 多多使用,学会从平时的项目中积累和总结,也要善于查阅 Vue.js 的官方文档,关注一些 Vue.js 的升级等。
所以,我们拒绝伸手党,但也不是鼓励大家什么时候都要去造轮子,当我们在使用一些现成插件的同时,也希望大家能多多思考,去探索一下现象背后的本质,把 “XX 效果如何用 Vue.js 实现” 这句话从问号变成句号。
better-scroll在vue中的应用的更多相关文章
- vue 中监测滚动条加载数据(懒加载数据)
vue 中监测滚动条加载数据(懒加载数据) 1:钩子函数监听滚动事件: mounted () { this.$nextTick(function () { window.addEventListene ...
- 基于iscroll的better-scroll在vue中的使用
什么是 better-scroll better-scroll 是一个移动端滚动的解决方案,它是基于 iscroll 的重写,它和 iscroll 的主要区别在这里.better-scroll 也很强 ...
- better-scroll在vue中的坑
在我们日常的移动端项目开发中,处理滚动列表是再常见不过的需求了,以滴滴为例,可以是这样竖向滚动的列表,如图所示: 也可以是横向滚动的导航栏,如图所示: 可以打开“微信 —> 钱包—>滴滴出 ...
- 移动端固定头部和固定左边第一列的实现方案(Vue中实现demo)
最近移动端做一份报表,需要左右滚动时,固定左边部分:上下滚动时,固定头部部分. 代码在Vue中简单实现 主要思路是: a.左边部分滚动,实时修改右边部分的滚动条高度 b.头部和内容部分都设置固定高度, ...
- 在vue中无论使用router-link 还是 @click事件,发现都没法从列表页点击跳转到内容页去
在vue中如论使用router-link 还是 @click事件,发现都没法从列表页点击跳转到内容页去,以前都是可以的,想着唯一不同的场景就是因为运用了scroll组件(https://ustbhua ...
- vue中回到顶部
1. 回到顶部,使用 scrollIntoView 方法: Element.scrollIntoView方法滚动当前元素,进入浏览器的可见区域 该方法可以接受一个布尔值作为参数.如果为true,表示元 ...
- vue中滚动页面,改变样式&&导航栏滚动时,样式透明度修改
vue中滚动页面,改变样式&&导航栏滚动时,样式透明度修改.vue <div class="commonHeader" v-bind:class=" ...
- 函数防抖节流的理解及在Vue中的应用
防抖和节流的目的都是为了减少不必要的计算,不浪费资源,只在适合的时候再进行触发计算. 一.函数防抖 定义 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时:典型的案例就是输入搜索:输入 ...
- Vue中的better-scroll插件
Vue中的better-scroll插件 在需要的文件中添加 import BScorll from 'better-scroll'; 引用的示例代码: let scroll = new BScrol ...
随机推荐
- onload函数和自执行函数的区别(jquery API网址:http://jquery.cuishifeng.cn/)
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- Logging模块总结 2018/5/30
日志的级别 Level 用处 数字级别 DEBUG 详细的信息,在调试过程中用于诊断错误 10 INFO 用于确认事件正在运行 20 WARNING 意外发生时予以提醒,或者预测一些未来可能发生的一些 ...
- HTML5框架、背景和实体
-----------------siwuxie095 HTML5 框架 1.框架标签 <frame> ...
- Opencv3 形态学操作
#include <iostream>#include <opencv2/opencv.hpp> using namespace std;using namespace cv; ...
- Spring MVC拦截器(Interceptor )详解
处理器拦截器简介 Spring Web MVC的处理器拦截器(如无特殊说明,下文所说的拦截器即处理器拦截器)类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理. 常见应用场 ...
- Linux arp命令
一.简介 arp命令用于操作主机的arp缓冲区,可以用来显示arp缓冲区中的所有条目.删除指定的条目或者添加静态的ip地址与MAC地址对应关系. 二.语法 -a<主机>:显示arp缓冲区的 ...
- win7 + eclipse + cocos2dx 开发环境配置
最近想在win7上配置eclipse+cocos2dx开发环境,在安装之前一定要注意每项是32位还是64位,我选择的都是64位版本的,闲话少叙我们开始安装吧! 1.下载cocos2dx,我选择的是co ...
- Django中的元类-乾颐堂
看Django(1.6)的Form相关源代码时比较迷惑,于是节选了django.forms.forms.py中的几个代码片段来分析Django中是怎么使用元类的: 1 2 3 4 5 6 7 8 9 ...
- Flip
Flip是一个能够让任意HTML.文本或jQuery Element产生漂亮翻转效果的jQuery插件. 可以配置翻转方向:从右到左.上到下或从左到右.下到上.翻转的速度也可以配置. 效果如下图所示: ...
- Docker for mac安装
Mac安装Docker docker下载地址: https://hub.docker.com/editions/community/docker-ce-desktop-mac docker for m ...