工作中写的代码,仅作参考

更新时间 20230329 :优化速度,主要是jquery.outerWidth()获取宽高性能很慢,使用了原生的element.getBoundingClientRect()替代
更新时间 20230323 :优化功能,在数据量较大时,避免浏览器卡掉

HTML:

    <div id="table-header-fixed" data-target="copy-data-table" data-copy-row="2" data-layout=""></div>
    <table class="table table-bordered table-hover" id="copy-data-table">
        <thead><tr><th>...</thead>
        <tbody><tr><td>...</tbody>
    </table>

JS 关键代码

        /**
         * 表头固定功能(已处理好表格点击处理)
         * 用法:在需要固定表头的表格 上方 添加代码:
         * <div id="table-header-fixed" data-target="copy-data-table" data-copy-row="1"></div>
         * data-target (目标表格的ID),data-copy-row (固定的行数量,不设置则默认为1)
         * 某些页面可能会出现浮动单元格对不齐,可尝试添加 data-layout="" 属性,值可选css table-layout 的值:auto|fixed|inherit|initial|unset, 默认为:fixed
         * 20211214更新:如果需要浮动的表头左侧再加上固定左侧边浮动层(横向滚动时显示在最上层),可以加上  data-fixed-left="3" 的属性,3即是浮动的列数量
         * 调用刷新的更新定位:
         * 刷新:window.updateFixedHeaderTable()
         * 定位:window.updateFixedHeaderTablePosition()
         */
        function  tableHeaderFixed() {
            var $obj = $("#table-header-fixed");
            if ($("#table-header-fixed").length < 1) return
            var d = $obj.data(), rows = d.copyRow || 1 ,columnNum = (d.fixedLeft || 1)-1, hasLeft = typeof d.fixedLeft != 'undefined';
            var $target = $('#'+d.target)
            var $copyTarget = $('<table id="fixed-header-table">')
            $copyTarget.attr('class',$target.attr('class'))
            $copyTarget.attr('style',$target.attr('style'))
            $copyTarget.css({
                position: 'fixed',
                top: -9999,
                margin: 0,
                background: '#fff',
                zIndex: 2,
                maxWidth: 'unset',
                tableLayout: typeof d.layout == 'undefined' ? 'fixed' : d.layout
            })
            $obj.html($copyTarget)
            var $copyTargetLeft = !hasLeft ? null : $copyTarget.clone().attr('id', 'fixed-header-table-left').css({zIndex:3});
            if ($copyTargetLeft) $obj.append($copyTargetLeft)
            $('html').css({ overflowX: 'auto', overflowY: 'auto', })
            $("body").css({overflowX: 'auto', width: $(window).innerWidth(), overflowY: 'auto', height: $(window).innerHeight()})
            $obj.on('click', '#fixed-header-table td, #fixed-header-table th', function () {
                var tdIdx = $(this).index(), tagName=$(this)[0].tagName, wrap = $(this).parent('tr').parent()[0].tagName, trIdx = $(this).parent().index();
                if (wrap == 'TABLE')
                    $target.find('tr:eq('+trIdx+')').find(tagName+":eq("+tdIdx+")").trigger('click')
                else
                    //有thead,tbody的情况
                    $target.find(wrap+':eq(0)').find('tr:eq('+trIdx+')').find(tagName+":eq("+tdIdx+")").trigger('click')
            })
            var holderHeight=0;
            window['updateFixedHeaderTable'] = function () {
                $target = $('#'+d.target)
                if ($target.length < 1) return;
                $copyTarget.css({width: $target.outerWidth(),});
                var $trs = $target.find('tr:lt('+rows+')').clone()
                $trs.attr('origin-id', function () {
                    this.id
                })
                $trs.removeAttr('id')
                if ($('thead', $target).length > 0) {
                    var $thead = $('<thead>')
                    $thead.attr('class', $('thead', $target).attr('class'))
                    $thead.attr('style', $('thead', $target).attr('style'))
                    $thead.html($trs)
                    $copyTarget.html($thead)
                } else {
                    $copyTarget.html($trs)
                }
                var fixTabOffset = $target.offset(), width = $target.outerWidth();
                $copyTarget.css({left:fixTabOffset.left, width: width})
                var leftWidth = 0
                let targetTd=$target.find('td,th');
                $copyTarget.find('td,th').each(function (index) {
                    var _$target = targetTd.eq(index)
                    if (_$target.length == 0) return true;
                    var _width = _$target[0].getBoundingClientRect().width
                    $(this).css({width: _width, maxWidth: 'unset'}).data('width', _width)
                    if (index < columnNum) leftWidth+=_width
                })

                holderHeight = $copyTarget.outerHeight()
                if (!$copyTargetLeft) return

                var minLeft = window.settingsMain == 'kai' ? 140 : 0;
                if (fixTabOffset.left < minLeft) $copyTargetLeft.css({width: leftWidth, left:minLeft})
                else $copyTargetLeft.css({width: leftWidth, left:fixTabOffset.left})
                var colspan = 0, $newTable = $($copyTarget.html());
                $copyTargetLeft.empty().append($newTable)
                $newTable.find("tr").each(function (index) {
                    if (index == 0) {
                        $("th,td", this).each(function (idx2) {
                            if (idx2>columnNum) return false;
                            colspan += parseInt($(this).attr('colspan') || 1)
                        })
                        colspan--
                        $("th:gt("+columnNum+"),td:gt("+columnNum+")", this).remove();
                    } else {
                        $("th:gt("+colspan+"),td:gt("+colspan+")", this).remove();
                    }
                })
            }
            window['updateFixedHeaderTablePosition'] = function () {
                if ($target.length == 0) return;
                var fixTabOffset = $target.offset();
                $copyTarget.css({left:fixTabOffset.left})
                if (!$copyTargetLeft) return;
                var minLeft = window.settingsMain == 'kai' ? 140 : 0;
                if (fixTabOffset.left < minLeft) $copyTargetLeft.css({left:minLeft})
                else $copyTargetLeft.css({left:fixTabOffset.left})
            }
            var prositionTimer
            var funcBodyScroll = function (e) {
                clearTimeout(prositionTimer)
                prositionTimer = setTimeout(function () {
                    if ($target.length == 0 ) return;
                    var offset = $target.offset()
                    if (offset.top < 0 - holderHeight) {
                        $copyTarget.css({top: 0})
                        $copyTargetLeft && $copyTargetLeft.css({top: 0})
                        $obj.height(holderHeight) //设置一下占位高度,解决因为突然出现的浮动头档住了表格内容的问题,让表格下沉
                    } else {
                        $copyTarget.css({top: -9999})
                        $copyTargetLeft && $copyTargetLeft.css({top: -9999})
                        $obj.height(0)
                    }
                    updateFixedHeaderTablePosition()
                }, 0)
            }
            var funcWindowResize = function (e) {
                var winHeight = $(window).innerHeight()
                $('body').css({height: winHeight})
                updateFixedHeaderTable();
                updateFixedHeaderTablePosition()
            }
            $('body').off('scroll.tableHeaderFixed', funcBodyScroll)
            $(window).off('resize.tableHeaderFixed', funcWindowResize)
            $("body").on('scroll.tableHeaderFixed',funcBodyScroll)
            $(window).on('resize.tableHeaderFixed',funcWindowResize)
            updateFixedHeaderTable()
        }
        tableHeaderFixed()
        window['tableHeaderFixed'] = tableHeaderFixed

        /**
         * 表格左侧列固定,仅支持一个页面1个表格
         * 用法:给表格定义ID:copy-data-table,定义属性 data-copy-column (固定的列数量,不设置则默认为1)
         * 调用刷新的更新定位:
         * 刷新:window.resetCopyTable()
         * 定位:window.updateCopyTableProsition()
         *
         //左侧固定表格的点击事件处理,兼容现有表格,新表格可以复制以下代码到页面(或者调用 setTimeout(function () {regFixedLeftTableTdClickEventHandler()}, 0) 方法)
         $(document).on('click', '#fixed-left-table td, #fixed-left-table th', function () {
            var tdIdx = $(this).index(), tagName=$(this)[0].tagName, wrap = $(this).parent('tr').parent()[0].tagName, trIdx = $(this).parent().index();
            if (wrap == 'TABLE')
                $("#copy-data-table").find('tr:eq('+trIdx+')').find(tagName+":eq("+tdIdx+")").trigger('click')
            else
                //有thead,tbody的情况
                $("#copy-data-table").find(wrap+':eq(0)').find('tr:eq('+trIdx+')').find(tagName+":eq("+tdIdx+")").trigger('click')
        })
         */
        function tableColumnLeftFixed(domId) {
            domId = domId || 'copy-data-table'
            var $obj = $("#"+domId), $copyTable;
            if ($obj.length < 1) return
            $('html').css({ overflowX: 'auto', overflowY: 'auto', })
            $("body").css({overflowX: 'auto', width: $(window).innerWidth(), overflowY: 'auto', height: $(window).innerHeight()})
            window['resetCopyTable'] = function () {
                $("#fixed-left-table").remove();
                var columnNum = ($obj.data('copy-column') || 1)-1;
                if (columnNum<0) return

                $copyTable = $('<table id="fixed-left-table">')
                $copyTable.attr('class',$obj.attr('class'))
                $copyTable.attr('style',$obj.attr('style'))

                $copyTable.appendTo('body');

                var fixTabOffset = $obj.offset();
                $copyTable.css({position:'fixed',left:-9999,top:fixTabOffset.top,width: 'auto', margin: 0, background: '#fff', zIndex: 1});
                if ($('thead', $obj).length > 0) {
                    var $thead = $('<thead>')
                    $thead.attr('class',$('thead', $obj).attr('class'))
                    $thead.attr('style',$('thead', $obj).attr('style'))
                    $thead.appendTo($copyTable)
                }
                if ($('tbody', $obj).length > 0) {
                    var $tbody = $('<tbody>')
                    $tbody.attr('class',$('tbody', $obj).attr('class'))
                    $tbody.attr('style',$('tbody', $obj).attr('style'))
                    $tbody.appendTo($copyTable)
                }
                var rowspan = {}, first = {}, l = $obj.find('tr').length;
                $obj.find('tr').each(function (idx) {
                    var $this=$(this), $tr = $this.clone(), tagName = $this.parent()[0].tagName
                    setTimeout(function () {
                        $tr.attr('origin-id', function () {
                            this.id
                        })
                        $tr.removeAttr('id')
                        //指定本行需要保留的列数量,0表示第1列
                        var _cut = $this.data('cut'), $td = $tr.children('td,th');
                        _cut = typeof _cut == 'undefined' ? columnNum : _cut
                        $td.filter(":gt("+_cut+")").remove();

                        $td = $td.children("td,th");
                        var num = columnNum;
                        $td.each(function (tdIdx) {
                            if ($this.children('td,th').eq(tdIdx).is(':hidden')) return true //continue
                            var _rowspan = parseInt($(this).attr('rowspan')) || 1
                            var _colspan = parseInt($(this).attr('colspan')) || 1
                            if (_colspan>1) {
                                num -= _colspan - 1;
                            }
                            if (_rowspan > 1) {
                                rowspan[tdIdx] = _rowspan-1
                                first[tdIdx] = true
                            }
                        })
                        for (var tdIdx in rowspan) {
                            if (rowspan[tdIdx] > 0 && first[tdIdx] == false) {
                                rowspan[tdIdx]--
                                num--
                            } else if (first[tdIdx]) {
                                first[tdIdx] = false
                            }
                        }
                        $td.filter(":gt("+num+")").remove();
                        if (tagName == 'THEAD') $tr.appendTo($('thead', $copyTable))
                        else if (tagName == 'TBODY') $tr.appendTo($('tbody', $copyTable))
                        else $tr.appendTo($copyTable)

                        if (idx == l-1) updateCopyTableWidth()
                    }, 0)
                });

                function updateCopyTableWidth() {
                    var $tr1td = $copyTable.find('tr') //复制表
                    var $tr2td = $obj.find('tr') //原表
                    var tdh = $obj.data('tdh') || 'width' //复制表单元格高度获取方式 outer | width
                    var w = 0; //整个复制表的宽度计算
                    $tr1td.each(function (index) {
                        //设置每一个复制表单元格的宽高
                        $(this).find('td,th').each(function (tdIdx,) {
                            var $this=$(this), _$target = $tr2td.eq(index).find('td,th').eq(tdIdx)
                            if (_$target.length == 0) return true;
                            var _offset = _$target[0].getBoundingClientRect()
                            var _w = _offset.width, _h = tdh == 'outer' ? _offset.height : _offset.height;
                            setTimeout(function () {
                                $this.css({width: _w, height: _h, verticalAlign: 'middle'});
                            }, 0)
                            if (index == 0) w += _w
                        })
                    });
                    $copyTable.css({width: w + 2});
                    $obj.trigger('fixed.left.update')
                    updateCopyTableProsition()
                }
                updateCopyTableProsition()
            }
            window['updateCopyTableProsition'] = function () {
                if ($obj.length == 0 || !$copyTable ) return;
                var fixTabOffset = $obj.offset();
                var minLeft = window.settingsMain == 'kai' ? 140 : 0;
                if (fixTabOffset.left<minLeft) {
                    $copyTable.data('opened', 1).css({left:minLeft,top:fixTabOffset.top})
                } else {
                    $copyTable.removeData('opened').css({left:-9999,top:fixTabOffset.top})
                }
            }
            var prositionTimer
            var funcBodySCroll = function (e) {
                clearTimeout(prositionTimer)
                prositionTimer = setTimeout(function () {
                    updateCopyTableProsition()
                }, 0)
            }
            var funcWinSize = function (e) {
                $('body').css({width: $(window).innerWidth()})
                updateCopyTableProsition()
            }
            $('body').off('scroll.tableColumnLeftFixed',funcBodySCroll)
            $(window).off('resize.tableColumnLeftFixed',funcWinSize)
            $('body').on('scroll.tableColumnLeftFixed',funcBodySCroll)
            $(window).on('resize.tableColumnLeftFixed',funcWinSize)
            resetCopyTable()
            updateCopyTableProsition()
        }
        tableColumnLeftFixed()
        window['tableColumnLeftFixed'] = tableColumnLeftFixed

        //注册表格左侧浮动单元格的点击事件
        window['regFixedLeftTableTdClickEventHandler'] = function () {
            $(document).on('click', '#fixed-left-table td, #fixed-left-table th', function () {
                var tdIdx = $(this).index(), tagName=$(this)[0].tagName, wrap = $(this).parent('tr').parent()[0].tagName, trIdx = $(this).parent().index();
                if (wrap == 'TABLE')
                    $("#copy-data-table").find('tr:eq('+trIdx+')').find(tagName+":eq("+tdIdx+")").trigger('click')
                else
                    //有thead,tbody的情况
                    $("#copy-data-table").find(wrap+':eq(0)').find('tr:eq('+trIdx+')').find(tagName+":eq("+tdIdx+")").trigger('click')
            })
        }

页面中多个表格,滚动到某个表格时固定该表格浮动表头的实现方法

<!--浮动表头需要dom-->
<div id='table-header-fixed' data-target='copy-data-table' data-copy-row='1' data-layout='auto'></div>
<table id="copy-data-table"></table>
<!--浮动表头需要dom-->

<!--真实数据表格-->
<table id="form-table-test1" class='table table-hover table-bordered table-center ag-data-table' style="margin-bottom: 0;width: auto">
...
</table>
<table id="form-table-test2" class='table table-hover table-bordered table-center ag-data-table' style="margin-bottom: 0;width: auto">
...
</table>
        //滚动后固定表格头部
        var targetCurrent=null, formHeadProsition = {}
        //保存每个目标表格的位置
        function updateFormHeadProsition() {
            let top = $('body').scrollTop()
            $('.ag-data-table').each(function () {
                var target = $(this)
                formHeadProsition[target] = $(this).offset().top + top - 50
            })
        }
        $('body').scroll(function (e) {
            var top = $(this).scrollTop(), target = null
            for (var i in formHeadProsition) {
                if (top >= formHeadProsition[i]) target = i
            }
            if (target && targetCurrent !== target) {
                targetCurrent = target
                $('#table-header-fixed').data('target', targetCurrent)
                tableHeaderFixed()
                tableColumnLeftFixed(target)
                updateFixedHeaderTablePosition()
                // console.log('1')
            }
            if (target === null) {
                $('#fixed-footer-btns').css('bottom', -100)
                // console.log('2')
            } else {
                // console.log('3')
                $('#fixed-footer-btns').css('bottom', 20)
            }
        })

标签: 浮动表头

添加新评论