本文介绍了一种通过CSS绝对定位结合JavaScript事件处理实现表格固定列的方案。该方案利用DIV包裹表头与表体,通过计算left、width及滚动条位置,配合虚拟滚动条和鼠标滚轮事件监听,实现了在主流浏览器中固定关键列并同步滚动的效果,兼顾了组件化开发的简洁性与多场景兼容性。
固定列需求与方案概述
在BS架构的企业级应用中,当表格列数较多时,用户常希望将前几列关键数据固定,以便在拖动滚动条查看数据时保持可见,从而提升用户体验。虽然许多大型JS组件库已具备此功能,但探索更轻量级的实现方式仍具价值。
常见的解决方案包括表格拼接法。该方法在制作静态网页或功能简单的动态页面时逻辑清晰、易于实现。然而,若需将其封装为功能丰富的组件,则会产生大量冗余代码,导致维护困难。例如,简单的事件处理也需编写大量代码,使得该方案显得笨拙且灵活性不足,并非理想选择。
经过长期分析与多场景测试,我们找到了一种兼容性极佳的解决方案。其核心思路基于定位计算,下文将展示具体代码并进行详细解读。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<script type="text/javascript">
function divScroll(scrollDiv){
var scrollLeft = scrollDiv.scrollLeft;
document.getElementById("tableDiv_title").scrollLeft = scrollLeft;
document.getElementById("tableDiv_body").scrollLeft = scrollLeft;
}
function divYScroll(scrollYDiv){
var scrollTop = scrollYDiv.scrollTop;
document.getElementById("tableDiv_y").scrollTop = scrollTop;
}
function onwheel(event){
var evt = event||window.event;
var bodyDivY = document.getElementById("tableDiv_y");
var scrollDivY = document.getElementById("scrollDiv_y");
if (bodyDivY.scrollHeight>bodyDivY.offsetHeight){
if (evt.deltaY){
bodyDivY.scrollTop = bodyDivY.scrollTop + evt.deltaY*7;
scrollDivY.scrollTop = scrollDivY.scrollTop + evt.deltaY*7;
}else{
bodyDivY.scrollTop = bodyDivY.scrollTop - evt.wheelDelta/5;
scrollDivY.scrollTop = scrollDivY.scrollTop - evt.wheelDelta/5;
}
}
}
</script>
<style type="text/css">
body {
margin:0;
padding:0;
}
table {
border-collapse:collapse;
border:0;
border:none;
}
table td {
border:1px solid #000;
overflow:hidden;
padding:0 2px;
}
</style>
</head>
<body>
<div style="width:500px; position:relative; padding-right:18px;">
<div style="position:relative;height:368px;overflow:hidden;width:100%">
<div style="padding-left:108px; width:auto; overflow:hidden; background:#f00;" id="tableDiv_title" >
<table border="0" cellspacing="0" cellpadding="0" >
<tr>
<td style="min-width:30px; max-width:30px; left:0; top:0; width:30px; overflow:hidden; background-color:#f00;position:absolute;z-index:1;">000</td>
<td style="min-width:74px; max-width:74px; left:30px; top:0; width:74px; overflow:hidden; background-color:#f00;position:absolute;z-index:1;">自动表格</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >最后一列</td>
</tr>
</table>
</div>
<div style="overflow:hidden; position:absolute;height:128px; width:100%;" id="tableDiv_y" onmousewheel="onwheel(event);" onwheel="onwheel(event);">
<div style="padding-left:108px; width:auto;overflow:hidden;" id="tableDiv_body">
<table border="0" cellspacing="0" cellpadding="0" >
<tr>
<td style="min-width:30px; max-width:30px; left:0; width:30px; overflow:hidden; background-color:#fff;position:absolute;z-index:1;">001</td>
<td style="min-width:74px; max-width:74px; left: 30px; width: 74px; overflow:hidden; background-color:#fff;position:absolute;z-index:1;">自动表格</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >最后一列</td>
</tr>
<tr>
<td style="min-width:30px; max-width:30px; left:0; width:30px; overflow:hidden; background-color:#fff; position:absolute; z-index:1;">002</td>
<td style="min-width:74px; max-width:74px; left:30px; width:74px; overflow:hidden;background-color:#fff; position:absolute; z-index:1;">自动表格</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >最后一列</td>
</tr>
<tr>
<td style="min-width:30px; max-width:30px; left:0; width:30px; overflow:hidden; background-color:#fff;position:absolute;z-index:1;">003</td>
<td style="min-width:74px; max-width:74px;left: 30px; width: 74px; overflow:hidden;background-color:#fff;position:absolute;z-index:1;">自动表格</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >最后一列</td>
</tr>
<tr>
<td style="min-width:30px; max-width:30px; left:0; width:30px; overflow:hidden; background-color:#fff;position:absolute;z-index:1;">004</td>
<td style="min-width:74px; max-width:74px; left: 30px; width: 74px; overflow:hidden;background-color:#fff;position:absolute;z-index:1;">自动表格</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >最后一列</td>
</tr>
<tr>
<td style="min-width:30px; max-width:30px; left:0; width:30px; overflow:hidden; background-color:#fff;position:absolute;z-index:1;">005</td>
<td style="min-width:74px; max-width:74px; left: 30px; width: 74px; overflow:hidden;background-color:#fff;position:absolute;z-index:1;">自动表格</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >最后一列</td>
</tr>
<tr>
<td style="min-width:30px; max-width:30px; left:0; width:30px; overflow:hidden; background-color:#fff;position:absolute;z-index:1;">006</td>
<td style="min-width:74px; max-width:74px; left: 30px; width: 74px; overflow:hidden;background-color:#fff;position:absolute;z-index:1;">自动表格</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >最后一列</td>
</tr>
<tr>
<td style="min-width:30px; max-width:30px; left:0; width:30px; overflow:hidden; background-color:#fff;position:absolute;z-index:1;">007</td>
<td style="min-width:74px; max-width:74px; left: 30px; width: 74px; overflow:hidden;background-color:#fff;position:absolute;z-index:1;">自动表格</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >最后一列</td>
</tr>
<tr>
<td style="min-width:30px; max-width:30px; left:0; width:30px; overflow:hidden; background-color:#fff;position:absolute;z-index:1;">008</td>
<td style="min-width:74px; max-width:74px; left: 30px; width: 74px; overflow:hidden;background-color:#fff;position:absolute;z-index:1;">自动表格</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >最后一列</td>
</tr>
<tr>
<td style="min-width:30px; max-width:30px; left:0; width:30px; overflow:hidden; background-color:#fff;position:absolute;z-index:1;">009</td>
<td style="min-width:74px; max-width:74px; left: 30px; width: 74px; overflow:hidden;background-color:#fff;position:absolute;z-index:1;">自动表格</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >最后一列</td>
</tr>
<tr>
<td style="min-width:30px; max-width:30px; left:0; width:30px; overflow:hidden; background-color:#fff;position:absolute;z-index:1;">010</td>
<td style="min-width:74px; max-width:74px; left: 30px; width: 74px; overflow:hidden;background-color:#fff;position:absolute;z-index:1;">自动表格</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >56454自动</td>
<td style="min-width: 100px; max-width: 100px; width: 100px;" >最后一列</td>
</tr>
</table>
</div>
</div>
<div style="background-color:#eee;overflow:hidden;top:150px; width:100%; z-index:2;position:absolute;">
<div style="margin-left:108px; width:auto;overflow-x:scroll;overflow-y:hidden;" onscroll='divScroll(this);'>
<div style="width:630px; height:1px;"></div>
</div>
</div>
</div>
<div id="scrollDiv_y" style="display:block; overflow-x:hidden; overflow-y:scroll; position:absolute; top:22px; right:0px; height:118px; padding-bottom:10px;" onscroll='divYScroll(this);'>
<div style="width:1px; height:194px;"></div>
</div>
</div>
</div>
</body>
</html>
总体结构与定位计算
一、总体结构:
页面基础结构由DIV和TABLE构成。固定列采用绝对定位方式固定,且每列需指定固定宽度。为解决横竖滚动条的显示问题,表头和表体分别被两层DIV包裹。滚动条采用虚拟形式,通过JS控制模拟正常DIV滚动条的效果,并固定在特定位置。
二、定位:
固定列需设置为绝对定位,通过left属性控制左侧位移。为确保固定列浮动于上层,z-index设为1。为保证竖滚动条正常显示,表体外层DIV也采用绝对定位,这使得滚动条同样需绝对定位。此外,表头、表体及滚动条的内层DIV通过margin-left属性控制左侧外边距,以留出固定列的偏移空间。
宽度与高度计算细节
二、宽度计算:
每列宽度需指定固定值,并需注意一个关键点:必须同时设置min-width和max-width属性,且其值与width相等。表头和表体内层DIV宽度设为auto以自适应表格宽度,外层DIV宽度为100%。最外层DIV通过padding-right属性控制右侧内边距,从而为竖滚动条留出空间。
三、高度计算:
由于绝对定位的存在,整个表格组件的高度需明确指定,可通过计算得出。同时,竖滚动条的top值也需进行相应计算。
虚拟滚动条与事件处理
四、滚动条:
本方案的一个显著特点是使用虚拟滚动条。通过一个与表格等宽、高度为1像素的DIV来模拟表体DIV的横向滚动条,竖滚动条同理。采用此形式的原因在于:横向滚动条这样处理更美观;对于竖滚动条,这样处理后表头和表体外层DIV宽度无需额外计算,均为100%。若不使用此法,存在滚动条时,表头和横向滚动条需空出竖滚动条宽度的位移,否则无法对齐。虽然此计算不复杂,但在某些情况下可能存在隐患,此处不再展开。
五、滚动事件:
由于表体滚动条被隐藏,鼠标滚轮默认失效,因此需用JS处理鼠标滚轮事件。本文样例代码兼容常见浏览器。重点在于同时编写了onmousewheel和onwheel事件:onmousewheel用于兼容IE,在计算滚动距离时,需注意deltaY和wheelDelta属性的差异。
方案优缺点分析
六、优缺点分析:
本文方案已精简,旨在阐明原理,实际应用中更为复杂。该设计考虑了广泛的兼容性,包括浏览器兼容性及各种场景。若需求简单,仍有简化空间。
该方案的优势在于,若封装为组件,因HTML结构简单(表头和表体均为TABLE),JS控制代码简洁,易于维护。缺点则是计算量较大。我们认为该方案更适合开发组件的场景,对于静态页面而言则略显繁琐。
以上所述即为本文的全部内容,希望对大家有所帮助。