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

news/2024/11/29 20:50:19/

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/news/1550984.html

相关文章

裸金属服务器和专属主机的区别是什么?

在当今互联网时代&#xff0c;人们越来越重视服务器的使用。裸金属服务器和专属主机是两种常见的服务器形式。裸金属服务器和云主机有什么区别呢&#xff1f; 一、定义和概念 裸金属服务器和云主机都是租用物理服务器的一种方式。 裸金属服务器是指没有安装虚拟化技术的物理…

使用 Python 剪辑视频的播放速度

要使用 Python 调整视频的播放速度&#xff0c;可以利用 moviepy 库中的 fx&#xff08;特效&#xff09;模块来实现这一功能。通过 moviepy.editor 中的 VideoFileClip 类和 fx.speedx 函数&#xff0c;可以轻松地调整视频的播放速度。 安装 moviepy 首先&#xff0c;确保已…

电脑上的ip地址可以改吗?如何改变ip地址

在现代网络环境中&#xff0c;IP地址作为设备在网络中的唯一标识&#xff0c;扮演着至关重要的角色。无论是日常上网冲浪&#xff0c;还是进行专业的网络操作&#xff0c;IP地址都与我们息息相关。那么&#xff0c;电脑上的IP地址可以改吗&#xff1f;答案是肯定的。接下来&…

linux系统中常用文件日常使用命令记录

我们办公机是Ubuntu系统&#xff1b; 记录下工作中经常使用的几个文件或命令或一些零碎的知识点&#xff1a; &#xff08;该文档会持续更新&#xff09; 查看系统信息&#xff1a; uname -a cat /etc/product-info cat /etc/os-version 存放系统启停脚本 /etc/init.d/ 存放源…

豆包MarsCode算法题:三数之和问题

问题描述 思路分析 1. 排序数组 目的: 将数组 arr 按升序排序&#xff0c;这样可以方便地使用双指针找到满足条件的三元组&#xff0c;同时避免重复的三元组被重复计算。优势: 数组有序后&#xff0c;处理两个数和 target - arr[i] 的问题可以通过双指针快速找到所有可能的组…

JAVA项目-------医院挂号系统

1&#xff0c;项目目的 1、科室管理&#xff1a;新增科室&#xff0c;删除科室&#xff08;如果有医生在&#xff0c;则不能删除该科室&#xff09;&#xff0c;修改科室。 2、医生管理&#xff1a;录入医生信息&#xff0c;以及科室信息。修改医生信息&#xff08;主要是修改…

【AI系统】分布式通信与 NVLink

分布式通信与 NVLink 在进入大模型时代后&#xff0c;大模型的发展已成为 AI 的核心&#xff0c;但训练大模型实际上是一项比较复杂的工作&#xff0c;因为它需要大量的 GPU 资源和较长的训练时间。 此外&#xff0c;由于单个 GPU 工作线程的内存有限&#xff0c;并且许多大模…

ODB 框架

目录 概述 基本工作原理 映射C对象到数据库表 从数据库中加载对象 持久化C对象到数据库 ODB常用接口 表创建预处理 #pragma db Object table 数据表属性 id auto column&#xff08;“xxx”&#xff09; type("xxx") unique index null default&…