1.基本结构
由于滚动轴会占据一定位置所以在横向表头和纵向表头计算时需要排除掉滚动条的宽动,如果希望使用插件scrollbar.js。
scrollbar.js是由jQuery写成的自定义样式的滚动条插件,特点是不会占用位置,并且在鼠标移出时隐藏显示,移入时正常显示,具有较好的浏览器兼容性。如果在angular中使用的话,可以通过directive对其进行封装。
由图中展示,整体结构分为四块,固定标题,横向表头,纵向表头和表格内容,滚动条只存在于表格内容块中。使用CSS的绝对定位便可达到如图的效果。
html代码,如下所示
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>双表头固定表格实现title>
<link rel="stylesheet" type="text/css" href="fixHeaderTable.css">
<link rel="stylesheet" type="text/css" href="base.css">
head>
<body ng-app="app">
<div class="fixed-header-table" ng-controller="tableController">
<div class="first-row">
<div class="fixed-title">
div>
<div class="fixed-row-header">
<ul id="rowHeader" class="row-header-list">
<li class="row-header-item" ng-repeat="row in mockData.rowHeader">
<span class="row-header-text">{{row}}span>
li>
ul>
div>
div>
<div class="second-row">
<div class="fixed-col-header">
<ul id="colHeader" class="col-header-list">
<li class="col-header-item" ng-repeat="col in mockData.colHeader">
<span class="col-header-text">{{col}}span>
li>
ul>
div>
<div id="scrollPanel" class="content-wrapper">
<ul class="content-list" ng-repeat="col in mockData.content">
<li class="content-item" ng-repeat="row in col">
<span class="content-text">{{row}}span>
li>
ul>
div>
div>
div>
<script src="node_modules/angular/angular.min.js">script>
<script src="node_modules/jQuery/tmp/jquery.js">script>
<script src="fixHeaderTable.js">script>
body>
html>
说明:将整体结构分成两行first-row
和second-row
,并在first-row
中实现固定标题和横向表头的结构,在second-row
中实现纵向表头和表格内容结构。
注意:在生成表格内容,横向表头标题和纵向表头标题时,使用angular的ng-repeat
指令,根据数据循环生成dom
展示结构,详情将官方API。
CSS代码,如下所示
/*整体结构样式*/
.fixed-header-table {
width: 600px;
height: 600px;
margin: 80px auto;
background: #eee;
position: relative;
}
/*第一行样式*/
.first-row {
width: calc(100% - 15px);
background: red;
height: 40px;
margin-right: 15px;
}
/*固定标题样式*/
.fixed-title {
position: absolute;
top: 0;
left: 0;
width: 40px;
height: 40px;
background: yellow;
z-index: 2;
}
/*第二行样式*/
.second-row {
background: green;
width: 100%;
height: calc(100% - 40px);
position: relative;
}
/*横向表头样式*/
.fixed-row-header {
height: 40px;
padding-left: 40px;
z-index: 0;
overflow: hidden;
/*在这里设置overflow属性,已隐藏超出的标题*/
}
.row-header-list {
white-space: nowrap;
/*因为标题横向排列时为inline-block样式,此属性使其不自动换行*/
height: 100%;
}
.row-header-item {
width: calc(100% / 5);
display: inline-block;
line-height: 40px;
height: 100%;
text-align: center;
border-left: 1px solid black;
}
.row-header-item:first-child {
border-left: none;
}
/*纵向表头样式*/
.fixed-col-header {
position: absolute;
top: 0;
left: 0;
width: 40px;
height: calc(100% - 15px);
background: blue;
overflow: hidden;
/*在这里设置overflow属性,已隐藏超出的标题*/
}
.col-header-list {}
/*纵向时不涉及li的横向排列问题,无需设置white-space*/
.col-header-item {
height: 30px;
line-height: 30px;
text-align: center;
border-bottom: 1px solid #fff;
}
.col-header-item:first-child {
border-bottom: none;
}
.col-header-text {
color: #fff;
}
/*表格内容样式*/
.content-wrapper {
position: absolute;
top: 0;
left: 40px;
width: calc(100% - 40px);
height: 100%;
background: pink;
white-space: nowrap;
/*因为表格内容分为纵向和横向,横向为inline-block样式,此属性使其不自动换行*/
overflow: auto;
/*用于展示横纵滚动条*/
}
.content-list {
display: inline-block;
width: calc(100% / 5);
}
.content-item {
height: 30px;
line-height: 30px;
text-align: center;
border: 1px solid black;
}
.content-text {
font-size: 12px;
}
注意:在编写样式时,需要注意代码中注释的几个位置,包括inline-block
,white-space
,overflow
等属性的使用,在其中还是用calc
,CSS样式计算表达式,来计算相应的宽度和高度。
2.JS联动滚动处理
基本结构和样式编写完成后,已经可以实现表格内容的滚动,但是无法做到横向表头和纵向表头相对于内容滚动式的对应联动效果,这就需要通过js进行实现,在这里将主要使用jQuery的.css()
方法和CSS中的transform-translate3d
属性,以及js中event.target.scrollTop
和event.target.scrollLeft
属性。
a..css()
方法:jQuery的内置方法,通过对象(key-value)在JavaScript文件中对指定的DOM
元素的样式进行变更;
b.transform-translate3d
:为CSS样式,标准写法为
transform:translate3d(x,y,z);
其中x,y,z为在横向,纵向和前后的移动距离,详情见transform详解。在这里使用translate3d是为了强制开启浏览器的3d加速效果,以保证滚动式的平滑过渡效果,在这里需要注意的是,连带滚动的DOM元素(在这里指横向表头和纵向表头)需要在发生滚动的元素之外,否则在safari中会出现闪烁、摇摆的现象,在Chrome中如果数据量较大时同时会出项相应问题。
另外,如果在angular中使用此方式进行联动时,尽量避免表格中的双向绑定数量控制在一定范围内,并且不要在监听事件时使用$apply()
,$digest
等深层检测方法,否则会出现严重卡顿现象。
c. event.target.scrollTop
和event.target.scrollLeft
:是当前时间对象中包含的元素的滚动上边距和滚动左边距,详见
JavaScript代码,如下所示。
angular.module('app', [])
.controller('tableController', ['$scope', '$timeout', function($scope, $timeout) {
/**
* 生成虚拟数据
* @return {[type]} [description]
*/
function mockData() {
var _mockData = {
rowHeader: [],
colHeader: [],
content: []
};
var NUM = 50;
var i = NUM;
while (i) {
_mockData.rowHeader.push('row' + ((NUM + 1 - i) < 10 ? '0' + (NUM + 1 - i) : (NUM + 1 - i)));
_mockData.colHeader.push('col' + ((NUM + 1 - i) < 10 ? '0' + (NUM + 1 - i) : (NUM + 1 - i)));
var j = NUM;
while (j) {
_mockData.content[(NUM - i)] = _mockData.content[(NUM - i)] ? _mockData.content[(NUM - i)] : [];
_mockData.content[(NUM - i)].push('content' + ((NUM + 1 - i) < 10 ? '0' + (NUM + 1 - i) : (NUM + 1 - i)) + '-' + ((NUM + 1 - j) < 10 ? '0' + (NUM + 1 - j) : (NUM + 1 - j)));
j--;
}
i--;
}
return _mockData;
}
$scope.mockData = mockData();
$timeout(function() {
var _scrollPanel = $('#scrollPanel');
var _rowHeader = $('#rowHeader');
var _colHeader = $('#colHeader');
_scrollPanel.on('scroll', function(event) {
// 根据表格内容横向滚动的距离,将横向表头在x方向进行translate
_rowHeader.css({
transform: 'translate3d(-' + event.target.scrollLeft + 'px,0,0)'
});
// 根据表格内容纵向滚动的距离,将纵向表头在y方向进行translate
_colHeader.css({
transform: 'translate3d(0,-' + event.target.scrollTop + 'px,0)'
});
});
});
}]);
文件中虚拟数据生成函数基本无需关注,最主要的部分为:
$timeout(function() {
var _scrollPanel = $('#scrollPanel');
var _rowHeader = $('#rowHeader');
var _colHeader = $('#colHeader');
_scrollPanel.on('scroll', function(event) {
// 根据表格内容横向滚动的距离,将横向表头在x方向进行translate
_rowHeader.css({
transform: 'translate3d(-' + event.target.scrollLeft + 'px,0,0)'
});
// 根据表格内容纵向滚动的距离,将纵向表头在y方向进行translate
_colHeader.css({
transform: 'translate3d(0,-' + event.target.scrollTop + 'px,0)'
});
});
});
在angular中使用jQuery的方法需要在外部包裹$timeout
,以确保angular双向绑定数据的及时更新。之后通过id获取对应元素,监听表格内容的滚动事件,根据滚动的左边距和右边距对横向和纵向表头进行位移操作,即可实现最终效果。
再会,呵呵,祝好!!