Skip to content

Instantly share code, notes, and snippets.

@supernovel
Created April 2, 2019 11:44
Show Gist options
  • Save supernovel/6eb86700f435a06ef6b1588933261437 to your computer and use it in GitHub Desktop.
Save supernovel/6eb86700f435a06ef6b1588933261437 to your computer and use it in GitHub Desktop.
vue-scroller.vue
<template lang="pug">
div(ref='container' :class='$style.container')
div(ref='content' :class='$style.content')
div(:class='$style.listWrapper')
ul(:class='$style.list')
slot(
name='item'
:class='$style.listItem'
@click='clickItem($event, item)'
v-for='item in items'
:item='item'
)
span {{ item }}
div(:class='$style.loadingLayer' v-if='pullUpLoad')
div(:class='$style.loadingBefore' v-if='isPullUpLoad')
slot(name='loading-before')
span {{ loadingText }}
div(:class='$style.loadingAfter' v-else)
slot(name='loading-after')
loading(:class='$style.loading')
div(ref='refresh' :class='$style.refreshLayer' v-if='pullDownRefresh')
div(:class='$style.refreshBefore' v-if='beforePullDown')
slot(name='refresh-before')
arrow(:class='$style.arrow' :style="{ 'transform': `rotate(${arrowDegree}deg)` }")
div(:class='$style.refreshAfter' v-else)
div(:class='$style.refreshLoading' v-if='isPullingDown')
slot(name='refresh-loading')
loading(:class='$style.loading')
div(:class='$style.refreshDone' v-else)
slot(name='refresh-done')
span {{ refreshText }}
</template>
<script>
import BScroll from 'better-scroll';
import Loading from './Loading.vue';
import Arrow from './Arrow.vue';
export default {
name: 'scroll',
components: {
loading: Loading,
arrow: Arrow
},
props: {
items: {
type: Array,
default: function() {
return [];
}
},
probeType: {
type: Number,
default: 1
},
click: {
type: Boolean,
default: true
},
scrollX: {
type: Boolean,
default: false //vertical || 'h' //horizontal
},
scrollY: {
type: Boolean,
default: true
},
pullDownRefresh: {
type: [Boolean, Object],
default: false
},
pullUpLoad: {
type: [Boolean, Object],
default: false
},
mouseWheel: {
type: [Boolean, Object],
default: true
},
stopPropagation: {
type: Boolean,
default: true
}
},
data() {
return {
beforePullDown: true,
isRebounding: false,
isPullingDown: false,
isPullUpLoad: false,
pullUpDirty: true,
pullDownStyle: '',
arrowDegree: 0
};
},
watch: {
items() {
setTimeout(() => {
this.forceUpdate(true);
}, this.refreshDelay);
}
},
computed: {
loadingText() {
const moreTxt =
(this.pullUpLoad &&
this.pullUpLoad.text &&
this.pullUpLoad.text.more) || 'More';
const noMoreTxt =
(this.pullUpLoad &&
this.pullUpLoad.text &&
this.pullUpLoad.text.noMore) || 'NoMore';
return this.pullUpDirty ? moreTxt : noMoreTxt;
},
refreshText() {
return (
(this.pullDownRefresh && this.pullDownRefresh.text) || 'Refresh'
);
}
},
created() {
this.pullDownInitTop = -50;
},
mounted() {
this.initScroll();
},
destroyed() {
this.scroll && this.scroll.destroy();
},
methods: {
initScroll() {
if (!this.$refs.container) {
return;
}
if (
this.$refs.content &&
(this.pullDownRefresh || this.pullUpLoad)
) {
this.$refs.content.style.minHeight = `${this.$refs.container.getBoundingClientRect()
.height +
1 +
1}px`;
}
let options = {
probeType: this.probeType,
click: this.click,
scrollY: this.scrollY,
scrollX: this.scrollX,
scrollbar: this.scrollbar,
pullDownRefresh: this.pullDownRefresh,
pullUpLoad: this.pullUpLoad,
mouseWheel: this.mouseWheel,
stopPropagation: this.stopPropagation
};
this.scroll = new BScroll(this.$refs.container, options);
if (this.pullDownRefresh) {
this._initPullDownRefresh();
}
if (this.pullUpLoad) {
this._initPullUpLoad();
}
},
disable() {
this.scroll && this.scroll.disable();
},
enable() {
this.scroll && this.scroll.enable();
},
refresh() {
this.scroll && this.scroll.refresh();
},
scrollTo() {
this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments);
},
autoPullDownRefresh() {
this.scroll && this.scroll.autoPullDownRefresh();
},
scrollToElement() {
this.scroll &&
this.scroll.scrollToElement.apply(this.scroll, arguments);
},
clickItem(e, item) {
this.$emit('click', item);
},
destroy() {
this.scroll.destroy();
},
forceUpdate(dirty) {
if (this.pullDownRefresh && this.isPullingDown) {
this.isPullingDown = false;
this._reboundPullDown().then(() => {
this._afterPullDown();
});
} else if (this.pullUpLoad && this.isPullUpLoad) {
this.isPullUpLoad = false;
this.scroll.finishPullUp();
this.pullUpDirty = dirty;
this.refresh();
} else {
this.refresh();
}
},
_initPullDownRefresh() {
const { threshold = 90, stop = 40 } = this.pullDownRefresh;
this.scroll.on('pullingDown', () => {
this.beforePullDown = false;
this.isPullingDown = true;
this.$emit('pullingDown');
});
this.scroll.on('scroll', pos => {
if (!this.pullDownRefresh) {
return;
}
if (this.beforePullDown) {
if(pos.y > threshold){
this.arrowDegree = 180;
}else{
this.arrowDegree = 0;
}
this.pullDownStyle = `top:${Math.min(
pos.y + this.pullDownInitTop,
10
)}px`;
}else{
this.arrowDegree = 0;
}
if (this.isRebounding) {
this.pullDownStyle = `top:${10 - (stop - pos.y)}px`;
}
});
},
_initPullUpLoad() {
this.scroll.on('pullingUp', () => {
this.isPullUpLoad = true;
this.$emit('pullingUp');
});
},
_reboundPullDown() {
const { stopTime = 600 } = this.pullDownRefresh;
return new Promise(resolve => {
setTimeout(() => {
this.isRebounding = true;
this.scroll.finishPullDown();
resolve();
}, stopTime);
});
},
_afterPullDown() {
setTimeout(() => {
this.pullDownStyle = `top:${this.pullDownInitTop}px`;
this.beforePullDown = true;
this.isRebounding = false;
this.refresh();
}, this.scroll.options.bounceTime);
}
}
};
</script>
<style lang="scss" module>
.container {
position: relative;
height: 100%;
overflow: hidden;
background: #fff;
.content {
position: relative;
z-index: 1;
}
.listWrapper {
position: relative;
z-index: 10;
background: #fff;
.list{
.listItem {
height: 60px;
line-height: 60px;
font-size: 18px;
padding-left: 20px;
border-bottom: 1px solid #e5e5e5;
}
}
}
}
.refreshLayer {
position: absolute;
width: 100%;
left: 0;
display: flex;
justify-content: center;
align-items: center;
.refreshBefore {
.arrow{
width: 20px;
height: 20px;
transition: transform .2s linear;
}
}
.refreshAfter {
margin-top: 10px;
.refreshLoading{
}
.refreshDone{
}
}
}
.loadingLayer {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 16px 0;
.loadingBefore{
}
.loadingAfter{
}
}
.loading{
width: 20px;
height: 20px;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment