实现el-select下拉框,下拉时加载数据
指令
定义指令部分
import { nextTick } from 'vue';
import { debounce } from "lodash-es";export default {mounted(el, binding) {el._binding = binding;// 监听点击事件const handleClick = async () => {await nextTick();const dropdownEl = el.querySelector('.el-select-dropdown__wrap');if (dropdownEl) {// 监听滚动事件setupScrollListener(dropdownEl, binding);}};// 监听 focus 事件(用户可能通过 Tab 键触发下拉菜单)const handleFocus = async () => {await nextTick();const dropdownEl = el.querySelector('.el-select-dropdown__wrap');if (dropdownEl) {// 监听滚动事件setupScrollListener(dropdownEl, binding);}};// 绑定事件el.addEventListener('click', handleClick);el.addEventListener('focus', handleFocus);// 保存事件处理函数,便于后续删除el._handleClick = handleClick;el._handleFocus = handleFocus;},beforeUnmount(el) {// 移除监听事件if (el._handleClick) {el.removeEventListener('click', el._handleClick);delete el._handleClick;}if (el._handleFocus) {el.removeEventListener('focus', el._handleFocus);delete el._handleFocus;}// 移除滚动监听器const dropdownEl = el.querySelector('.el-select-dropdown__wrap');if (dropdownEl && dropdownEl._handleScroll) {dropdownEl.removeEventListener('scroll', dropdownEl._handleScroll);delete dropdownEl._handleScroll;}}
};function setupScrollListener(dropdownEl, binding) {// 清除之前的监听器if (dropdownEl._handleScroll) {dropdownEl.removeEventListener('scroll', dropdownEl._handleScroll);delete dropdownEl._handleScroll;}// 防抖const handleScroll = debounce(async () => {// 判断是否滚动到底部,滚动到底部时调用函数if (dropdownEl.scrollTop + dropdownEl.clientHeight >= dropdownEl.scrollHeight - 50) {if (typeof binding.value === 'function') {await binding.value();}}}, 200);// 保存dropdownEl._handleScroll = handleScroll;// 监听滚动事件dropdownEl.addEventListener('scroll', handleScroll);
}
注册和使用
在main中注册
app.directive('selectScroll', selectScroll)
<el-select v-selectScroll="handleSelectScroll"><el-option v-for="item in list" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
---------------------------------------------------
const handleSelectScroll = () => {// console.log('触底了')if (list.value.length < total.value) {query.pageNo = query.pageNo + 1getList() }
}
滚动事件例子
滚动事件的参数解析
- scrollTop: 滚动到顶部的距离
- clientHeight:可视区域的高度(包括padding,不包括border,margin,滚动条等内容)
- scrollHeight:元素内容的总高度(包括不可见部分,如被滚动隐藏的内容),常用于判断是否滚动到底部。
- offsetHeight:元素的总高度(包括 padding、border、margin 和滚动条)
scrollTop + clientHeight <= scrollHeight - 滚动到底部:当 scrollTop + clientHeight >= scrollHeight 时,表示用户已滚动到底部。
- 滚动到顶部:当 scrollTop === 0 时,表示用户滚动到顶部。
应用例子
实现无限滚动
window.addEventListener("scroll", () => {if (isAtBottom()) {loadMoreData(); // 加载更多数据}
});function isAtBottom() {const scrollTop = window.scrollY || document.documentElement.scrollTop;const clientHeight = window.innerHeight;const scrollHeight = document.documentElement.scrollHeight;return scrollTop + clientHeight >= scrollHeight;
}
平滑滚动到指定位置
window.scrollTo({top: 500, // 滚动到距离顶部500px的位置behavior: "smooth" // 平滑滚动
});
固定导航栏
window.addEventListener("scroll", () => {const nav = document.getElementById("navbar");if (window.scrollY > 100) {nav.classList.add("fixed"); // 滚动超过100px时固定导航栏} else {nav.classList.remove("fixed");}
});
思路
(1) Vue 指令定义
mounted 钩子:
-
监听 click 和 focus 事件,确保下拉框展开后能够正确绑定滚动事件。
-
使用 nextTick 确保 DOM 更新完成后再查询下拉框元素。
-
调用 setupScrollListener 绑定滚动事件。
beforeUnmount 钩子: -
移除所有事件监听器,避免内存泄漏。
-
清理 click、focus 和 scroll 事件。
(2) 滚动监听逻辑
setupScrollListener 函数:
- 防抖处理:使用 lodash-es 的 debounce 函数,避免频繁触发回调(设置 200ms 的防抖间隔)。
滚动到底部判断: - 当 scrollTop + clientHeight >= scrollHeight - 50 时,认为滚动到底部(预留 50px 的缓冲区域)。
回调触发:如果绑定的值是一个函数(binding.value),则调用它。
(3)为什么监听 click 和 focus 事件
el-select 下拉框的展开可能通过点击或键盘操作(如 Tab 键)触发。
通过监听这两种事件,确保无论以何种方式展开下拉框,都能正确绑定滚动事件。
(4) 滚动到底部的条件
- scrollTop:当前滚动位置。
- clientHeight:可视区域高度。
- scrollHeight:内容总高度。
- 条件:scrollTop + clientHeight >= scrollHeight - 50,表示距离底部还有 50px 时触发回调。