Skip to content

Instantly share code, notes, and snippets.

@supernovel
Last active September 21, 2019 13:56
Show Gist options
  • Save supernovel/09efd2e35dd431f26bc17da62e268506 to your computer and use it in GitHub Desktop.
Save supernovel/09efd2e35dd431f26bc17da62e268506 to your computer and use it in GitHub Desktop.
Vue Infinity Scroll
<template lang="pug">
.dataLoader
.loadingHeader(:style="{ height: pullHeight + 'px' }" v-show="PULL_DOWN === pull.type")
.headerText(v-show="!pull.available")
slot(:name="PULL_DOWN + '-before'"): span.defaultText Pull down
.headerText(v-show="pull.available & PULL_DOWN !== loadingType")
slot(:name="PULL_DOWN + '-ready'"): span.defaultText Pull down ready
.headerText(v-show="PULL_DOWN === loadingType")
slot(:name="PULL_DOWN + '-loading'"): span.defaultText Loading
.loadingContent
slot
.loadingFooter(:style="{height: pullHeight + 'px'}" v-show="PULL_UP === pull.type")
.footerText(v-show="!pull.available")
slot(:name="PULL_UP + '-before'"): span.defaultText Pull up
.footerText(v-show="pull.available & PULL_UP !== loadingType")
slot(:name="PULL_UP + '-ready'"): span.defaultText Pull up ready
.footerText(v-show="PULL_UP === loadingType")
slot(:name="PULL_UP + '-loading'"): span.defaultText Loading
.loadingFooter(:style="{height: distance + 'px'}" v-show="loading && INFINITE_SCROLL === loadingType")
.footerText(v-show="!pull.available")
slot(:name="INFINITE_SCROLL + '-loading'"): span.defaultText Loading
.loadingFooter(:style='{height: distance + "px"}' v-show="!loading && completed")
.footerText
slot(name="completed"): span.defaultText End of Content
</template>
<script>
export default {
props: {
loading: {
type: Boolean,
default: false,
},
completed: {
type: Boolean,
default: false,
},
distance: {
type: Number,
default: 60,
},
offset: {
type: Number,
default: 0,
},
listens: {
type: Array,
default() {
return ['infinite-scroll', 'pull-down', 'pull-up'];
},
},
container: {
type: String,
},
initScroll: {
type: Boolean,
default: false,
},
},
data() {
return {
margin: {
top: 0,
bottom: 0,
},
pull: {
from: -1,
to: -1,
distance: 0,
type: null,
available: false,
},
loadingType: null,
PULL_UP: 'pull-up',
PULL_DOWN: 'pull-down',
INFINITE_SCROLL: 'infinite-scroll',
};
},
computed: {
_container() {
return this.container
? this.$el.closest(this.container)
: window.window;
},
pullHeight() {
return this.pull.distance > this.distance
? this.distance
: this.pull.distance;
},
},
mounted() {
this.$nextTick(() => {
this.init();
});
},
methods: {
init() {
this.bindEvents();
this.updateView();
if (this.initScroll) {
this.handleScroll();
}
},
updateView() {
let { top, height } = this.$el.getBoundingClientRect();
this.margin = {
top,
bottom: window.innerHeight - (height + top + this.offset),
};
},
setLoadingType(type = null) {
this.loadingType = type;
},
handleScroll() {
this.updateView();
if (this.loading || this.completed) {
return;
}
if (this.margin.bottom >= 0) {
this.$emit(this.INFINITE_SCROLL);
this.setLoadingType(this.INFINITE_SCROLL);
}
},
handleTouchStart(e) {
if (
this.loading ||
!(
this.hasListen(this.PULL_UP) ||
this.hasListen(this.PULL_DOWN)
) ||
(this.margin.top < 0 && this.margin.bottom < 0)
) {
return;
}
this.pull.from = e.touches.item(0).pageY;
},
handleTouchMove(e) {
if (this.loading || this.pull.from < 0) {
return;
}
this.pull.to = e.touches.item(0).pageY;
let distance = this.pull.to - this.pull.from;
if (
distance > 0 &&
this.margin.top > 0 &&
this.hasListen(this.PULL_DOWN)
) {
// pull down
this.pull.type = this.PULL_DOWN;
} else if (
distance < 0 &&
this.margin.bottom > 0 &&
this.hasListen(this.PULL_UP)
) {
// pull up
this.pull.type = this.PULL_UP;
} else {
this.pull.type = null;
}
this.pull.distance = Math.abs(distance);
this.pull.available = this.pull.distance >= this.distance;
},
handleTouchEnd() {
if (this.pull.distance >= this.distance) {
if (
this.PULL_UP === this.pull.type ||
this.PULL_DOWN === this.pull.type
) {
this.$emit(this.pull.type);
this.setLoadingType(this.pull.type);
}
} else {
this.resetPull();
}
},
resetPull() {
this.pull = {
from: -1,
to: -1,
distance: 0,
type: null,
available: false,
};
},
bindEvents() {
// scroll
if (this.hasListen(this.INFINITE_SCROLL)) {
this._container.addEventListener(
'scroll',
this.handleScroll.bind(this)
);
window.addEventListener('resize', this.handleScroll.bind(this));
}
// touch
if (
this.hasListen(this.PULL_UP) ||
this.hasListen(this.PULL_DOWN)
) {
this._container.addEventListener(
'touchstart',
this.handleTouchStart.bind(this)
);
this._container.addEventListener(
'touchmove',
this.handleTouchMove.bind(this)
);
this._container.addEventListener(
'touchend',
this.handleTouchEnd.bind(this)
);
}
},
hasListen(event) {
return this.listens.indexOf(event) >= 0;
},
},
watch: {
loading(val, oldVal) {
if (oldVal && !val) {
this.resetPull();
this.setLoadingType();
}
},
},
};
</script>
<style lang="scss" scoped>
.dataLoader{
display: flex;
flex-flow: column;
.loadingContent{
display: flex;
flex-flow: column;
}
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment