介绍
瀑布流滚动加载,用于展示长列表,当列表即将滚动到底部时,会触发事件并加载更多列表项。
scroll 组件通过 loading 和 finished 两个变量控制加载状态,当组件滚动到底部时,会触发 load 事件并将 loading 设置成 true。此时可以发起异步操作并更新数据,数据更新完毕后,将 loading 设置成 false 即可。若数据已全部加载完毕,则直接将 finished 设置成 true 即可。
<template>
<div class="infinite_scroll">
<slot></slot>
<div ref="placeholder" class="list_placeholder"></div>
<div v-if="loading" class="list_loading">
<div class="loading">
<span class="loading_icon"></span>
<span class="loading_text">{{ loadingText }}</span>
</div>
</div>
<div v-if="finished" class="list_finished">{{ finishedText }}</div>
<div v-if="error" class="list_error" @click="clickErrorText">
{{ errorText }}
</div>
</div>
</template>
<script>
const overflowScrollReg = /scroll|auto|overlay/i;
function getScroller(el, root = window) {
let node = el;
while (
node &&
node.tagName !== "HTML" &&
node.tagName !== "BODY" &&
node.nodeType === 1 &&
node !== root
) {
const { overflowY } = window.getComputedStyle(node);
if (overflowScrollReg.test(overflowY)) {
return node;
}
// 从 infinite_scroll 一层一层网上找
node = node.parentNode;
}
return root;
}
export default {
props: {
loading: Boolean,
finished: Boolean,
error: Boolean,
immediateCheck: {
type: Boolean,
default: true,
},
offset: {
type: [Number, String],
default: 300,
},
},
data() {
return {
direction: "down",
scroller: "",
loadingText: "加载中",
finishedText: "没有更多了",
errorText: "请求失败,点击重新加载",
};
},
mounted() {
this.scroller = getScroller(this.$el);
// 添加滚动事件监听器
this.scroller.addEventListener("scroll", this.check);
if (this.immediateCheck) {
this.check();
}
},
beforeUnmount() {
// 移除滚动事件监听器
this.scroller.removeEventListener("scroll", this.check);
},
watch: {
loading: "check",
finished: "check",
},
methods: {
check() {
this.$nextTick(() => {
if (this.loading || this.finished || this.error) {
return;
}
const { scroller, offset, direction } = this;
let scrollerRect;
if (scroller.getBoundingClientRect) {
scrollerRect = this.scroller.getBoundingClientRect();
} else {
scrollerRect = {
top: 0,
bottom: scroller.innerHeight,
};
}
let isReachEdge = false;
const placeholderRect = this.$refs.placeholder.getBoundingClientRect();
isReachEdge = placeholderRect.bottom - scrollerRect.bottom <= offset;
if (isReachEdge) {
this.$emit("update:loading", true);
this.$emit("load");
}
});
},
genLoading() {
if (this.loading && !this.finished) {
return "加载中";
}
},
genFinishedText() {
if (this.finished) {
return "加载完毕";
}
},
genErrorText() {
if (this.error) {
return "获取失败";
}
},
clickErrorText() {
this.$emit("update:error", false);
this.$emit("update:loading", false);
this.check();
},
},
};
</script>
<style scoped>
.list_placeholder {
height: 0;
visibility: hidden;
}
.list_loading,
.list_finished,
.list_error {
padding: 10px 16px;
font-size: 14px;
text-align: center;
color: #999;
background-color: #fff;
}
.list_loading .loading {
display: flex;
align-items: center;
justify-content: center;
}
.list_loading .loading_icon {
/* 设置 loading 图标样式 */
}
.list_loading .loading_text {
line-height: 18px;
}
.list_finished,
.list_error {
/* pointer-events: none; */
}
.list_error {
cursor: pointer;
}
</style>
使用
<template>
<div class="list">
<scroll
@load="handleLoad"
:loading.sync="loading"
:finished.sync="finished"
:error.sync="error"
>
<div v-for="(item, index) in list" :key="index" class="item">
{{ index }}
</div>
</scroll>
</div>
</template>
<script>
import scroll from "./scroll.vue";
export default {
components: {
scroll,
},
data() {
return {
list: [],
loading: false,
finished: false,
error: false,
pageNum: 1,
};
},
methods: {
handleLoad() {
if (this.list.length === 80) {
this.list.push("1");
this.error = true;
} else {
console.log("执行");
setTimeout(() => {
for (let i = 0; i < 20; i++) {
this.list.push(i);
}
this.pageNum++;
this.loading = false;
if (this.list.length === 161) {
this.finished = true;
}
}, 2000);
}
},
},
};
</script>
<style>
* {
margin: 0;
padding: 0;
}
html,
body,
#app {
height: 100%;
}
.item {
padding: 10px 0;
border-bottom: 1px solid blue;
}
.item:last-child {
border: none;
}
.list {
height: 100%;
overflow-y: auto;
}
</style>