依旧是jQuery+html架构的前端项目,依旧用不了element.ui的组件。但产品已经提过无数遍想要表头固定、列固定的报表展示,没办法,只能借由排期不算太满的工作时间手肝一个HTML+JS的表头/列固定Table组件,命名为 fixedscrolltable 。
一、基本思路
参考el-table交互可知,想要固定表头、固定列,需要将主表拆成4块:主表表头、主表表体;左侧列表头、左侧列表体。
这里还补充了class名为table-leftmodel的div,是为了突出左侧列固定时的shadow。
二、数据渲染
确认四块区域后,接下来就是往里填数据。
假如表头项是动态获取的,那么表单数据应该分为headers和rows:
2.1 拼接表头
用到 isfixed 和 fixednum 传值,满足部分固定和随机列固定。
// 主表的表头
var header_html = '<tr>';
var fixheader_html = '';
// 固定列的表头
var leftheader_html = '<tr>';
for (var h = 0; h < header.length; h ++) {
header_html += '<th>' + header[h] + '</th>';
fixheader_html += '<div>' + header[h] + '</div>';
if (isfixed == 'true' && h < fixednum) {
leftheader_html += '<th>' + header[h] + '</th>';
}
}
header_html += '</tr>';
leftheader_html += '</tr>';
2.2 拼接表体
和拼接表头类似,搭配css样式设置body_html和leftbody_html。
注意:如果列固定需要合并单元格,则需要提前处理rows,计算rowspan和colspan。
2.3 模块组装
到这一步为止,仅展示数据。table自适应th和td导致的样式错乱还要再处理......
$(obj).empty().append(header_html).append(body_html); // 主表(包含表头)
$('#' + types + ' .table-box .boxhead').empty().append(fixheader_html); // 主表的表头
if (isfixed == 'true') {
// true设置列固定,则显示固定列
$('#' + types + ' .table-box .boxlefthead').empty().append(leftheader_html).show();
$('#' + types + ' .table-box .boxleftbody').empty().append(leftbody_html).show();
} else {
// true不设置列固定,只按固定表头展示
$('#' + types + ' .table-box .boxlefthead').empty().hide();
$('#' + types + ' .table-box .boxleftbody').empty().hide();
}
三、统一样式
已知<table>标签存在css属性table-layout,这一属性的默认值为automatic —— 也就是自适应布局。简而言之,table内部的单元格会单元格内容自动拉伸,如果某行的td内容过长,那么相应列的宽度就会拉长。
这个时候就要用到fiexed属性值 —— 给th(也就是table表头)设置宽度。
由此展开思路,想要统一样式,首先得获取主表th的各项宽度,再赋值给其他三个模块。
3.1 获取主表表单的th宽度
$.each($('#' + types + 'boxbody tr:first th'), function(i, item) {
var wid = $(item)[0].getBoundingClientRect().width.toFixed(3);
var borderwidth = $(item).css('border-right-width').split('px')[0];
borderwidth = Number(borderwidth);
widthlist.push(wid - 12 - borderwidth);
});
知识点1:为什么使用getBoundingClientReact().width?
起先,通过width()方法获取元素宽度,此方法会自动四舍五入小数,导致渲染结果有细微错位。
故寻找另一种方法,也就是直接获取视图下元素的真实属性,包括内边距、边框。
知识点2:为什么要获取border-width?
由于获取宽度时引用了视图维度的宽度值,所以需要兼容分辨率 ——
3.2 将主表表单的th宽度赋值给固定列
这里就不多做赘述,简言之就是用css()方法给需要固定的表单进行赋值。
其中还有一个重要逻辑:为了容错,能用原始表单展示尽量用原始表单。比如,当数据不足以在固定高度内滚动,就不需要固定表头;当数据不足以在固定宽度内滚动,就不需要左侧固定列。这样也是为了避免浏览器兼容样式有误,导致表单变形。
var boxbodywidth = $('#' + types + 'boxbody')[0].getBoundingClientRect().width.toFixed(3);
var boxtablewidth = $('#' + types + 'boxbody .table')[0].getBoundingClientRect().width.toFixed(3);
var boxbodyheight = $('#' + types + 'boxbody')[0].getBoundingClientRect().height.toFixed(3);
var boxtableheight = $('#' + types + 'boxbody .table')[0].getBoundingClientRect().height.toFixed(3);
boxbodywidth = Number(boxbodywidth);
boxtablewidth = Number(boxtablewidth);
boxbodyheight = Number(boxbodyheight);
boxtableheight = Number(boxtableheight);
if (boxtablewidth > boxbodywidth || boxtableheight > boxbodyheight) {
$.each($('#' + types + 'boxhead .table div'), function(i, kk) {
$(kk).css('width', widthlist[i] + 'px');
});
$('#' + types + 'boxhead').css('width', (boxbodywidth - 6) + 'px');
$('#' + types + 'boxhead').show();
} else {
$('#' + types + 'boxhead').hide();
}
3.3 重置表单的滚动位置
结合上面获取的变量,当表单重置前已经存在滚动时,需要通过scrollLeft和scrollTop手动设值;若不存在滚动,则不需要设置位置。
if (boxtablewidth > boxbodywidth) {
$('#' + types + 'boxbody').scrollLeft() && $('#' + types + 'boxbody').scrollLeft(0) && $('#' + types + 'boxhead .table').scrollLeft(0);
$('#' + types + 'boxbody').scrollTop() && $('#' + types + 'boxbody').scrollTop(0) && $('#' + types + 'boxhead .table').scrollTop(0);
}
四、监听滚动和页面可视区域
至此,基本的表单样式已经展示完成。
接下来的工作就是监听滚动,让表单能保持正常交互。
在测试人员的惯用操作下,还补充了监听页面可视区域 —— 重置表单。
五、总结
显然,自己造轮子是一件很麻烦且很没有必要的事情。但研发必须得满足产品提出的合理的用户需求,所以与其一次次拖延,不如早点调研、早点尝试,即使过程中有太多样式BUG、交互BUG。
所以,还是尽量不要用原始的框架。
PS.文章中并未提及每块的css样式。