为table表格,固定表头和表侧列(浮动表头表列)
工作中写的代码,仅作参考
更新时间 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)
}
})