JS div滚动条下拉到底部时触发事件(懒加载)

文章讲述了如何使用JavaScript监听浏览器窗口和div滚动条的滚动事件,实现动态分页和预加载数据。通过调整判断条件,在滚动条接近底部时开始加载新内容,以提升用户体验。

1.针对浏览器窗口滚动

<script type="text/javascript">
        var pageNum = 1;
     //分页
        $(window).scroll(function () {  
			//滚动到底部时
			if ($(window).scrollTop() + $(window).height() == $(document).height()) {   
				pageNum += 1;
				//具体数据操作
       }
    }); 
</script>

2.针对div的滚动条

<div class="default-scrollbar" style="height: 500px; overflow: auto;">
	内容
</div>
//滚动条拖动到底事件
//注意:含有css类名为:[default-scrollbar]的div要出现滚动条 此函数(scroll)才会触发
$('.default-scrollbar').unbind("scroll").on("scroll", function (e) {
	//滚动条距顶部距离(页面超出窗口的高度)
	var sum = this.scrollHeight;
	var $obj = $(this);
	//判断滚动条是否 到达了 底部 
	//$obj.scrollTop():滚动条距顶部距离(页面超出窗口的高度)
	//$obj.height():当前(div)滚动窗口的高度
	if (sum <= $obj.scrollTop() + $obj.height()) {
		//的滚动条-滚动到底时需要处理的逻辑
		console.log("滚动条到达浏览器底部");
	}
});

这里的if判断的是刚好下拉滚动条到底时的情况,这种情况下去处理逻辑(加载数据)可能会比较影响用户体验,毕竟处理逻辑(加载数据)也是需要时间的,所以这里可以使用预加载的形式,就是说在还有一段距离beforeHandHeight到底时就开始加载数据。
那么上面的if条件可以改为的判断 if 条件就需要改成:sum - beforeHandHeight <= $obj.scrollTop() + $obj.height()this.scrollHeight - beforeHandHeight <= $(this).scrollTop() + $(this).height()
例如:当滚动条距离浏览器底部还有30px时 就需要开始处理逻辑(加载数据),那么上述if语句就需要改为sum - 30 <= $obj.scrollTop() + $obj.height() this.scrollHeight - 30 <= $(this).scrollTop() + $(this).height()

<think>我们参考用户提供的引用内容,特别是引用[2]和[3],它们展示了如何通过`onPopupScroll`事件(在Vue中是`@popupScroll`)来监听下拉框的滚动事件,并在滚动到底部触发加载更多数据的逻辑。 关键点: 1. 给Select组件添加`onPopupScroll`属性(在React中)或`@popupScroll`事件(在Vue中),绑定一个滚动事件处理函数。 2. 在滚动事件处理函数中,判断是否滚动到底部。判断条件通常是:`target.scrollTop + target.offsetHeight >= target.scrollHeight - 某个阈值`(或者等于,但考虑到精度问题,一般用大于等于,并设置一个阈值,比如10px)。 3. 当满足条件,执行加载数据的函数(例如分页请求下一页数据)。 但是,用户提到引用[4]中有一个问题:当数据加载后,滚动条会弹回顶部。这是因为在数据加载后,下拉框的内容高度增加,但滚动位置没有保持,导致滚动条回到顶部。我们需要在加载新数据保持当前的滚动位置,或者避免在加载过程中触发不必要的重绘。 解决方案(避免弹回问题): 1. 在加载新数据之前,记录当前的滚动位置(scrollTop)和滚动高度(scrollHeight)。 2. 加载数据后,使用记录的滚动高度和当前的滚动高度计算出新的滚动位置(因为内容高度增加了,所以需要调整scrollTop),然后重新设置滚动位置。 但是,引用[4]提到直接修改scrollTop可能会被重置,因为antd组件内部可能会重新渲染。因此,我们可以尝试在数据加载完成后,在下一次事件循环中设置滚动位置。 另一种思路:在加载数据,不要立即将新数据追加到选项中,而是等待下拉框的滚动位置调整后再追加?这不太可行,因为数据是异步加载的。 实际上,引用[4]的解决方案是失败的,所以我们需要更稳健的方法。 参考引用[5],它封装了一个组件,但并没有详细说明如何解决弹回问题。因此,我们可以尝试以下方法: 在滚动事件处理函数中,当检测到滚动到底部,我们开始加载数据。在加载数据之前,我们记录当前的滚动位置(即当前的scrollTop)。然后,在数据加载完成后,我们使用setTimeout(或者nextTick在Vue中)来设置滚动容器的scrollTop为之前记录的位置加上新加载内容的高度(或者更简单,记录加载前的scrollHeight,然后设置scrollTop为之前记录的scrollTop加上新加载内容的高度差)。但是,这样计算比较复杂。 一个更简单且有效的方法:在加载数据之前,记录当前的scrollTop和scrollHeight。数据加载后(此dom已经更新,新的scrollHeight会变大),然后设置滚动容器的scrollTop为之前记录的scrollTop加上(新的scrollHeight减去之前记录的scrollHeight)。这样就能保持用户之前浏览的位置。 然而,由于antd的下拉框是动态渲染的,我们可能无法直接操作滚动容器?实际上,在滚动事件处理函数中,我们可以通过e.target获取到滚动容器(即下拉框的滚动容器)。 具体步骤(以Vue为例,React类似): 1. 在data中定义变量,用于记录滚动位置和滚动高度(可选,因为我们可以在事件触发记录)。 2. 在`handlePopupScroll`方法中,当滚动到底部,记录当前的scrollTop和scrollHeight(作为旧值),然后触发加载数据。 3. 数据加载完成后(异步请求返回),在nextTick(确保DOM更新)中,再次获取同一个滚动容器(可以通过ref或者事件对象,但注意事件对象在异步中不可用,所以最好用ref),然后设置: 滚动容器.scrollTop = 旧scrollTop + (滚动容器.scrollHeight - 旧scrollHeight) 但是,在异步加载数据后,我们如何获取到滚动容器?我们可以通过给Select组件添加ref,然后通过antd提供的方法获取下拉框的滚动容器。但antd的Select组件并没有直接提供滚动容器的ref。因此,我们可以通过事件对象来获取,但事件对象在异步中可能已经不可用,所以我们在触发加载保存滚动容器的引用。 另一种方法:在滚动事件中,当需要加载,我们保存当前的滚动容器(e.target)到一个变量中,然后在数据加载完成后,使用这个变量来设置滚动位置。 注意:由于是同一个DOM元素,所以可以直接操作。 代码示例(Vue): 假设我们有一个加载数据的方法`loadMore`,我们这样写: ```vue <template> <a-select v-model="value" @popupScroll="handleScroll" :loading="loading" mode="multiple" // 如果是多选 // 其他属性 > <a-select-option v-for="item in options" :key="item.id"> {{ item.label }} </a-select-option> </a-select> </template> <script> export default { data() { return { value: [], options: [], // 当前已加载的选项 loading: false, page: 1, hasMore: true, scrollContainer: null, // 用于保存滚动容器 oldScrollTop: 0, oldScrollHeight: 0 }; }, methods: { handleScroll(e) { const target = e.target; // 判断是否滚动到底部 if (target.scrollTop + target.offsetHeight >= target.scrollHeight - 10) { // 避免重复加载 if (this.loading || !this.hasMore) return; // 记录当前的滚动容器和滚动位置、高度 this.scrollContainer = target; this.oldScrollTop = target.scrollTop; this.oldScrollHeight = target.scrollHeight; // 触发加载 this.loadMore(); } }, async loadMore() { this.loading = true; try { // 假设fetchData是获取下一页数据的方法,返回新一页的数据 const newData = await this.fetchData(this.page); if (newData.length === 0) { this.hasMore = false; return; } this.options = [...this.options, ...newData]; this.page++; // 在DOM更新后调整滚动位置 this.$nextTick(() => { if (this.scrollContainer) { // 计算新的滚动位置 const newScrollTop = this.oldScrollTop + (this.scrollContainer.scrollHeight - this.oldScrollHeight); this.scrollContainer.scrollTop = newScrollTop; } }); } finally { this.loading = false; } }, fetchData(page) { // 模拟异步请求 return new Promise(resolve => { setTimeout(() => { // 返回模拟数据,实际中应该是根据page请求接口 const data = ...; resolve(data); }, 500); }); } } }; </script> ``` 注意:上面的代码中,我们在加载新数据后,通过`$nextTick`确保DOM已经更新,然后调整滚动位置。调整的方法是用原来的滚动位置加上新增内容的高度(即新的scrollHeight减去原来的scrollHeight),这样滚动条就会保持在原来的相对位置,不会跳回顶部。 但是,这种方法在多次加载可能会累积误差?实际上,每次加载,我们记录的是上一次加载前的scrollHeight,然后与当前加载后的scrollHeight做差,得到新增的高度,然后加到原来的scrollTop上,这样滚动位置就会向下移动新增内容的高度,从而保持用户之前浏览的位置。 另外,在React中的实现思路类似,只是语法不同。 但是,引用[4]中提到,自行赋值不会超出`scrollHeight - clientHeight`的范围,所以是安全的。 另外,为了避免频繁触发加载,我们可以使用防抖,但这里主要是在滚动到底部触发一次,所以只要控制好加载状态(loading)即可。 总结步骤: 1. 监听下拉框的滚动事件。 2. 滚动到底部,记录滚动容器的引用、当前的scrollTop和scrollHeight。 3. 发起异步请求获取更多数据。 4. 数据返回后,更新选项列表,并在DOM更新后,重新设置滚动容器的scrollTop为:旧scrollTop + (新scrollHeight - 旧scrollHeight) 5. 重置加载状态。 这样,用户就会看到滚动条停留在原来的位置,而下方加载了新的数据,不会出现跳回顶部的情况。 注意:如果加载的数据量很大,导致渲染间很长,可能会在设置scrollTop出现偏差,因为DOM更新可能分多次(比如虚拟滚动?)。但antd的Select组件默认不是虚拟滚动,所以一次加载的数据量不宜过大(比如一页20条)。 另外,引用[1]中提到`getPopupContainer`属性,用于解决下拉框跟随滚动条滚动的问题,但这里我们主要关注下拉框内部的滚动,所以不需要特别设置。不过,如果下拉框在滚动容器内,可能需要设置`getPopupContainer`使下拉框挂载到父节点,避免样式问题。 因此,用户要实现下拉懒加载,可以按照上述方法实现。 相关问题: 1. 在Ant Design的Select组件中,如何避免下拉懒加载数据滚动条跳回顶部? 2. 除了滚动到底部加载,还有哪些方式可以实现Select组件的懒加载? 3. 如何优化Select组件在大量数据下的性能?</think>### Ant Design Select 组件实现下拉懒加载方案 在 Ant Design 的 Select 组件中实现下拉懒加载(滚动到底部加载数据),主要通过监听下拉框的滚动事件,结合分页加载逻辑完成。以下是具体实现步骤和代码示例: #### 核心实现步骤 1. **监听滚动事件**:使用 `onPopupScroll` 属性绑定滚动事件 2. **判断滚动位置**:计算 `scrollTop + offsetHeight >= scrollHeight` 触发加载 3. **分页加载数据**:滚动到底部请求下一页数据 4. **防止滚动条弹回**:记录滚动位置并动态调整(解决常见问题) #### 代码实现(React 示例) ```jsx import { Select, Spin } from 'antd'; import { useState, useRef } from 'react'; const LazyLoadSelect = () => { const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [page, setPage] = useState(1); const scrollContainerRef = useRef(null); const scrollPosRef = useRef({ top: 0, height: 0 }); // 滚动事件处理 const handleScroll = (e) => { const { scrollTop, offsetHeight, scrollHeight } = e.target; // 记录当前滚动位置(防止弹回) scrollPosRef.current = { top: scrollTop, height: scrollHeight }; // 滚动到底部判断(留 10px 缓冲) if (scrollTop + offsetHeight >= scrollHeight - 10 && !loading) { loadMoreData(); } }; // 加载更多数据 const loadMoreData = async () => { setLoading(true); try { // 模拟 API 请求 const newData = await fetchPageData(page); // 合并新数据 setData(prev => [...prev, ...newData]); setPage(prev => prev + 1); // 恢复滚动位置(防止弹回) setTimeout(() => { if (scrollContainerRef.current) { scrollContainerRef.current.scrollTop = scrollPosRef.current.top; } }, 0); } finally { setLoading(false); } }; return ( <Select loading={loading} onPopupScroll={handleScroll} listHeight={300} dropdownRender={menu => ( <div ref={scrollContainerRef} onScroll={handleScroll} // 双重绑定确保兼容性 > {menu} {loading && <Spin style={{ display: 'block', margin: '10px auto' }} />} </div> )} > {data.map(item => ( <Select.Option key={item.id} value={item.id}> {item.name} </Select.Option> ))} </Select> ); }; // 模拟数据请求 const fetchPageData = (page) => { return new Promise(resolve => { setTimeout(() => { resolve(Array.from({ length: 20 }, (_, i) => ({ id: `${page}-${i}`, name: `选项 ${page}-${i}` }))); }, 500); }); }; ``` #### 关键优化点 1. **滚动位置恢复** 通过 `scrollPosRef` 记录位置,数据加载后使用 `setTimeout` 恢复滚动条位置,解决加载后滚动条弹回顶部的问题[^4]。 2. **双重滚动绑定** `dropdownRender` + `onPopupScroll` 双重绑定确保兼容不同 Ant Design 版本。 3. **加载状态反馈** 使用 `Spin` 组件展示加载状态,提升用户体验。 4. **滚动阈值缓冲** 设置 `scrollHeight - 10` 缓冲避免频繁触发请求。 #### 注意事项 1. 当使用 **多选模式** ,需添加 `mode="multiple"` 属性 2. 分页参数需重置:当搜索条件变化,重置 `page=1` 并清空数据 3. 接口防抖:可在滚动事件中添加 200ms 防抖避免频繁请求 4. 禁用滚动加载:数据全部加载完成后隐藏 spinner ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值