一、关于iscroll
1、安装iscroll :npm install iscroll@5.2.0
!important:
首先注意iscroll内每一个滚动元素都需要设定高度,不能设定滚动元素的宽度使高度自适应,否则iscroll会无法滚动到底部,底部总有一部分无法显示。
其次注意iscroll要滚动,容器的高度必须要小于内容的高度,在引入iscroll滚动组件时,父组件必须在iscroll滚动组件外套一层容器,我一般是使用div内嵌套scrollView组件。div属性设置为:
div{ // 容器的高度固定。
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
}
在div属性设置完毕,注意在iscroll滚动组件中,设置iscroll组件宽高同容器一致。
2、封装滚动组件
<template>
// 必要的三层结构的其中两层,另一层在父组件中
<div id="wrapper" ref="wrapper">
<slot></slot>
</div>
</template>
<script>
import IScroll from 'iscroll/build/iscroll-probe'
export default {
name: 'ScrollView', // iscroll滚动组件命名为ScrollView
mounted() {
this.iscroll = new IScroll(this.$refs.wrapper, {
onmousewheel: true, // 禁止鼠标滚动
scrollbars: false, // 是否需要显示滚动条
probeType: 3, // 监听iscroll滚动的灵敏程度,3是最灵敏的。
// 解决卡顿拖拽问题
scrollX: false,
ScrollY: true,
disablePointer: true,
disableTouch: false,
disableMouse: true,
// 另外想进一步解决卡顿拖拽,在全局样式中的 html,body{} 中添加 touch-action:none;
click: true // iscroll滚动之后可以对元素绑定click事件
})
// 1.创建一个观察者对象
/*
MutationObserver构造函数只要监听到了指定内容发生变化,就会执行传入的回调函数
!注意: MutationObserver是微任务
mutationList:发生变化的数组
observer:观察者对象
*/
const observer = new MutationObserver((mutationList, observer) => {
// console.log(mutationList)
// console.log(this.iscroll.maxScrollY)//maxScrollY即滚动范围
this.iscroll.refresh()
// console.log(this.iscroll.maxScrollY)
})
// 2.告诉观察者对象我们需要观察什么内容
const config = {
childList: true, // 观察目标子节点的变化,添加或者删除
subtree: true, // 默认为false,设置为true可以观察后代节点
attributeFilter: ['height', 'offsetHeight'] // 观察特定属性
}
// 3.告诉观察者对象,我们需要观察谁,需要观察什么内容
/*
第一个参数:告诉观察者对象我们需要观察谁
第二个参数:告诉观察者对象我们需要观察什么内容
*/
observer.observe(this.$refs.wrapper, config)
},
methods: {
// 提供一个监听滚动距离的方法给外界使用
scrolling(fn) {
this.iscroll.on('scroll', function() {
fn(this.y)
})
},
refresh() {
setTimeout(() => {
this.iscroll.refresh()
}, 100)
},
scrollTo(x, y, time) {
this.iscroll.scrollTo(x, y, time)
}
}
}
</script>
<style lang="scss" scoped>
#wrapper { // 设置iscroll组件的宽高同容器一致。
width: 100%;
height: 100%;
}
</style>
二、根据iscroll实现下拉刷新、下拉加载更多效果
<template>
<div id="wrapper" ref="wrapper">
<div
class="scroll-wrapper"
ref="scrollWrapper"
:class="{ transition: isTransition }"
>
// 下拉刷新
<div class="pull-down-box" v-if="this.value && this.isPullDownShow">
<div class="pull-down">
<img v-lazy="isShrinked ? refreshImg : loadImg" alt="" />
</div>
</div>
<div class="content">
<slot name="content"></slot>
</div>
// 上拉加载更多效果
<div class="pull-up-box" v-if="this.value && this.isPullUpShow">
<div class="pull-up">
<img v-lazy="loadImg" />
</div>
</div>
</div>
</div>
</template>
<script>
import IScroll from 'iscroll/build/iscroll-probe'
export default {
name: 'ScrollView',
props: {
value: {
type: Boolean,
default: false,
required: false
}
},
methods: {
refresh () {
setTimeout(() => {
this.iscroll.refresh()
}, 100)
},
scrolling (fn) {
this.iscroll.on('scroll', function () {
fn(this.y)
})
},
scrollTo (x, y, time) {
this.iscroll.scrollTo(x, y, time)
}, // 最好不要刚给scrollTo传递时间,可能是因为scrollTo内部是setInternal?传递时间后重新滚动有bug
scrollEnd (fn) {
this.iscroll.on('scrollEnd', function () {
fn()
})
}
},
data () {
return {
isPullDownShow: false,
isPullUpShow: false,
pageHeight: 0, // 可视高度
contentHeight: 0, // 页面元素高度
isShrinked: true, // 选择下移标识图片
loadImg:
'https://img.lanrentuku.com/img/allimg/1212/5-121204193R5-50.gif',
refreshImg:
'https://s.cn.bing.net/th?id=OIP.fDBrKAm7YOFweVMG2A0VqgAAAA&w=183&h=183&c=7&qlt=90&bgcl=ececec&o=6&pid=PersonalBing',
isTransition: false,
isPullDownData: false, // 下拉是否达到进行刷新数据的状态
isPullUpData: false, // 上拉是否达到加载数据的状态
isBackTop: true // 当上拉加载数据之后是否需要返回顶部
}
},
mounted () {
this.iscroll = new IScroll(this.$refs.wrapper, {
onmousewheel: true,
scrollbars: false,
probeType: 3,
scrollX: false,
ScrollY: true,
disablePointer: true,
disableTouch: false,
disableMouse: true,
click: true
})
const scrollWrapper = document.querySelector('.scroll-wrapper')
const contentWrapper = document.querySelector('.content')
// 1.创建一个观察者对象
const observer = new MutationObserver((mutationList, observer) => {
this.iscroll.refresh()
scrollWrapper.style.marginTop = '0px'
// console.log(
// 'MutationObserver中',
// this.value,
// '父组件v-model给子组件的值为true,同时在异步async方法和同步方法中设置v-model的数据的值为false,在MutationObserver中为false,但是且无论是同步方法还是异步async方法设置的false,而MutationObserver外都是最开始传递的v-model值true?'
// 解答:关于在MutationObserver中为false,原因是MutationObsernver 是微任务。但是疑问还是??...继续学习...猜想和v-model有关。
// )
// 获取实际高度(包括滚动距离)
// 必须在此处获取:因为MutationObserver中时时监听节点变化,在MutationObserver外获取是固定值,则会无限执行下列if条件内的事件
this.contentHeight = scrollWrapper.offsetHeight
if (this.isPullUpShow) {
if (this.contentHeight < this.pageHeight) {
// if (scrollWrapper.offsetHeight < this.pageHeight) {
// 页面元素不铺满一页
this.$emit('load')
// console.log(this.contentHeight, this.pageHeight)
}
}
if (scrollWrapper.offsetHeight < 1) {
console.log('页面无任何元素')
}
})
// 2.告诉观察者对象我们需要观察什么内容
const config = {
childList: true,
subtree: true,
attributeFilter: ['height', 'offsetHeight']
}
// 3.告诉观察者对象,我们需要观察谁,观察什么对象
observer.observe(this.$refs.wrapper, config)
// console.log('MutationObserver外', this.value) // true
// this.value为true表示是加载模式,即排除之前设置的三联选择器模式
if (this.value) {
// 获得页面的可视高度
const pageDom = document.querySelector('#wrapper')
this.pageHeight = Math.ceil(pageDom.getBoundingClientRect().height)
const that = this
this.iscroll.on('scroll', function () {
// 下拉刷新和上拉加载操作
if (this.y > 200) {
// 关于下拉刷新
if (that.isPullDownShow) {
scrollWrapper.style.marginTop = this.y + 'px'
that.isShrinked = false
// 获取下拉更新数据
that.isPullDownData = true
}
} else if (this.y < 0) {
// 上拉加载模式
if (that.isPullUpShow) {
scrollWrapper.style.marginTop = 0 + 'px'
that.isShrinked = true
that.isTransition = true
if (Math.abs(this.y) + that.pageHeight > that.contentHeight) {
// 到达滚动条底部,获取上拉加载数据
that.isPullUpData = true
// console.log(that.isPullUpShow)
}
}
}
})
this.iscroll.on('scrollEnd', function () {
if (that.isPullDownData) {
// console.log('scrollEnd下拉刷新', that.isPullDownData)
// 执行下拉刷新操作
that.$emit('refresh')
// 执行完毕下拉刷新操作之后,恢复原先状态
that.isPullDownData = false
} else if (that.isPullUpData) {
// console.log('scrollEnd上拉加载', that.isPullUpData)
// 执行上拉加载操作
that.$emit('load')
// 加载完成之后附加的说明段落语句
const contentOver = document.createElement('p')
contentOver.innerHTML = '加载完成'
contentOver.style.fontSize = 'larger'
contentOver.style.textAlign = 'center'
contentOver.style.color = 'black'
contentWrapper.appendChild(contentOver)
setTimeout(function () {
contentWrapper.removeChild(contentOver)
}, 1000)
// 执行完毕上拉加载操作之后,恢复原先状态
that.isPullUpData = false
}
})
}
},
created () {
if (this.$listeners.load) {
this.isPullUpShow = true
this.$emit('load')
}
if (this.$listeners.refresh) {
this.isPullDownShow = true
}
}
}
</script>
<style lang="less" scoped>
#wrapper {
width: 100%;
height: 100%;
.scroll-wrapper {
width: 100%;
position: relative;
&.transition {
transition: top 0.5s ease-in 0s;
}
.content {
&::after {
content: '-';
display: inline-block;
color: #fff;
width: 100%;
height: 30px;
}
}
.pull-down,
.pull-up {
position: absolute;
margin-left: 50%;
transform: translateX(-50%);
top: -35px;
img {
width: 20px;
height: 20px;
}
}
.pull-up {
width: 100px;
top: unset;
bottom: 3px;
text-align: center;
}
.textOver {
background: red;
position: absolute;
left: 0;
bottom: 0;
}
}
}
</style>
// 要点一:使用该组件的下拉刷新上拉加载功能,必须传入一个model,即该组件会询问想要使用该功能的用户:是否是上拉加载下拉刷新模式,这时应该传入是的答案。
即在父组件中给子组件传入 v-model='参数',而参数在父组件中答案为“是”,子组件中的props用value接收
// 要点二:使用该组件的父组件给该组件传递上拉加载办法=> load,以及下拉刷新方法=> refresh 。
// 未解决的问题,若前文this.iscroll.scrollTo(0,0)执行,当上拉数据加载之后iscroll会滚回顶部,而该功能不需要滚回顶部
// 注意iscroll中的是具名插槽。
// 关于MutationObserver是微任务,需要进一步了解js的执行机制。
因为本人菜,本文有很多不足,如果有大佬指出,非常感谢。