Table 滚动条始终停靠在可视区域的底部

ops/2024/11/30 11:47:36/

1. 话题引入

存在这样一个场景:当页面尺寸发生变化时,希望滚动条能够随之动态调整,始终展示在 table可视区域的最下方,而不是整个 table 本身的最底部。

这种行为可以提升用户的使用体验,尤其是在处理大数据表格时,用户不需要滚动到整个页面的最底部才能看到滚动条,而是始终在当前可视区域操作。

效果如下,在 Table 未完全展示下的滚动条

完全展示后,滚动条在 右侧 和 底部。

2. 如何实现?

这个实现方法比较困难,不能通过控制 Table 自带的滚动条或简单的 CSS 来实现效果。我尝试了很久,都不OK,因此决定手动实现一个与其本身风格一致的自定义滚动条

需要解决的问题包括:

1、监听横向滚动条,实时捕获水平滚动状态并动态更新滚动条的位置和宽度。样式部分可以简单处理,不是重点。

2、监听视口可视区域高度变化,动态展示滚动条,使用 position 固定到可视区域底部。

3、实现拖拽滚动功能,通过鼠标按下滚动条并拖动,能够实现同步滚动的效果。

3. 解决实现

感兴趣的小伙伴可以自行实现下哦O。

HTML 结构

在 Table 下使用一个 div 实现滚动条效果,📢 外层的 div 是必须的,负责实时的定位效果。

<div class="custom-container" style={{ position: "relative" }}><ElTable ref={tableRef} {...props} {...attrs} v-slots={slots}>{slots.default && slots.default()}</ElTable><divclass="custom-scrollbar"style={{zIndex: 999,width: `${scrollBarWidthRef.value}px`,height: `${SCROLLBAR_HEIGHT}px`,position: "absolute",top: `${scrollBarTopRef.value}px`,left: `${scrollBarLeftRef.value}px`}}onMousedown={onMouseDown}></div>
</div>

第一个问题:滚动条宽度和位置处理

难点:监听横向滚动条变化

const getScrollBarWidth = () => {// 获取横向滚动条元素const scrollWrapper = tableRef.value?.$el.querySelector(".el-scrollbar__wrap");// 获取 table 元素大小及其相对位置const tableRect = tableRef.value?.$el.getBoundingClientRect();// ---- 获取滚动条的宽度(源码) ----const GAP = 4;const offsetWidth = scrollWrapper.offsetWidth - GAP;const originalWidth = offsetWidth ** 2 / scrollWrapper.scrollWidth;const scrollbarWidth = Math.max(originalWidth, SCROLLBAR_MIN_WIDTH);scrollBarWidthRef.value = scrollbarWidth;return {tableRect, // 表格大小scrollbarWidth // 滚动条宽度};
};

接下来设置滚动条的位置

// 设置滚动条宽度
const setScrollBarWidth = () => {const { scrollbarWidth } = getScrollBarWidth();scrollBarWidthRef.value = scrollbarWidth;
};// 设置滚动条高度
const setScrollBarTop = () => {const { tableRect } = getScrollBarWidth();scrollBarTopRef.value =window.innerHeight - tableRect.top - SCROLLBAR_HEIGHT > tableRect.height? tableRect.height - SCROLLBAR_HEIGHT: window.innerHeight - tableRect.top - SCROLLBAR_HEIGHT;
};// 监听滚动的同时设置 Left
const handleScroll = e => {const { tableRect } = getScrollBarWidth();setScrollBarTop();setScrollBarWidth();if (e.target) {// 比例计算scrollBarLeftRef.value = (e.target.scrollLeft / e.target.scrollWidth) * tableRect.width;}
};const handleWindowScroll = () => {setScrollBarTop();
};

既然方法都写好啦,那就开始调用。

onMounted(() => {// 初始化时执行// 为什么在 setTimeout 呢?避免过早地执行这些方法,它们依赖于表格 DOM 完全渲染完成后的尺寸信息。setTimeout(() => {setScrollBarWidth();setScrollBarTop();}, 0);// 确保 DOM 已完全更新nextTick(() => {if (tableRef.value) {scrollWrapperRef.value = tableRef.value?.$el.querySelector(".el-scrollbar__wrap");bodyWrapperRef.value = tableRef.value?.$el.querySelector(".el-table__body-wrapper");if (scrollWrapperRef.value) {scrollWrapperRef.value.addEventListener("scroll", handleScroll);}if (bodyWrapperRef.value) {window.addEventListener("scroll", handleWindowScroll);}}});
});

注意:当窗口尺寸变化时,需要重新计算滚动条的高度和宽度。

const handleResize = () => {setScrollBarWidth();setScrollBarTop();
};
window.addEventListener("resize", handleResize);

OK,到此滚动条的位置完成,接下来处理滚动条的拖拽。

这个就比较简单了,监听鼠标抬起,移动的事件,设置滚动距离。

let isDragging = false;
let startX = 0;
let startLeft = 0;
let rafId: number | null = null;// 鼠标抬起
const onMouseUp = () => {isDragging = false;document.removeEventListener("mousemove", onMouseMove);document.removeEventListener("mouseup", onMouseUp);
};
// 鼠标移动
const onMouseMove = (e: MouseEvent) => {if (!isDragging) return;if (rafId !== null) {cancelAnimationFrame(rafId);}rafId = requestAnimationFrame(() => {const moveX = e.clientX - startX; // 计算拖动距离const newLeft = startLeft + moveX; // 计算滚动条的新位置scrollBarLeftRef.value = Math.max(0,Math.min(newLeft, tableRef.value?.$el.scrollWidth - scrollBarWidthRef.value));// 更新表格滚动位置if (scrollWrapperRef.value) {scrollWrapperRef.value.scrollLeft = (scrollBarLeftRef.value / tableRef.value?.$el.scrollWidth) * scrollWrapperRef.value.scrollWidth;}});
};
// 鼠标按下
const onMouseDown = (e: MouseEvent) => {isDragging = true;startX = e.clientX;startLeft = scrollBarLeftRef.value; // 当前滚动条的位置document.addEventListener("mousemove", onMouseMove);document.addEventListener("mouseup", onMouseUp);
};

按下滚动条可滑动 Table。

最后清空一下监听的事件。

onBeforeUnmount(() => {if (scrollWrapperRef.value) {scrollWrapperRef.value.removeEventListener("scroll", handleScroll);}window.removeEventListener("scroll", handleWindowScroll);window.removeEventListener("resize", handleResize);
});

自此就成功实现一个符合要求的滚动条 👏🏻 👏🏻 👏🏻。 


http://www.ppmy.cn/ops/137913.html

相关文章

MacOS 配置github密钥

MacOS 配置github密钥 1. 生成GitHub的SSH密钥对 ssh-keygen -t ed25519 -C "xxxxxxx.com" -f ~/.ssh/id_ed25519_github 其中 xxxxxxxxxxx.com 是注册github、gitee和gitlab的绑定账号的邮箱 -t ed25519:生成密钥的算法为ed25519&#xff08;ed25519比rsa速度快&…

【计算视觉算法与应用】金字塔,下采样Gaussian Pyramid. 上采用 Laplacian Pyramid (code: py)

金字塔&#xff08;Pyramid&#xff09;在图像处理中主要用于多尺度分析和图像压缩。常见的图像金字塔有两种&#xff1a; 高斯金字塔&#xff08;Gaussian Pyramid&#xff09;&#xff1a;用于下采样图像&#xff0c;生成分辨率逐渐降低的图像序列。拉普拉斯金字塔&#xff…

攻防世界GFSJ1193 cat_theory

题目编号&#xff1a;GFSJ1193 附件下载后是一个jpg文件和一个sage文件&#xff08;python&#xff09;&#xff1a; 1. 分析图片&#xff08;.jpg文件&#xff09; 这个交换图展示的是一个加密系统的 同态加密 性质&#xff0c;其核心思想是&#xff1a;加密前的操作与加密后…

2017 NHOI小学(C++)

A. 吃西瓜&#xff08;2017 NHOI小学 1&#xff09; 问题描述: 炎热的夏天来的可真快&#xff0c;小花猫和编程兔决定去买一个又大又甜的西瓜。可是小花和编程兔是两只非常奇怪的动物&#xff0c;都是偶数的爱好者&#xff0c;它们希望把西瓜切成两半后&#xff0c;每一部分的…

Vue 3 的双向绑定原理

Vue 3 的双向绑定原理是基于 响应式系统 和 数据劫持 技术来实现的。在 Vue 3 中&#xff0c;双向绑定通常是通过 v-model 指令来完成的&#xff0c;它本质上是数据的双向同步&#xff1a;当数据改变时&#xff0c;视图自动更新&#xff0c;反之&#xff0c;视图的修改也会更新…

小程序 - 婚礼邀请函

小程序页面和样式练习 - 婚礼邀请函小程序开发笔记 目录 婚礼邀请函 准备工作 加载静态资源 项目初始化 标签栏的配置 各页面导航栏标题配置 全局导航栏样式配置 公共样式的编写 项目内容 邀请函页面内容 邀请函页面样式 照片页面内容 照片墙页面样式 美好时光页…

Ubuntu桥接模式设置静态IP

目录 关于 NAT VS 桥接 为桥接模式配置静态IP 编辑虚拟机设置 虚拟网络编辑器 选择要桥接的网络适配器 固定桥接该网络适配器 确定静态IP与网关 虚拟机内更改 桌面可直接更改设置 非桌面版可以更改配置文件 关于Windows网络适配器&#xff08;可以改&#xff09;…

信创改造 - Redis -》TongRDS 安装方式之单节点模式安装

安装前准备 安装 JDK 参考链接&#xff1a;安装 JDK 8【Linux】 语雀 创建用户 # 用户名可以自己起 useradd rds 上传安装包到服务器 单节点模式是由两个部署单元组成&#xff1a;1 个RDS 服务节点&#xff0c;1 个 RDS 中心节点。 上传到 /home/rds 用户文件夹&#xff0…