前言:
最近在做一个项目,一个列表展示原本是分页器,点击下一页,请求接口。但是业务说需要改成滑倒底部,加载数据。
一、实现过程
整体思路就是 获取 滚动元素的scrollHeight(元素内容的总高度)、clientHeight(元素的可见高度)、scrollTop(元素滚动条垂直滚动的距离)
scrollHeight表示元素内容的总高度,包括溢出部分;clientHeight表示元素的可见高度,不包括滚动条、边框和外边距;scrollTop表示元素滚动条垂直滚动的距离,即内容顶部相对于可见区域顶部的偏移量。
然后 在滚动事件里 判断 满足scrollTop+clientHeight >=scrollHeight 即可调用接口。
1.html+js
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><style>/* * {padding: 0;margin: 0;} */ul>li {width: 300px;height: 100px;list-style: none;border: 1px solid red;text-align: center;}#btnTop {position: fixed;bottom: 50px;right: 50px;}</style><body><div class="a"><ul id="box"></ul><div><button id="btnTop">滚动到顶部</button></div></div></body><script>//获取滚动条被卷进去的高function getScrollTop() {var scrollTop = 0, bodyScrollTop = 0, documentScrollTop = 0;if (document.body) {bodyScrollTop = document.body.scrollTop;}if (document.documentElement) {//兼容iedocumentScrollTop = document.documentElement.scrollTop;}scrollTop = (bodyScrollTop - documentScrollTop > 0) ? bodyScrollTop : documentScrollTop;return scrollTop;}//获取可滚动高度function getScrollHeight() {var scrollHeight = 0, bodyScrollHeight = 0, documentScrollHeight = 0;if (document.body) {bodyScrollHeight = document.body.scrollHeight;}if (document.documentElement) {//兼容iedocumentScrollHeight = document.documentElement.scrollHeight;}scrollHeight = (bodyScrollHeight - documentScrollHeight > 0) ? bodyScrollHeight : documentScrollHeight;return scrollHeight;}//获取可见区域高function getWindowHeight() {var windowHeight = 0;if (patMode == "CSS1Compat") {//兼容iewindowHeight = document.documentElement.clientHeight;} else {windowHeight = document.body.clientHeight;}return windowHeight;}function add(val) { //这里实际是请求接口比如到底部请求下一页接口let box = document.getElementById('box');for (var i = 0; i < val; i++) {let li = document.createElement('li');li.innerText = i;box.appendChild(li)}}add(10)//事件监听window.onscroll = function () {//滚动条被卷进去的高+可见区域高=可滚动高度说明到底部了console.log(getScrollTop() + getWindowHeight(), getScrollHeight())//到底部请求接口 Math.round四舍五入因为太多位会导致精度缺失if (Math.round(getScrollTop() + getWindowHeight()) >= getScrollHeight()) {add(10)}};let btn = document.getElementById('btnTop');btn.onclick = function () { // 页面滚动到顶部// 方法一document.body.scrollTop = document.documentElement.scrollTop = 0// 方法二//document.body.scrollIntoView()// scrollIntoView 是元素也有的方法, 可以用在页面元素上,例如// document.getElementById('id').scrollIntoView()}</script></html>
2.react实现
import React, { Component } from 'react';class Index extends Component {constructor(props) {super(props)this.state = {data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}}componentDidMount() {window.addEventListener("scroll", this.add);//监听滚动事件}add = () => {let { data, page } = this.state;console.log(Math.round(this.getScrollTop() + this.getWindowHeight()), this.getScrollHeight())if (Math.round(this.getScrollTop() + this.getWindowHeight()) >= this.getScrollHeight() - 1) {//项目中在这里面请求接口data.push(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);this.setState({data})}}//获取滚动条被卷进去的高getScrollTop = () => {var scrollTop = 0, bodyScrollTop = 0, documentScrollTop = 0;if (document.body) {bodyScrollTop = document.body.scrollTop;}if (document.documentElement) {//兼容iedocumentScrollTop = document.documentElement.scrollTop;}scrollTop = (bodyScrollTop - documentScrollTop > 0) ? bodyScrollTop : documentScrollTop;return scrollTop;}//获取可滚动高度getScrollHeight = () => {var scrollHeight = 0, bodyScrollHeight = 0, documentScrollHeight = 0;if (document.body) {bodyScrollHeight = document.body.scrollHeight;}if (document.documentElement) {//兼容iedocumentScrollHeight = document.documentElement.scrollHeight;}scrollHeight = (bodyScrollHeight - documentScrollHeight > 0) ? bodyScrollHeight : documentScrollHeight;return scrollHeight;}//获取可见区域高getWindowHeight = () => {var windowHeight = 0;if (patMode == "CSS1Compat") {//兼容iewindowHeight = document.documentElement.clientHeight;} else {windowHeight = document.body.clientHeight;}return windowHeight;}componentWillUnmount(){window.removeEventListener("scroll",this.add);//销毁监听事件防止内存泄漏}render() {let { data } = this.state;return (<div><ul>{data.map((item, index) => {return (<li style={{ height: "200px" }} key={index}>{item}</li>)})}</ul></div>);}}export default Index
3.react升级版本
为什么升级呢,因为 我发现上一个版本不适合所有场景(基于body的)。上面的版本知识和 没有指定 滚动dom的情况。而大部分使用场景都是需要指定某个元素的。所以我做了改动,也就是这个版本更灵活。
1.class版本
import React, { FC, useEffect, useRef, useState, Component } from 'react'import styles from './Home.module.less'class Home1 extends Component {constructor(props) {super(props)this.state = {data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],dom: null}}componentDidMount() {let dom = document.getElementById("list-box");dom.addEventListener("scroll", this.add);//监听滚动事件this.setState({dom})}add = () => {let { data, page } = this.state;console.log(Math.round(this.getScrollTop() + this.getClientHeight()), this.getScrollHeight())if (Math.round(this.getScrollTop() + this.getClientHeight()) >= this.getScrollHeight() - 1) {//项目中在这里面请求接口let arr=[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];let data1 = data.concat(arr);this.setState({data: data1})}}//获取滚动条被卷进去的高getScrollTop = () => {const { dom } = this.state;let scrollTop = 0;if (typeof dom.scrollTop !== "undefined") {// 如果浏览器支持直接获取scrollTop属性scrollTop = dom.scrollTop;} else if (typeof dom.pageYOffset !== "undefined") {// 如果浏览器支持pageYOffset属性scrollTop = dom.pageYOffset;} else if (typeof dom.scrollY !== "undefined") {// 如果浏览器支持scrollY属性scrollTop = dom.scrollY;} else if (typeof document.documentElement.scrollTop !== "undefined") {// 兼容IE浏览器的获取scrollTop属性scrollTop = document.documentElement.scrollTop;} else if (typeof document.body.scrollTop !== "undefined") {// 兼容IE浏览器的获取scrollTop属性scrollTop = document.body.scrollTop;}return scrollTop;}//获取可滚动高度getScrollHeight = () => {const { dom } = this.state;var scrollHeight = 0;if (typeof dom.scrollHeight !== "undefined") {// 如果浏览器支持直接获取scrollHeight属性scrollHeight = dom.scrollHeight;} else if (typeof dom.offsetHeight !== "undefined") {// 如果浏览器支持offsetHeight属性scrollHeight = dom.offsetHeight;} else if (typeof dom.clientHeight !== "undefined") {// 如果浏览器支持clientHeight属性scrollHeight = dom.clientHeight;}return scrollHeight;}//获取可见区域高getClientHeight = () => {const { dom } = this.state;var clientHeight = 0;if (typeof dom.clientHeight !== "undefined") {// 如果浏览器支持直接获取clientHeight属性clientHeight = dom.clientHeight;} else if (typeof dom.offsetHeight !== "undefined") {// 如果浏览器支持offsetHeight属性clientHeight = dom.offsetHeight;}return clientHeight;}componentWillUnmount() {const { dom } = this.state;dom.removeEventListener("scroll", this.add);//销毁监听事件防止内存泄漏}render() {let { data } = this.state;return (<div><ul className={styles.listBox} id='list-box'>{data.map((item, index) => {return (<li className={styles.listItem} key={index}>{item}</li>)})}</ul></div>);}}export default Home1;
样式:
* {margin: 0;padding: 0;}.listBox {width: 300px;height: 800px;border: 1px solid red;overflow: auto;}.listItem {width: 100%;height: 80px;border-bottom: 1px solid blue;}
2.hook版本
import React, { FC, useEffect, useRef, useState, Component } from 'react'import styles from './Home.module.less'const Home1 = () => {const [data, setData] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);useEffect(() => {let dom = document.getElementById("list-box");dom.addEventListener("scroll", add);//监听滚动事件return () => {// 组件卸载时,移除滚动事件监听器dom.removeEventListener('scroll', add);};}, [])const add = () => {//console.log(Math.round(getScrollTop() + getClientHeight()), getScrollHeight())if (Math.round(getScrollTop() + getClientHeight()) >= getScrollHeight()-1) {//项目中在这里面请求接口let data1 = data.push(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);setData(prevData => prevData.concat(data1));}}//获取滚动条被卷进去的高const getScrollTop = () => {let dom = document.getElementById("list-box");let scrollTop = 0;if (typeof dom.scrollTop !== "undefined") {// 如果浏览器支持直接获取scrollTop属性scrollTop = dom.scrollTop;} else if (typeof dom.pageYOffset !== "undefined") {// 如果浏览器支持pageYOffset属性scrollTop = dom.pageYOffset;} else if (typeof dom.scrollY !== "undefined") {// 如果浏览器支持scrollY属性scrollTop = dom.scrollY;} else if (typeof document.documentElement.scrollTop !== "undefined") {// 兼容IE浏览器的获取scrollTop属性scrollTop = document.documentElement.scrollTop;} else if (typeof document.body.scrollTop !== "undefined") {// 兼容IE浏览器的获取scrollTop属性scrollTop = document.body.scrollTop;}return scrollTop;}//获取可滚动高度const getScrollHeight = () => {let dom = document.getElementById("list-box");var scrollHeight = 0;if (typeof dom.scrollHeight !== "undefined") {// 如果浏览器支持直接获取scrollHeight属性scrollHeight = dom.scrollHeight;} else if (typeof dom.offsetHeight !== "undefined") {// 如果浏览器支持offsetHeight属性scrollHeight = dom.offsetHeight;} else if (typeof dom.clientHeight !== "undefined") {// 如果浏览器支持clientHeight属性scrollHeight = dom.clientHeight;}return scrollHeight;}//获取可见区域高const getClientHeight = () => {let dom = document.getElementById("list-box");var clientHeight = 0;if (typeof dom.clientHeight !== "undefined") {// 如果浏览器支持直接获取clientHeight属性clientHeight = dom.clientHeight;} else if (typeof dom.offsetHeight !== "undefined") {// 如果浏览器支持offsetHeight属性clientHeight = dom.offsetHeight;}return clientHeight;}return (<div><ul className={styles.listBox} id='list-box'>{data.map((item, index) => {return (<li className={styles.listItem} key={index}>{item}</li>)})}</ul></div>)}export default Home1;
样式:
* {margin: 0;padding: 0;}.listBox {width: 300px;height: 800px;border: 1px solid red;overflow: auto;}.listItem {width: 100%;height: 80px;border-bottom: 1px solid blue;}
4.vue里
vue里 以上代码理论上也能使用,把函数放到 methods里 变量放到data里(vue2)。
vue3的话就按照 ref写响应式变量,const 写函数。
如果 documnet.getxx不好用可以使用 vue 里的ref ,react同理。