指令
定义指令部分
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 + 1
getList()
}
}
滚动事件例子
滚动事件的参数解析
- 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 时触发回调。