zoukankan      html  css  js  c++  java
  • 原生javascript 固定表头原理与源码

        我在工作中需要固定表头这个功能,我不想去找,没意思。于是就写了一个,我写的是angularjs 自定义指令 起了个 "fix-header" ,有人叫  “freeze-header” ,算了,看心情吧,最近心情不太好就不改了~~~

    想了想,我还是改成原生吧,angularjs就是个毛毛~~~。

    先讲一下思路:

        1.想一想,再想一想,肯定用定位了,具体是绝对定位还是固定定位,看实际情况;

        2.clone 一份thead元素,用于再创建一个定位的表头;

        3.clone有点坑,不能clone当前元素的 实际 宽高 和 事件, 只能获取原先的加上;

        4.加scroll事件;

        5.我很开心,成功了~~~~;

    先把页面创建了 ,就叫fixHeaderDemo.html,如下:

     1 <!DOCTYPE html>
     2 <html lang="en">
     3 <head>
     4     <meta charset="UTF-8">
     5     <title>Title</title>
     6     <style>
     7         *{box-sizing: border-box;}
     8         .table{max-width: 100%; width: 100%;border-collapse: collapse;}
     9         .table>thead>tr>th{background-color: #059ca1; color:#FFF; padding-top:10px;padding-bottom: 10px;}
    10         .table>thead>tr>th,.table>tbody>tr>td{
    11             border:1px solid #CCC;
    12         }
    13     </style>
    14 </head>
    15 <body>
    16 <div style=" 80%; margin:40px auto; height: 100px;overflow: auto;position: relative;">
    17     <table class="table">
    18         <thead>
    19         <tr>
    20             <th>Name1</th>
    21             <th>Name2</th>
    22             <th>Name3</th>
    23         </tr>
    24         </thead>
    25         <tbody>
    26         <tr>
    27             <td>亚瑟</td>
    28             <td>荆轲</td>
    29             <td>程咬金</td>
    30         </tr><tr>
    31             <td>亚瑟</td>
    32             <td>荆轲</td>
    33             <td>程咬金</td>
    34         </tr><tr>
    35             <td>亚瑟</td>
    36             <td>荆轲</td>
    37             <td>程咬金</td>
    38         </tr><tr>
    39             <td>亚瑟</td>
    40             <td>荆轲</td>
    41             <td>程咬金</td>
    42         </tr><tr>
    43             <td>亚瑟</td>
    44             <td>荆轲</td>
    45             <td>程咬金</td>
    46         </tr><tr>
    47             <td>亚瑟</td>
    48             <td>荆轲</td>
    49             <td>程咬金</td>
    50         </tr><tr>
    51             <td>亚瑟</td>
    52             <td>荆轲</td>
    53             <td>程咬金</td>
    54         </tr><tr>
    55             <td>亚瑟</td>
    56             <td>荆轲</td>
    57             <td>程咬金</td>
    58         </tr><tr>
    59             <td>亚瑟</td>
    60             <td>荆轲</td>
    61             <td>程咬金</td>
    62         </tr><tr>
    63             <td>亚瑟</td>
    64             <td>荆轲</td>
    65             <td>程咬金</td>
    66         </tr><tr>
    67             <td>亚瑟</td>
    68             <td>荆轲</td>
    69             <td>程咬金</td>
    70         </tr><tr>
    71             <td>亚瑟</td>
    72             <td>荆轲</td>
    73             <td>程咬金</td>
    74         </tr><tr>
    75             <td>亚瑟</td>
    76             <td>荆轲</td>
    77             <td>程咬金</td>
    78         </tr>
    79         </tbody>
    80     </table>
    81 </div>
    82 </body>
    83 </html>

    上面的都太小儿科了,js才是关键。

    其实真的很简单,有兴趣还能在优化:

    第一步,先来个构造函数,这个构造函数接收一个参数,也就是你要固定的那个表格,代码如下:

    1 function FixHeader(tableElement){
    2     this.tableElement=tableElement;
    3 }

    第二步,写FixHeader构造函数的方法:

    1 FixHeader.prototype={
    2     constructor:FixHeader,
    3     init:function(){
    4       //这个位置是初始化的位置      
    5     }
    6 };

    第三步,其实我们的滚动是表格外面有个div父级元素,设置了他的最大高度,当超过这个最大高度就显示滚动条。那么,我们在初始化函数中肯定先获取div和表头元素等一些初始的事情;

    1  init:function(){
    2     //获取表格的父级元素
    3     this.tableParent=this.tableElement.parentNode;
    4     //获取thead元素
    5     this.header=this.tableElement.querySelector('thead');
    6     //克隆thead元素
    7     this.cloneHeader=this.header.cloneNode(true);
    8  }

    第四步,我们要用克隆的数据,往表格中插入一个固定的表头,可能会问为什么要clone一下呢?因为如果直接操作原来的表头数据会直接影响,我们不是要去动原来的东西,那些东西已经完美了;如果你有兴趣可以

          尝试一下,你会收获很大。在FixHeader原型中加了一个cloneFixHeader函数

    1 cloneFixHeader:function(){
    2     this.cloneHeader.className='cloneThead';
    3     this.cloneHeader.style.position='absolute';
    4     this.cloneHeader.style.top=0;
    5     this.cloneHeader.style.left=0;
    6     this.cloneHeader.style.right='-1px';
    7     this.tableElement.appendChild(this.cloneHeader);
    8 }

    上面的代码大家肯定明白,就是给clone的元素加一些样式和属性,加一个className是为了标识它是克隆的。那先看看效果,在初始化函数中调用cloneFixHeader()函数;

    运行的截图如下:

    我的天哪,这是怎么回事,怎么这个熊样了。哈哈。。。上面我已经说了clone不能把原来的宽高和事件一起克隆了,有些人是在css中把每个表格的宽度都写死,这是多么不好的做法,每次加或者减一列,都要修改css中的宽度。那我们是怎么解决的呢?太简单了,把每个元素的宽高设置到clone的元素中不就可以了吗!

     1 cloneFixHeader:function(){
     2     var cloneThs=this.cloneHeader.children[0].children,
     3         ths=this.header.children[0].children,
     4         th,cloneTh,i=0,l=cloneThs.length;
     5     for(;i<l;i++){
     6         th=ths[i];cloneTh=cloneThs[i];
     7         cloneTh.style.width=th.offsetWidth+'px';
     8         cloneTh.style.height=th.offsetHeight+'px';
     9     }
    10     this.cloneHeader.className='cloneThead';
    11     this.cloneHeader.style.position='absolute';
    12     this.cloneHeader.style.top=0;
    13     this.cloneHeader.style.left=0;
    14     this.cloneHeader.style.right='-1px';
    15     this.tableElement.appendChild(this.cloneHeader);
    16 }

    运行的结果如下(太完美了~~~):

    第五步,应该监听滚动事件了。先在原型中加一个函数叫listenerScroll ,代码如下:

     1 listenerScroll:function(ev){
     2     var top=ev.target.scrollTop,
     3             //用于判断是否已经添加上了,添加了就不让再次添加
     4             cloneThead=ev.target.querySelector('.cloneThead');
     5     if(top>0){
     6         if(cloneThead){
     7             cloneThead.style.display='block';
     8             cloneThead.style.top=top+'px';
     9             return;
    10         }
    11         this.cloneFixHeader();
    12     }else{
    13         if(cloneThead){
    14             cloneThead.style.display='none';
    15         }
    16     }
    17 },

    把在init中调用的cloneFixHeader() 函数换成监听事件:

    1 init:function(){
    2     this.tableParent=this.tableElement.parentNode;
    3     this.header=this.tableElement.querySelector('thead');
    4     this.cloneHeader=this.header.cloneNode(true);
    5     this.tableParent.addEventListener('scroll',this.listenerScroll.bind(this),false);
    6 },

    上面看似完美了,但是当你改变浏览器窗口大小时,你会惊讶于我是多么认真与细心,是的,当窗口变化时,一切都不完美了,原因你应该知道的呀!

    截图如下:

    亲,你想到了方法吗?是的,就是监听窗口大小变化,好了再加一个listenerResize函数:

     1 listenerResize:function(){
     2     var that=this;
     3     if(that.timer){
     4         clearTimeout(that.timer);
     5     }
     6     that.timer=setTimeout(function(){
     7         var top=that.tableParent.scrollTop;
     8         if(top<=0){
     9             return;
    10         }
    11         var globalWidth=that.global.innerWidth;
    12         if(that.globalWidth&&that.globalWidth==globalWidth){
    13             return;
    14         }
    15         that.globalWidth=globalWidth;
    16         var cloneThead=that.tableElement.querySelector('.cloneThead'),
    17              theads=that.tableElement.querySelectorAll('thead'),i,l=theads.length;
    18         for(i=0;i<l;i++){
    19             if(theads[i].className!='cloneThead'){
    20                 that.header=theads[i];
    21                 break;
    22             }
    23         }
    24         if(cloneThead){
    25             var cloneThs=cloneThead.children[0].children,
    26                     ths=that.header.children[0].children,
    27                     th,cloneTh;
    28             l=cloneThs.length;
    29             for(i=0;i<l;i++){
    30                 th=ths[i];cloneTh=cloneThs[i];
    31                 cloneTh.style.width=th.offsetWidth+'px';
    32                 cloneTh.style.height=th.offsetHeight+'px';
    33             }
    34             return;
    35         }
    36         that.cloneFixHeader();
    37     },60);
    38 },

    最后全部js代码如下:

     1 function FixHeader(tableElement, global) {
     2         this.tableElement = tableElement;
     3         this.global = global;
     4         this.timer = null;
     5     }
     6     FixHeader.prototype = {
     7         constructor: FixHeader,
     8         init: function () {
     9             this.tableParent = this.tableElement.parentNode;
    10             this.header = this.tableElement.querySelector('thead');
    11             this.cloneHeader = this.header.cloneNode(true);
    12             this.tableParent.addEventListener('scroll', this.listenerScroll.bind(this), false);
    13             this.global.addEventListener('resize', this.listenerResize.bind(this), false);
    14         },
    15         listenerScroll: function (ev) {
    16             var top = ev.target.scrollTop,
    17             //用于判断是否已经添加上了,添加了就不让再次添加
    18                     cloneThead = ev.target.querySelector('.cloneThead');
    19             if (top > 0) {
    20                 if (cloneThead) {
    21                     cloneThead.style.display = 'block';
    22                     cloneThead.style.top = top + 'px';
    23                     return;
    24                 }
    25                 this.cloneFixHeader();
    26             } else {
    27                 if (cloneThead) {
    28                     cloneThead.style.display = 'none';
    29                 }
    30             }
    31         },
    32         listenerResize: function () {
    33             var that = this;
    34             if (that.timer) {
    35                 clearTimeout(that.timer);
    36             }
    37             that.timer = setTimeout(function () {
    38                 var top = that.tableParent.scrollTop;
    39                 if (top <= 0) {
    40                     return;
    41                 }
    42                 var globalWidth = that.global.innerWidth;
    43                 if (that.globalWidth && that.globalWidth == globalWidth) {
    44                     return;
    45                 }
    46                 that.globalWidth = globalWidth;
    47                 var cloneThead = that.tableElement.querySelector('.cloneThead'),
    48                         theads = that.tableElement.querySelectorAll('thead'), i, l = theads.length;
    49                 for (i = 0; i < l; i++) {
    50                     if (theads[i].className != 'cloneThead') {
    51                         that.header = theads[i];
    52                         break;
    53                     }
    54                 }
    55                 if (cloneThead) {
    56                     var cloneThs = cloneThead.children[0].children,
    57                             ths = that.header.children[0].children,
    58                             th, cloneTh;
    59                     l = cloneThs.length;
    60                     for (i = 0; i < l; i++) {
    61                         th = ths[i];
    62                         cloneTh = cloneThs[i];
    63                         cloneTh.style.width = th.offsetWidth + 'px';
    64                         cloneTh.style.height = th.offsetHeight + 'px';
    65                     }
    66                     return;
    67                 }
    68                 that.cloneFixHeader();
    69             }, 60);
    70         },
    71         cloneFixHeader: function () {
    72             var cloneThs = this.cloneHeader.children[0].children,
    73                     ths = this.header.children[0].children,
    74                     th, cloneTh, i = 0, l = cloneThs.length;
    75             for (; i < l; i++) {
    76                 th = ths[i];
    77                 cloneTh = cloneThs[i];
    78                 cloneTh.style.width = th.offsetWidth + 'px';
    79                 cloneTh.style.height = th.offsetHeight + 'px';
    80             }
    81             this.cloneHeader.className = 'cloneThead';
    82             this.cloneHeader.style.position = 'absolute';
    83             this.cloneHeader.style.top = 0;
    84             this.cloneHeader.style.left = 0;
    85             this.cloneHeader.style.right = '-1px';
    86             this.tableElement.appendChild(this.cloneHeader);
    87         }
    88     };

    调用方式如下:

    1  new FixHeader(document.querySelector('.table'), window).init();

    总结:

    表头固定可以用了,不过由于我知识有限,可能上面有错误的地方,请大家批评指出。

  • 相关阅读:
    Samba 4.0 RC3 发布
    SymmetricDS 3.1.7 发布,数据同步和复制
    Express.js 3.0 发布,Node.js 的高性能封装
    GIFLIB 5.0.1 发布,C语言的GIF处理库
    jQuery UI 1.9.1 发布
    SVN Access Manager 0.5.5.14 发布 SVN 管理工具
    DynamicReports 3.0.3 发布 Java 报表工具
    HttpComponents HttpClient 4.2.2 GA 发布
    AppCan 2.0 正式发布,推移动应用云服务
    Ruby 2.0 的新功能已经冻结
  • 原文地址:https://www.cnblogs.com/zhangkunweb/p/6142251.html
Copyright © 2011-2022 走看看