稀土掘金技术社区 2024年11月03日
不是吧,刚毕业几个月的前端,就写这么复杂的表格??
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文分享了一个前端开发中遇到的复杂表格案例,该表格需要实现左右滚动联动、中间信息栏动态显示、悬浮操作栏等功能。作者详细介绍了使用三个饿了么UI表格组件实现该表格的思路和方法,包括协同滚动、信息栏位置计算、悬浮操作栏的实现等。同时,文章也探讨了开发过程中遇到的挑战,例如滚动事件死循环、信息栏位置计算、样式穿透等,并分享了相应的解决方案。最后,作者还提到了表格优化的问题,例如数据更新导致的卡顿,并计划进行进一步优化。文章适合前端开发人员学习和参考,了解复杂表格的开发技巧和优化方法。

🤔 **三个表格组件协同滚动实现**: 作者采用了三个`el-table`组件横向排列的方式构建复杂表格,并通过监听每个表格的滚动事件,同步更新其他两个表格的滚动位置,实现了左右滚动联动和上下滚动联动。然而,这种方式容易导致滚动事件死循环,作者通过类似防抖的方式,使用一个公共的防抖开关控制事件执行,减少了函数执行次数,优化了性能。例如,在左表格滚动时,清除定时器,设置定时器,并在定时器回调函数中更新中间和右表格的滚动位置,以及更新信息栏位置和左右滚动位置。 在开发过程中,需要注意滚动事件的触发机制,以及如何避免死循环,可以使用防抖或节流等技术优化滚动事件的执行频率。同时,也要考虑不同浏览器和设备下的兼容性问题,确保滚动效果的流畅性和稳定性。

📊 **信息栏动态显示与位置计算**: 信息栏需要横穿三个表格,且位置需要根据表格数据动态调整。作者通过绝对定位将信息栏脱离文档流,并覆盖在表格数据之上。为了确定信息栏的位置,作者采用了二分查找算法,找到对应数据所在的行索引,再结合行高、表头高度和表格滚动高度计算出信息栏的`top`值,并通过滚动事件动态更新其位置。此外,作者还设置了信息栏的显示和隐藏条件,当`top`值小于0或大于表格高度时隐藏,其他情况显示。 在实现信息栏动态显示时,需要注意数据更新和滚动事件的同步性,避免信息栏位置错乱。同时,要考虑信息栏的样式和交互设计,使其与表格整体风格保持一致,并方便用户理解和使用。例如,可以考虑使用动画效果,使信息栏的显示和隐藏更加流畅自然。

🖱️ **悬浮操作栏的实现**: 表格的每一列悬浮时,需要在最左边或最右边显示操作栏。作者巧妙地利用CSS的`hover`选择器实现了这一功能。在DOM结构中,固定一列用于存放操作图标,并使用CSS选择器,当鼠标悬浮在列上时,选择到对应的操作栏div并改变其`display`属性,使其显示出来。 在实现悬浮操作栏时,需要注意操作栏的样式和位置,使其与表格整体风格保持一致,并方便用户操作。同时,要考虑操作栏的交互设计,例如,可以使用动画效果,使操作栏的显示和隐藏更加流畅自然。此外,还可以考虑添加一些提示信息,帮助用户理解操作栏的功能。

🎨 **样式穿透修改UI组件样式**: 由于UI组件通常自带样式,为了满足设计需求,需要修改UI组件的内部样式。作者使用样式穿透技术,通过包裹组件并使用`::v-deep`选择器,修改`el-table`组件的内置样式,例如,修改表格固定列的边框颜色、单元格的填充和换行等。 在使用样式穿透时,需要注意选择器的 specificity,避免样式冲突。同时,要谨慎修改UI组件的内置样式,避免影响其他组件的功能和样式。建议在修改前备份原始样式,以便出现问题时可以恢复。此外,还可以考虑使用CSS Modules或其他样式隔离方案,避免样式污染。

原创 落课 2024-11-03 09:02 重庆

点击关注公众号,“技术干货”及时达!

点击关注公众号,“技术干货” 及时达!


最近负责了项目的一个大迭代,但是有一个非常复杂的表格,怎么个复杂法呢。正常来说,我们前端的一个表格大多是使用ui框架中的现成表格组件,顶多就在表格里面内嵌一些输入框,选择框,改改内嵌样式什么的!但是这个表格,就真的很牛逼,给大家分享一下(如下),顺便记录一下。然后本人很菜,是个刚毕业的前端彩笔,看个乐子就行。

乍一看是不是也挺简单,就这?反正对我来说,是开发的想死了,我写这个表格使用三个饿了么的表格组件。是的,三个表格组件实现成同一个表格,经常写表格的大佬们应该一看就知道痛点在哪里了。

技术难点

    表格的协同滚动

    左右两个表格是要能够左右滚动

    表格悬浮展示操作栏

    屏幕中间有个信息栏穿越整个表格的

    改变el-table的内置样式

如下图,左中右分别都是一个表格组件,为什么要这个写,细心的盆友可以看到1和3的表格是可以「左右滚动」的,从「盒模型」的角度来看,就不可能写在同一个元素中了,而且以我目前的工作经验来看,滚动好像只能使用原生的,顶多改变一下样式。至于2的话,应该是可以不用这个表格组件也能写的,但是我为了方便就这样写了,使用组件的话很多样式就可以cv了。

前面说过,这个表格是由三个表格组件实现的,所以这个三个表格之间的协同滚动就非常的重要了,「比如我在1表格上下滚动,2和3表格也要对应的进行滚动」,同理在2或者3表格中上下滚动,对应的其他两个表格也应该进行滚动。还有就是「1表格和3表格是有一个镜像左右滚动」的。

这样听起来好像非常easy是吧,就是「给每个表格都监听滚动事件,滚动的时候去改变其余的两个表格滚动条」。对整体思路就是这样,但是我写着写着就「死循环」了哈哈哈哈哈。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>        * {
            padding0;
            margin0;        }
        .box {
            width800px;
            height450px;
            /* background-color: red; */
            margin0 auto;
            margin-top200px;
            display: flex;        }
        .common {
            width300px;
            height450px;
            background-color: blue;
            overflow: auto;        }
        .middle {
            width200px;
            background-color: aqua;
            overflow: auto;        }
        .content {
            height1000px;        }
    
</style>
</head>
<body>
    <div class="box">
        <div class="common" id="left">
            <div class="content" style="width: 500px;"></div>
        </div>
        <div class="middle" id="middle">
            <div class="content"></div>
        </div>
        <div class="common" id="right">
            <div class="content" style="width: 500px;"></div>
        </div>
    </div>
    <script>
        const leftO = document.querySelector("#left")
        const middleO = document.querySelector("#middle")
        const rightO = document.querySelector("#right")
        leftO.addEventListener("scroll", (e) => {
            console.log("左表格")
            const top = e.target.scrollTop
            const left = e.target.scrollLeft            middleO.scrollTop = e.target.scrollTop            rightO.scrollTop = e.target.scrollTop            rightO.scrollLeft = left
        },true)
        middleO.addEventListener("scroll", (e) => {
            console.log("中表格")
            const top = e.target.scrollTop            leftO.scrollTop = e.target.scrollTop            rightO.scrollTop = e.target.scrollTop
        },true)
        rightO.addEventListener("scroll", (e) => {
            console.log("右表格")
            const left = e.target.scrollLeft
            const top = e.target.scrollTop            leftO.scrollTop = e.target.scrollTop            middleO.scrollTop = e.target.scrollTop            leftO.scrollLeft = left
        },true)
    
</script>
</body>
</html>

这是一个小demo,大家可以复制一下去本地跑一下,可以发现

    不管是上下滚动,还是左右滚动,都是触发同一个滚动事件

    去设置scrollTop或者scrollLeft也会触发滚动事件函数

也就是说,这三个表格,「任意一个表格滚动其实都是触发了三个表格的滚动事件」,当然这里是没有死循环的,因为我还有一个需求是在上下滚动的时候同时去改变左右滚动地方,所以就会导致「他调用她,她反过来又调用他」,所以就死循环了,后面是通过类似「防抖」的写法去解决的(如下vue3的写法),为什么说他是类似呢,因为它是三个事件函数去共用同一个防抖开关,所以他最后还是会多执行一次,有兴趣的大佬可以去试试。用这种写法也可以「减少函数执行次数」,也是一种「性能优化」哈哈哈哈。

// updateMassge和setLeftFn是两个方法就不具体写了
// 防抖开关
const flag = ref(null)
// 左表格滚动事件
const scrollLeftFn = e => {
  const scrollLeft = e.target.scrollLeft
  const scrollTop = e.target.scrollTop  clearTimeout(flag.value)
  flag.value = setTimeout(() => {
    console.log("左表格执行")
    // updateMassge这个方法是去更新信息栏的一个方法,后面会有提到    updateMassge(scrollTop)    middleTable.value.setScrollTop(scrollTop)    rightTable.value.setScrollTop(scrollTop)
    // 设置左右滚动    setLeftFn(scrollLeft)  })}
// 右表格滚动事件
const scrollRightFn = e => {
  const scrollLeft = e.target.scrollLeft
  const scrollTop = e.target.scrollTop  clearTimeout(flag.value)
  flag.value = setTimeout(() => {
    console.log("右表格执行")
    // updateMassge这个方法是去更新信息栏的一个方法,后面会有提到    updateMassge(scrollTop)    middleTable.value.setScrollTop(scrollTop)    leftTable.value.setScrollTop(scrollTop)
    // 设置左右滚动    setLeftFn(scrollLeft)  })}
// 中间表格滚动事件
const scrollMiddleFn = e => {
  const scrollTop = e.target.scrollTop  clearTimeout(flag.value)
  flag.value = setTimeout(() => {
    console.log("中间表格执行")
    // updateMassge这个方法是去更新信息栏的一个方法,后面会有提到    updateMassge(scrollTop)  })}

我觉得这应该是这个表格最抓马的地方了,为什么呢,这个表格整体由三个表格组件「横向排列」形成的,所以想要横穿这三个表格放一个div,只能让它「脱离文档流」,去父相子绝的定位它。更恶心的地方是这个信息栏的位置是「需要根据表格的数据去确定位置」,如下图,信息栏中的数字是143.59,那它的位置就应该是在143和144之间。大佬们可以思考一下要怎么实现呢?

我说说我是怎么做的。首先,前面说过只能通过「绝对定位」去脱离文档流,将它放到它应该放到的地方去。应该说是「覆盖」,对!在这个信息栏的下面其实是有一行假数据的。所以最重要是去「正确的找到它的位置」(top)然后在表格滚动的时候「动态」的去更新它的位置也就是前面说的 「updateMassge」 方法,以及「如何控制它正确的显示和隐藏」(display)因为在它滚动到顶部或者底部的时候应该把它隐藏掉。

寻找top

如何寻找top呢,首先就是维护好一个下标变量,有点像之前写过的一个算法题,「一个有顺序的数组,去找到他的插入位置,让这个数组依然有序」,我一开始使用的findIndex写的,然后项目提测后在一些特定的数据会有bug哈哈哈,因为在findIndex没找到的时候返回值是-1,就会有一些问题。其实就是一个「二分法」就能解决!

function findInsertPosition(nums, target{
  let left = 0;
  let right = nums.length - 1;
  while (left <= right) {
    let mid = Math.floor(left + (right - left) / 2);
    if (nums[mid] === target) {
      return mid;
    } else if (nums[mid] < target) {
      left = mid + 1;
    } else {
      right = mid - 1;    }  }
  return left;}

拿到这个下标索引之后,就是一些把他的计算公式写出来,我当时搁那拿本子画了好久哈哈哈,要找到表格滚动高度scrollTop和top的一个关系。

如图,可以得出

index * 行高 + 表头高 = scrollTop + top

所以

top = (index * 行高 + 表头高) - scrollTop

然后就是在「滚动的事件函数中去动态的更新top」以及去「寻找出现信息栏和隐藏信息栏的临界点」

这个临界点也很好找,当「top小于0或者大于表格高度」的时候就隐藏,其他时候就显示

在前面的动态图中,可以看到这个表格,每「悬浮一列最左边或者最右边都会出现一个操作栏」,这个一开始不知道从何下手,因为我看element-ui中并没有提供能实现的这种效果的属性或者方法。不过还是给我想出来办法来了,不得不说我还是有点实力的哈哈哈哈哈。

其实实现起来也非常简单,就是通过css的hover选择器去实现的,大家看一下伪代码应该就能明白是怎么实现的。

dom结构

<el-table ref="leftTable" :data="KZData">
        // 操作列
        <el-table-column label="" fixed="left" width="100px" align="center">
          <template #default="scope">
            <div class="operation-show red">
              <div class="function-box">
                <img src="图标地址"/>
                <span></span>
                <span></span>
              </div>
            </div>
          </template>
        </el-table-column>

        // 其他列
        <el-table-column v-for="(item, index) in Column">            {{ scope.row[item.prop] }}
        </el-table-column>

</el-table>

样式

// 悬浮操作栏.operation-show {  display: none;}.el-table__body tr:hover > td .operation-show {  display: block;}

是吧,是不是非常简单,在最左边去「固定一个列去存放这些操作图标」,然后就是「css选择器」的使用,悬浮列的时候用选择器去选择到对应的div去改变他的display属性。

这些ui组件都是自带样式的,而我们要根据ui设计图去开发,所以学会去改变ui组件的内部样式是非常重要的,而一般我们都是通过「样式穿透」去改变的

举个例子,当我们要改变el-table的样式时,首先需要将这个ui组件用盒子去包裹一层,并给定一个类名,用这个类名进行样式穿透。

<div class="left-table">
   <el-table>
   </el-table>

</div>

然后就是使用::v-deep去选定内置组件的类名或者标签名,然后在里面编写你想要的样式,至于内置组件的相关信息就需要使用F12「检查」去查看了。

.left-table {    ::v-deep(.el-table-fixed-column--left) {      border-right: 1px solid red;    }    ::v-deep(.el-table-fixed-column--right) {      border-right: 1px solid red;    }    ::v-deep(.cell) {      padding: 0;      white-space: nowrap;      overflow: hidden;      text-overflow: ellipsis;    }    ::v-deep(.el-table thead th) {      font-weight: 400;    }}

这个表格除了这些主要的功能,还有很多小小的功能,其实现在都还在优化,特别是性能卡顿问题,因为这个表格还要实时的更新数据,所以在数据大量更新的时候根本使用不了,卡死了,也不知道我能不能把这个卡顿的问题优化好。「表格优化」的文章 https://juejin.cn/post/7430026536215281698

点击关注公众号,“技术干货” 及时达!


阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

前端开发 表格组件 滚动事件 样式穿透 性能优化
相关文章