最新要闻
- 【聚看点】车评人怒批埃尔法不装防撞梁:一老板出车祸 后排都变形了
- 00后发明全新上班方式!谁说电动轮椅只适合老年人 天天快播报
- 比亚迪腾势N7正式上市 售价30.18万起 特斯拉Model Y还香吗
- 2023果冻行业市场现状分析-天天观速讯
- 【播资讯】高温之下 导游及游客安全更要上心
- 大模型中的“罗翔” 北大团队发布法律大模型ChatLaw-天天快资讯
- 日本核污染水排海符合标准!合法性不受认可:日本被爆贿赂IAEA百万欧元 天天快资讯
- 【当前热闻】女子推开保护罩让孩子摸文物 管理方:禁止触摸
- RTX 4060遇冷:没想到降价这么快、这么狠! 全球讯息
- 东风雪铁龙公司回应C6拖欠补贴:8月15日前全部退还
- 国家气候中心:今年盛夏极端天气气候事件偏多 高温、强降水等来袭_世界资讯
- 实时焦点:AMD锐龙5 7500F真身浮现:Zen4第一次没了核显、价格诱人
- 男子被鱼刺扎伤截掉中指:感染病死率高的创伤弧菌 医生提醒
- 每日看点!高温叠加少雨:北方2地出现特旱
- 【天天速看料】32:9比例曲面屏!红魔公布49英寸QD-OLED电竞显示器
- 要闻速递:42万买奥迪A7L新车 先异响后地库抛锚!第一视角感受车主无奈
手机
光庭信息跌4.57% 2021上市超募11亿2022扣非降74% 时快讯
搜狐汽车全球快讯 | 大众汽车最新专利曝光:仪表支持拆卸 可用手机、平板替代-环球关注
- 光庭信息跌4.57% 2021上市超募11亿2022扣非降74% 时快讯
- 搜狐汽车全球快讯 | 大众汽车最新专利曝光:仪表支持拆卸 可用手机、平板替代-环球关注
- 视点!美国首位女总统即将诞生?拜登恐怕要提前下岗,美政坛迎来变局?
- 当前速递!用理想仪器实现更好的颗粒 德国新帕泰克亮相CPHI & PMEC China获好评
- 微粒贷怎么申请开通 开通方法如下
- 焦点简讯:心疼!这位40岁的云南缉毒警,已是满头白发
家电
记录--组件库的 Table 组件表头表体是如何实现同步滚动?|每日时讯
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
前言
在使用 Vue 3 组件库 Naive UI 的数据表格组件 DataTable 时碰到的问题,NaiveUI 的数据表格组件 DataTable 在固定头部和列的示例中,在键盘操作下表格横向滚动会有问题,本文是记录下解决问题的过程,并最后向 Naive UI 提交 PR。
(资料图)
问题复现步骤:
- 鼠标点击表头,此时按键盘左右键,表格横向滚动没问题;
- 再把鼠标移入表体,按键盘左右键,会发现表头滚动而表体没动。
相关 issue:
- github.com/tusen-ai/na…
- github.com/tusen-ai/na…
- github.com/tusen-ai/na…
Naive UI 中的实现
打开 Chrome 开发者工具,可以看到固定头和列中,表头和表体是由两个 table 元素单独实现,我们遇到的问题可能就是表头表体同步滚动的实现有点问题,具体得看源码中的实现验证下。
在 DataTable 组件源码中涉及到滚动的相关文件 src/data-table
:
use-scroll.ts
处理表格滚动事件DataTable.tsx
表格组件tableParts/Header.tsx
表头组件tableParts/Body.tsx
表体组件
我们按照复现步骤的操作来看看滚动的时候做了什么?
大致过程就是表头或表体滚动时会触发 scroll
事件,在监听 scroll
事件的回调中获取 scrollLeft
值,然后设置另一部分的 scrollLeft
来同步滚动。
1. 鼠标点击表头,按键盘左右键,表头表体横向滚动正常
当鼠标移入表头时,表头监听了 mouseenter
事件,会设当前 scrollPartRef
为 head
;按键盘左右键使表头滚动,触发了表头 scroll
事件,执行 handleTableHeaderScroll
方法,该方法是由 DataTable.tsx
组件提供(provider
),在 use-scroll.ts
中的 useScroll
方法导出的;handleTableHeaderScroll
作用主要是来同步表体的滚动及一些样式设置;代码如下:
//use-scroll.tsfunctionhandleTableHeaderScroll():void{//判断当前滚动的部分是不是表头,scrollPartRef值为head或bodyif(scrollPartRef.value==="head"){//beforeNextFrameOnce的作用是每一帧只调用一次传入的回调//syncScrollState的作用是同步滚动表体和一些样式设置beforeNextFrameOnce(syncScrollState)}}
2. 再把鼠标移入表体,按键盘左右键,表头横向滚动正常而表体没动
当将鼠标移入表体时,表体监听了 mouseenter
事件,会设当前 scrollPartRef
为 body
,在按键盘左右键时表头滚动,执行了表头 scroll
事件回调 handleTableHeaderScroll
,但不满足判断条件 scrollPartRef.value === "head"
,没有执行 syncScrollState
方法。
问题原因:在移入表体后,此时鼠标焦点依旧在表头,所以按键盘左右键时,仍然是表头滚动及触发 scroll
事件,执行的是 handleTableHeaderScroll
方法,而此时 scrollPartRef
的值为 body
,导致没有执行 syncScrollState
方法来同步表体的 scrollLeft
值,最终表现表体没有跟随表头滚动。
其他组件库中的实现
在解决问题前,观察了一下各组件库表格组件中固定表头和列的示例,看看是否有类似问题,查看之后发现表头和表体都是通过两个 table 元素来单独实现,这就遇到一个问题,因为是两个 table 元素,那怎么实现表头表体同步滚动呢?以及怎么解决在 Naive UI 中遇到的问题?
Element Plus
Element Plus 中,当鼠标点击表头,按键盘左右键是无法横向滚动的,只有鼠标焦点在表体上才能横向滚动;也就是滚动只能由表体滚动带动表头滚动。
源码实现里面它的表格滚动条不像 Naive UI 表头和表体都设为 overflow: scroll
来产生滚动,而是在表体包了一层封装的滚动条组件,表头则没有包直接设为 overflow: hidden
不让滚动;在滚动表体时,获取滚动条组件的 scrollLeft
来同步表头的 scrollLeft
。Table 组件源码点这里
Ant Design Vue
Ant Design Vue 的表现同 Element Plus,表头无法滚动,只能由表体滚动带动表头滚动。
源码实现原理跟 Element Plus 差不多,它的表格表头也是设为 oveflow: hidden
无法滚动,表体设为 overflow: auto scroll
来滚动,然后监听表体的滚动事件 scroll
获取 scrollLeft
来同步表头 scrollLeft
。Table 组件源码点这里
问题解决过程
问题复现
根据 Naive UI DataTable 源码中固定头和列时同步滚动的实现方式,搞一个 demo 复现问题。
代码实现思路:滚动分为表头、表体两个部分,监听各自的滚动事件 scroll
,滚动某一个部分时,在 scroll
事件处理函数中通过设置另一部分 scrollLeft
来同步滚动,因为在设置 scrollLeft
时也会触发 scroll
事件,这样就会造成死循环,所以需要判断当前滚动的是哪个部分,这里用 scrollPartRef
变量来记录,在鼠标移入表头时设 scrollPartRef
为 "head"
,在鼠标移出表头或移入表体时设 scrollPartRef
为 ‘body’
,然后在滚动事件处理回调 handleHeaderScroll
/ handleBodyScroll
方法中,判断 scrollPartRef
是不是为对应的 "head"
/ "body"
,是的话才会执行 syncScrollState
方法来同步另一部分的 scrollLeft
。
具体代码如下:
Demo 在线地址:[Bug] NaiveUI-DataTable-scrolling-sync (demo) - codesandbox
/***NaiveUIDataTable组件滚动同步demo实现*/scrollPart:{{scrollPartRef}}
head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head
问题分析
想下使浏览器原生滚动条滚动的交互操作有几种:
- 触控板手势滚动
- 鼠标按住滚动条拖动
- 键盘 shift 键 + 鼠标滚轮滚动
- 鼠标焦点在滚动内容上,接着用键盘左右键滚动
- 还有么?
概括下来就是分为三种:触控板、鼠标、键盘,作者实现的时候可能没考虑到键盘操作的场景。
问题原因上面分析过,在鼠标点击表头后移入表体,scrollPartRef
会被设为 "body"
,而此时鼠标焦点依旧在表头上,当操作键盘方向键,滚动的是表头,执行它的 handleHeaderScroll
方法,但不满足判断条件 scrollPartRef.value === "head"
,没有执行 syncScrollState
方法,导致表体没有同步滚动。
现在能想到的解决方案有两种:一种就是参考其他组件库中的方案,不让表头能主动滚动,只能由表体滚动带动表头滚动;一种就是修复复现操作下的问题。
我认为现在的实现方式太复杂了,需要监听表头、表体的鼠标事件 mouseenter
、mouseleave
来预设当前滚动部分 scrollPartRef
,如果按照这种思路,要修复键盘操作下的问题,是不是还要监听当前焦点focus
事件然后做判断,有没有更简单的方式?
解决思路
我的想法是只监听 scroll
事件能不能做到同步滚动,现在有表头、表体两个滚动部分,那么可分为主动滚动和被动滚动;在未滚动前,我们不预设主动滚动是哪部分(即不设置 scrollPartRef
),等到真正滚动的时候,如果我们能知道主动滚动的是哪部分,这样就能获取主动滚动部分的 scrollLeft
,去设置被动滚动部分的 scrollLeft
,以此实现同步滚动。如果大家有更好的解决思路,欢迎讨论!
怎么判断主动滚动的是哪部分?
当时给 Naive UI 提 PR 的时候想到的是第一种思路,但是我觉得第二种思路更好一点,后续重新提交一个。
第一种思路:在每次滚动中取表头或表体的 scrollLeft
和上一次滚动记录下的 lastScrollLeft
(初始为 0)比较来判断当前主动滚动部分是哪个,这里取表头部分的 scrollLeft
,如果差值不为 0,说明当前主动滚动部分为表头(即 scrollPartRef = ‘head’
),否则为表体。
constscrollPartRef=ref();//当前主动滚动部分constheaderRef=ref();constbodyRef=ref();functionhandleHeaderScroll(){if(scrollPartRef.value!=="body"){syncScrollState();}else{//每次滚动结束,置空scrollPartRef.value=undefined;}}functionhandleBodyScroll(){if(scrollPartRef.value!=="head"){syncScrollState();}else{//每次滚动结束,置空scrollPartRef.value=undefined;}}letlastScrollLeft=0;functionsyncScrollState(){if(!scrollPart.value){//取header的scrollLeft跟上一次滚动记录的scrollLeft比较constdirectionHead=lastScrollLeft-headerRef.value.scrollLeft;//不为0说明header滚动了,主动滚动即为head,否则为bodyscrollPart.value=directionHead!==0?"head":"body";}if(scrollPart.value==="head"){lastScrollLeft=headerRef.value.scrollLeft;bodyRef.value.scrollLeft=lastScrollLeft;}else{lastScrollLeft=bodyRef.value.scrollLeft;headerRef.value.scrollLeft=lastScrollLeft;}}第二种思路:主动滚动部分肯定会先触发滚动事件,所以可以在表头或表体的
scroll
事件处理函数中判断 scrollPartRef
是否存在,不存在则将 scrollPartRef
设为对应的 "head"
\ "body"
(即为主动滚动部分),然后调用syncScrollState
同步被动滚动部分的scrollLeft
。代码如下:functionhandleHeaderScroll(){if(!scrollPart.value){scrollPartRef.value="head"}if(scrollPartRef.value==="head"){syncScrollState();}else{//每次滚动结束,置空scrollPartRef.value=undefined}}functionhandleBodyScroll(){if(!scrollPart.value){scrollPartRef.value="body"}if(scrollPartRef.value==="body"){syncScrollState();}else{//每次滚动结束,置空scrollPartRef.value=undefined}}functionsyncScrollState(){if(scrollPart.value==="head"){lastScrollLeft=headerRef.value.scrollLeft;bodyRef.value.scrollLeft=lastScrollLeft;}else{lastScrollLeft=bodyRef.value.scrollLeft;headerRef.value.scrollLeft=lastScrollLeft;}
怎么判断滚动结束?
我们需要在每一次滚动结束后置空 scrollPartRef
,否则同步会出问题。被动滚动部分在被设置 scrollLeft
时也会触发 scroll
事件,而被动滚动部分的 scroll
事件会晚于主动滚动的 scroll
事件触发,所以可以认为被动滚动事件执行完滚动就结束了,在它的事件回调处理中的 else 分支置空 scrollPartRef
,如上代码所示。
但是这样判断结束在 Safari 中还是有点有问题的,详见下面遗留问题。
完整代码
Demo 在线地址:[Fix] NaiveUI-DataTable-scrolling-sync (demo) - codesandbox
<scriptsetuplang="ts">import{ref}from"vue"constscrollPart=ref()//当前主动滚动部分constheaderRef=ref()functionhandleHeaderScroll(){if(scrollPart.value!=="body"){console.log("<< scrollPart:{{scrollPart}}
head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head-head body-body-body-body-body-body-body-body-body-body-body-body-body-body-body-body-body-body-body-body-body-body-body >>")syncScrollState()}else{scrollPart.value=undefinedconsole.log("<< >>")console.log("\n")}}constbodyRef=ref()functionhandleBodyScroll(){if(scrollPart.value!=="head"){console.log("<< >>")syncScrollState()}else{scrollPart.value=undefinedconsole.log("<< >>")console.log("\n")}}letlastScrollLeft=0functionsyncScrollState(){//取header的scrollLeft跟上一次滚动记录的scrollLeft比较constdirectionHead=lastScrollLeft-headerRef.value.scrollLeft//不为0说明header滚动了,主动滚动即为head,否则为bodyscrollPart.value=directionHead!==0?"head":"body"if(scrollPart.value==="head"){lastScrollLeft=headerRef.value.scrollLeftbodyRef.value.scrollLeft=lastScrollLeft}else{lastScrollLeft=bodyRef.value.scrollLeftheaderRef.value.scrollLeft=lastScrollLeft}console.log("finalscrollLeft",lastScrollLeft)}</script>
PR
github.com/tusen-ai/na…
遗留问题
虽然解决了键盘操作的问题,但是后面发现在 Safari 浏览器中使用触控板快速滑动会有点小问题,这是由于在 Safari 中,滚动会有弹性效果导致的,复现步骤:
- 鼠标先点击表头,让焦点在表头上;
- 鼠标移入表体,使用触控板快速滑动,有弹性效果;
- 按键盘左键,第一下表头滚动,表体没动。
原因是因为快速滑动表体(主动滚动部分)时,表头(被动滚动部分)到达边界后就不会再触发 scroll
事件了,而表体因为弹性效果依旧在触发 scroll
事件,导致 scrollPartRef
一直为 "body"
,未被清空,后面再按键盘滚动表头时,事件处理中条件 scrollPartRef.value === "head"
不满足,未执行 syncScrollState
方法,导致了表体未同步滚动。根本原因就是我们没法正确判断滚动什么时候结束,如果能知道什么时候滚动结束,那么在滚动结束时重置 scrollPartRef
就不会有问题了。
听说现在有了 scrollend
,但是兼容性不行。
本文转载于:
https://juejin.cn/post/7251786381483376695
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
关键词:
记录--组件库的 Table 组件表头表体是如何实现同步滚动?|每日时讯
2023容器网络趋势:CNI网络插件逐渐普及,Kube-OVN受欢迎度持续攀升 今日最新
环球微动态丨手机刷了PE类原生系统后的体验
环球即时看!债市日报:7月4日
【聚看点】车评人怒批埃尔法不装防撞梁:一老板出车祸 后排都变形了
00后发明全新上班方式!谁说电动轮椅只适合老年人 天天快播报
比亚迪腾势N7正式上市 售价30.18万起 特斯拉Model Y还香吗
2023果冻行业市场现状分析-天天观速讯
每日消息!java基础之行为抽象
天天热资讯!华为云河图KooMap 共筑数字孪生底座 共建产业标杆
环球看热讯:商品日报(7月4日):纸浆刷新逾两个月新高 双焦走弱焦煤跌近3%
【播资讯】高温之下 导游及游客安全更要上心
大模型中的“罗翔” 北大团队发布法律大模型ChatLaw-天天快资讯
日本核污染水排海符合标准!合法性不受认可:日本被爆贿赂IAEA百万欧元 天天快资讯
【当前热闻】女子推开保护罩让孩子摸文物 管理方:禁止触摸
RTX 4060遇冷:没想到降价这么快、这么狠! 全球讯息
东风雪铁龙公司回应C6拖欠补贴:8月15日前全部退还
全球热推荐:文档在线预览 总结篇
Nftables栈溢出漏洞(CVE-2022-1015)复现
HawkEye产品深受市场认可,与多家行业龙头企业达成合作,共同开拓行业市场
国家气候中心:今年盛夏极端天气气候事件偏多 高温、强降水等来袭_世界资讯
实时焦点:AMD锐龙5 7500F真身浮现:Zen4第一次没了核显、价格诱人
男子被鱼刺扎伤截掉中指:感染病死率高的创伤弧菌 医生提醒
每日看点!高温叠加少雨:北方2地出现特旱
【天天速看料】32:9比例曲面屏!红魔公布49英寸QD-OLED电竞显示器
要闻速递:42万买奥迪A7L新车 先异响后地库抛锚!第一视角感受车主无奈
当前焦点!易基因: RRBS揭示基于DNA甲基化驱动基因的肾透明细胞癌预后模型的鉴定和验证|项目文章
每日热讯!数据万象AVIF图片压缩 - 小程序省流量利器
环球快资讯:从0开始,手写MySQL数据管理器DM
Python中对open读取文件内容时的mode模式解析
收评:A股三大股指窄幅震荡 汽车产业链股全天走强 当前视点
今日看点:华建集团下属公司水利院联合签署有关防洪能力提升工程合同
腾势N7做了违背王传福的决定!比亚迪首款智能车30万开卖
每日简讯:极摩客新一代迷你主机要上14代酷睿 GPU追赶RTX 3050
理想联合创始人沈亚楠退出公司股东行列:李想持股比例超95%
【当前热闻】董明珠谈预制菜:要让世界爱上中国味道
北科大用“卡脖子”技术做录取通知书!这种钢打破国外垄断
金丝兔压脚厂家(金丝兔)
ChatGPT 让人们大吃一惊——以下三项技术可能会带来改变
北斗卫星时钟同步服务器(卫星授时服务)天线安装意见-每日热门
每日消息!债券通助力国家金融市场对外开放稳步推进
国家气候中心:预计今年盛夏我国气候状况为一般到偏差 极端天气气候事件偏多 独家焦点
入汛以来189条河流发生超警以上洪水
小米又对华为提专利无效宣告请求 细节揭秘:涉及拍照、4G/LTE技术
不讲武德!扎克伯格偷家马斯克:推特大乱Meta趁机火速上线竞品 热资讯
天天观察:意大利原装进口:100%橙汁9.7元/L抄底0脂肪
网友称看电影《消失的她》时发现靠背有针:影院连道歉都没有
076期书生大乐透预测奖号:区间比012路比分析 天天热闻
哪里需要写哪里,FromServices注入 — ASP.NET CORE
《太极张三丰》观后感
海尔洗衣机宣传抗菌率大于99%被罚3万_时快讯
每日快看:小孩影厅排泄致退票家长拒赔偿 年纪小不懂事:网友热议为啥要带出来
滚动:镓锗出口管制!概念股应声大涨,业内:半导体产业或受影响
动车售卖盒饭出现双层价签 12306:15元卖成35元违规|全球简讯
RTX 4090 Ti四插槽巨型散热器流出:有人要价888888元
中青报谈文字失语症:别让垃圾短视频废了可思化 网友有话说 每日消息
男子偷住无人别墅多日喝光6瓶茅台:家政打扫卫生时才发现
威马汽车:公司近期开始还债复产 CEO沈晖限消令已撤销
柏拉图中的累计百分比怎么算出来的_百分比怎么算出来的
kernel pwn入门
当前要闻:柏瑞投资:亚洲高收益债券最具吸引力
高考中文题简单!老师称在越南学好中文工资更高:当地人不加班不为钱放弃生活
每日速看!199元起 雷柏VT9 PRO无线鼠标上架:68g轻量化设计
大梁+四驱!新一代哈弗H5回归:长城史上最大SUV_世界独家
全球观速讯丨谨防上当!女子网上找补漏报价800上门变4300:已有多人被骗
全球观速讯丨不到半价:八喜自选口味冰淇淋15大杯90g狂促99元
漫威系列电影观影顺序_威漫电影观影顺序
世界信息:linux Nginx+Tomcat负载均衡、动静分离
世界今亮点!CakePHP教程_编程入门自学教程_菜鸟教程-免费教程分享
js的中的函数(一)
语音房源码搭建技术分享之降噪功能详解
C#.NET Framework 使用BC库(BouncyCastle) RSA 私钥签名 公钥验签(验证签名) ver:20230704 每日观察
香港特区政府财政司司长陈茂波:进一步强化香港作为全球离岸人民币业务枢纽和国际风险管理中心的地位-看热讯
环球新动态:可转债市场首个退市整理期敲定 市场风格分化明显
天天快资讯丨普里戈任再次发声寻求支持:“你们会看到我们在前线取得新的胜利”
新能源硬派越野!比亚迪方程豹首款车型定名:叫“豹5”
天天快播:男子用共享电动车拦婚车要钱 司机曝内幕:专业闹喜人 不要红包只要100块
前有阿维塔 后有深蓝 “亲儿子”启源如何突围?-最新
荣耀全家桶来袭:手表、平板、电视全都有 焦点热门
环球消息!终于透明了!广州新规:网约车驾驶员端需显示抽成比例
每日热闻!昀冢科技:7月3日融资买入200.78万元,融资融券余额4987.8万元
B站内测搜索AI助手:输入“?”即可体验!
【笔试实战】LeetCode题单刷题-编程基础 0 到 1【二】 全球观天下
环球报道:Linux调优+Tomcat调优,超级干货,一定珍藏
三步搞定CentOS7下的MariaDB 10_天天观天下
前沿资讯!vue通用的增删改查按钮组件
制氧设备相关内容简介介绍图片_制氧设备相关内容简介介绍
广汽埃安副总经理:让特斯拉跑网约车 可能3个月车就不行了
百度专为学习打造!小度青禾学习手机第二款入网:支持5G
广汽“奇葩” 埃安凶猛_当前滚动
资讯:浙江仙居太阳像戴了美瞳:绝美彩色光晕 专家科普为何形成
当前观点:苹果Apple Beta短暂维护:iOS 17公测版要来了
亚太实业7月4日开盘涨停
spring启动流程 (3) BeanDefinition详解 每日播报
【天天报资讯】前端Vue自定义精美宫格菜单按钮组件 可设置一行展示个数 可设置成九宫格 十二宫格 十五宫格
景区放“丑女”雕塑被指侮辱女性,官方回应
微信支付每月免费提现额度引热议:1.2万免费提你会用吗?
要大涨价还买吗?iPhone 15系列新配色曝光:新渲染图亮相
复旦教授谈为何中国出不了马斯克 要对失败和试错足够包容:网友吐槽
新型进网许可标志启用 你买的手机是正品吗?最新查询方法来了_全球观察