实现双向固定表头的表格

实现目标

  1. 包含横向表头和纵向表头的表格
  2. 在横向滚动轴滚动时,横向表头随之滚动,纵向表头固定不变
  3. 在纵向滚动轴滚动时,纵向表头随之滚动,横向表头固定不变
  4. 效果图

    这里写图片描述

使用资源

  1. angularjs
  2. jquery
  3. sublime

实现

1.基本结构
由于滚动轴会占据一定位置所以在横向表头和纵向表头计算时需要排除掉滚动条的宽动,如果希望使用插件scrollbar.js。

scrollbar.js是由jQuery写成的自定义样式的滚动条插件,特点是不会占用位置,并且在鼠标移出时隐藏显示,移入时正常显示,具有较好的浏览器兼容性。如果在angular中使用的话,可以通过directive对其进行封装。

这里写图片描述

由图中展示,整体结构分为四块,固定标题,横向表头,纵向表头和表格内容,滚动条只存在于表格内容块中。使用CSS的绝对定位便可达到如图的效果。

html代码,如下所示

<!DOCTYPE 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-rowsecond-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-blockwhite-spaceoverflow等属性的使用,在其中还是用calc,CSS样式计算表达式,来计算相应的宽度和高度。


2.JS联动滚动处理

基本结构和样式编写完成后,已经可以实现表格内容的滚动,但是无法做到横向表头和纵向表头相对于内容滚动式的对应联动效果,这就需要通过js进行实现,在这里将主要使用jQuery的.css()方法和CSS中的transform-translate3d属性,以及js中event.target.scrollTopevent.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.scrollTopevent.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获取对应元素,监听表格内容的滚动事件,根据滚动的左边距和右边距对横向和纵向表头进行位移操作,即可实现最终效果。

再会,呵呵,祝好!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章