摘要:今天,你的浏览器滚动了吗序在页面中,一个有高度或者宽度的容器是最常见的构成元素,而在其中的子元素有很大的概率超过父容器的尺寸限制,我们称之为溢出。
今天,你的浏览器 “滚动” 了吗? 序
在 Web 页面中,一个有高度或者宽度的容器是最常见的构成元素,而在其中的子元素有很大的概率超过父容器的尺寸限制,我们称之为“溢出”。而应对“溢出”,隐藏或者滚动是最常见的处理方式。滚动,作为 FEers 最经常处理的一种行为,却因为不同浏览器的各种表现形式让大家头痛不已,今天笔者从自身维护的组件出发,和大家分享一下自己在处理滚动和滚动条时遇到的问题,以及解决的办法,希望能够给你在解决同类问题时带来一些启发。同时本文也是 “从零开始的 React 组件开发之路” 系列的第二篇 - 表格篇番外。
遇到的问题 1:尴尬的双滚动条笔者在团队中负责基础组件的开发和维护,作为一个 B 类业务较多的团队,表格是最常用和需求最为旺盛的组件,假设有下方这样一个最简单的表格结构。
图1:最简单的表格结构
因为空间有限,我们希望表格高度限定,这样势必引入表格上下滚动的情况。同时,为了查看的方便,我们希望表格头不会一起滚动,即表格头需要固定,只有表格体滚动,因此我们需要把表格头和表格体放入两个容器中,而只让表格体的容器滚动。这是很普通的需求,也很容易实现,到目前为止一切都很顺利。
图2:表头固定,表格体滚动
然而这一切的美好,随着表格列数的增多,变的有了一点乌云。因为页面宽度受到电脑屏幕的限制,我们往往对表格的宽度也有限制,不可能无限延展开。那么如果有很多的列呢?显然,让表格左右滚动是一个很自然的想法。由于我们的表头和表格体在两个不同的容器中,让这件事变的稍微麻烦一点。关于如何让表头和表格体同步左右滚动,不是这篇文章讨论的重点,所以不做详细讨论,简单来说,我们通过监听横向滚动的事件和不断获取当前的 scrollLeft 来获得同步。有一个麻烦的点是,我们不希望只能通过滚动表格体来实现表格滚动,也希望可以通过滚动表头来实现表格的左右滚动,这就要求必须设置表头的容器为 overflow-x: auto。
图3:由于表格头和表格体都需要横向滚动,会引入两个滚动条。
这显然突破了大多数人对表格的认知,横向滚动会有两个滚动条,一点都不美观,需要我们在这个基础之上进行优化。表格体上的横向滚动条是没有问题的,主要问题在于表格头的,我们既希望能够横向滚动,又不想看到那个该死的滚动条,怎么办呢?想办法隐藏掉他就好了!首先我们设置表格头的容器 overflow-x: scroll 以保证无论是否需要横向滚动都会出现滚动条,方便我们简化状态的判断。接下来我们可以再设置表格头的容器 margin-bottom: -scrollBarWidth 来隐藏让他的父级帮忙吞掉这个滚动条,一切就大功告成了。但令人头大的是,滚动条的尺寸在不同浏览器,甚至是不同系统(例如 Windows 和 Mac 下的 chrome)中都是不一样的! 我们无法很暴力地通过制定一个固定的值来做这件事,因此我们需要在表格渲染到页面上去之后,主动去探测滚动条的宽度。
const scrollbarMeasure = { position: "absolute", top: "-9999px", width: "50px", height: "50px", overflow: "scroll", }; let scrollbarWidth; const measureScrollbar = () => { if (typeof document === "undefined" || typeof window === "undefined") { return 0; // 如果 document 不在,则证明不在浏览器环境,直接返回,兼容 node server render。 } if (scrollbarWidth) { return scrollbarWidth; // 滚动条在固定的环境下宽度不会改变,因此只做一次探测即可,优化性能。 } const scrollDiv = document.createElement("div"); Object.keys(scrollbarMeasure).forEach((scrollProp) => { if (Object.prototype.hasOwnProperty.call(scrollbarMeasure, scrollProp)) { scrollDiv.style[scrollProp] = scrollbarMeasure[scrollProp]; } }); // 创造一个远离人世的带滚动条的 div 用于探测,用户对于此无感知。 document.body.appendChild(scrollDiv); const width = scrollDiv.offsetWidth - scrollDiv.clientWidth; // 获取滚动条的宽度,offsetWidth 和 clientWidth 的区别,你能说清楚吗? document.body.removeChild(scrollDiv); // 探测完成,销毁测试元素,减少对页面的影响。 scrollbarWidth = width; // 缓存结果,优化性能 return scrollbarWidth; };遇到的问题 2:对不齐的表头和数据
通过上面的方法,我们成功地隐藏了表格头的横向滚动条。稍微滚动一下,一切正常,一切都按照预想的执行,直到一直滚动到头,问题出现了,最后一列的表头和下面的数据居然是对不齐的!!
这是怎么回事呢?图4:当表格体又可以左右滚动时,问题开始复杂起来~
原来,因为我们允许表格体上下滚动,使得在容器右侧出现了一个纵向的滚动条。而表头因为我们希望他是固定的,因此放在了另一个容器中,这导致他不能共享这个滚动条。因此,这使得表格体拉到头的时的 scrollLeft 也正好是表格头到头的位置,两个到头的位置上差了一个滚动条的宽度!看到这里,也许有的小伙伴可能会开始自己操练起来看看是不是这样,然后发现并没有类似问题,大呼坑爹,他们看到的情况大致如下图:
图4:Mac 某些设置下,看到的是另一份景象。
这引出了一个 Mac 下一个比较好玩的小设置,在 Mac 下滚动条何时显示也可以配置,大致分为三类,具体的配置可以在 系统偏好设置 -> 通用 中看到。
当我们选择 滚动时 的时候,只有当我们滚动一个元素的时候才会显示滚动条,且这个滚动条是飘在内容上,不会占据体积,于是便能看到 图3 中的情景。这个兼容性上升到了系统的程度,在 PC 上还是比较少见的(笑)。
所以这个问题只会在这种情况下没有出现,在 Mac 下选择 始终显示,也同样会出现。
那么如何解决这个问题呢?其实,从上面的分析中我们也可以也大致地找到了问题的根源,表头没有共享表格体的纵向滚动条,那么我们只要想办法解决这个就好了。这个说起来简单,但毕竟不是在同一个容器中,如何共享呢?方案一:插入一个和滚动条相同宽度的 dom 元素充当滚动条,但这会引起另一个问题就是表格头和表格体的实际宽度不同,在各种滚动计算上引发很多麻烦。方案二:滚动条虽然在不同系统、不同浏览器里都不一样尺寸,但却有个优点就是,只要在同一系统、统一浏览器里不管因为什么原因,出现在什么地方,他的尺寸总是保持一致的。利用这个特点,我们可以设置表头容器的 overflow-y: scroll,总是包含一个纵向滚动条的道,这样就兵不血刃的解决了这个麻烦的问题。
但这样又引入了新的问题图5:利用空的滚动条连接下面的滚动条来就可以解决上面的问题。
这样虽然比较好的解决了表格体有滚动条的情况,但是如果表格体没有滚动的情况下,遇到的问题就正好逆转了,又会出现对不齐的情况!
图6:当表格体没有纵向滚动条的情况下,又会出现新的问题。
解决这个问题也有几种思路,简单一点的思路可以模仿上面,给表格体也设置 overflow-y: scroll,这样不管是否有滚动,看起来都是一样而且不会错位了。但是这种方法并不十分美观,尤其在 windows 下显得比较丑陋。于是在此方案基础之上,我们加入了对于表格体纵向滚动的检测,当滚动区域的高度不大于容器高度时,即可认为没有滚动,此时同时设置表头和表格体 overflow-y: hidden ,就可以达到解决上述问题,而在没有滚动的情况下也保持美观的要求。
遇到的问题3:列固定下的整体横向滚动解决了上面两个问题之后,一个完整的支持表头固定和横向滚动的表格就完成了。接下来又有了新的需求,要在原有表格基础之上,支持左右侧列固定。这也是表格中比较常见的需求,一些数据列或者操作列处于高频使用下,希望能够固定,这时就产生两种处理表格体横向滚动条的方式。
图7:支持左右列固定后的方案 A 和 B
方案 A 模仿表头的设计方案,将固定的列放在另一个容器当中,把需要滚动的列多带带放置在一个可以滚动的容器当中。这种方案的问题在于,固定列的宽度和列数都不像表头一样固定且较小。当固定区域很大的时候,会严重挤压中间滚动区域滚动条的可操作区域,影响用户体验。同时他也会遇到和表头一样,滚动至最后一行对不齐的情况。方案 B 则比较好的解决了方案 A 的第一个问题,左右侧漂浮在表格的左右两边,覆盖对应的固定区域,横向滚动条可以覆盖整个表格,不会受到固定列宽度的限制。
方案 B 虽然好,但是仍有两个问题需要解决如何实现在固定区域也可以做到整个表格上下滚动
漂浮的固定列会遮盖住全局的横向滚动条。
对于问题 1,他的解决思路其实和刚才表头的解决思路类似。主要是通过适当地设置 margin 来隐藏本应存在的滚动条,这里不再赘述。
对应问题 2,如果是没有纵向滚动,即 height: auto 这样的情况下经过测试不存在这个问题,float 的元素会很自然地不占用滚动条的体积,因此不会有遮挡。如果是有纵向滚动的,情况则复杂了一些,当 float 的元素和下面的主表格等高时会出现遮挡的情况。
图8:方案 B 引入的遮挡滚动条的问题
那既然同高会有遮挡的问题,只要我们对应的减掉对应的滚动条高度就可以了,解决第一个问题时我们得到的大杀器,获取滚动条的高度,也可以用在这里。这个方案也能很好地应该对 Mac 下有的设置不显示滚动条的情况,在不显示滚动条的情况下,我们获取到的宽度是 0,即没有影响。而滚动时,滚动条会自动浮在最高的位置,因此仍然整条可见。
总结那么,今天,你的浏览器 “滚动” 了吗?你是否已经笑对其中了呢~
本文从实际的组件需求出发,通过三个有关滚动条的问题出现和解决为线索,和大家分享了如何跨系统、跨浏览器地兼容滚动尤其是滚动条的问题,虽然是以表格为核心阐述,但解决方案不局限于表格之中,希望能给大家在遇到类似问题时提供一些灵感。文中涉及的行列固定相关的知识,因为非本文重点,故一笔带过,如果有兴趣了解实现详情,可以参考我们团队开源的 PC 端 UI 组件库 UXCore 和对应的组件 Table 里的代码~
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/87182.html
摘要:今天,你的浏览器滚动了吗序在页面中,一个有高度或者宽度的容器是最常见的构成元素,而在其中的子元素有很大的概率超过父容器的尺寸限制,我们称之为溢出。 今天,你的浏览器 滚动 了吗? 序 在 Web 页面中,一个有高度或者宽度的容器是最常见的构成元素,而在其中的子元素有很大的概率超过父容器的尺寸限制,我们称之为溢出。而应对溢出,隐藏或者滚动是最常见的处理方式。滚动,作为 FEers 最经常...
摘要:前端日报精选专题之函数柯里化前端可用性保障实践入门指南页面布局这个属性你可能都不知道如何监听页面变动并高效响应发布中文深入理解笔记集合与集合第期介绍译系统设计入门之面试题解答设计一个网页爬虫掘金基于构建的移动端微应用个人文章 2017-08-11 前端日报 精选 JavaScript专题之函数柯里化前端可用性保障实践CSS入门指南-4:页面布局 这5个CSS属性你可能都不知道!如何监听...
摘要:二分析排查一步骤一使用搜索引擎我是在无意中发现该问题的,当时观察到的现象是绑定在上的事件有时会被触发,有时会失效。这说明并不存在偶尔失效的问题。也就是说,我需要找到确切的令响应事件失效的原因。接下来的事很简单,继续搜索事件在页面滚动后失效。 如果你关注我应该知道,我最近对PC端页面进行移动适配。在这个过程中,为了节省用户300ms的时间,同时给予用户更及时的点击反馈(这意味着更好的用户...
阅读 3304·2023-04-26 00:07
阅读 3877·2021-11-23 10:08
阅读 2921·2021-11-22 09:34
阅读 821·2021-09-22 15:27
阅读 1724·2019-08-30 15:54
阅读 3701·2019-08-30 14:07
阅读 895·2019-08-30 11:12
阅读 648·2019-08-29 18:44