当前位置: 首页 > Web前端 > CSS

今天你的浏览器“滚动”了吗?

时间:2023-03-31 13:38:10 CSS

今天你的浏览器“滚动”了吗?在网页中,具有高度或宽度的容器是最常见的构成元素,其中的子元素极有可能超过父容器的大小限制,我们称之为“溢出”。对付“溢出”,隐藏或者滚动是最常用的处理方式。滚动作为FEer经常要处理的行为之一,由于不同浏览器的表现形式千差万别,一直让人很头疼。今天就从我维护的组件出发,和大家分享一下我在处理滚动和滚动条时遇到的问题。问题及解决方法,希望能给大家在解决类似问题时带来一些启发。同时,本文也是《从零开始的React组件开发之路》系列文章的第二篇——表格文章的旁注。问题一:尴尬的双滚动条作者在团队中负责基础组件的开发和维护。作为一个B类业务比较多的团队,表格是最常用也是需求量最大的组件。假设有这样一个最简单的表结构。图一:最简单的表格结构由于空间有限,我们希望表格的高度是有限的,这样就不可避免地会引入表格上下滚动的情况。同时,为了方便查看,我们希望表头不要一起滚动,即表头需要固定,只滚动表体,所以我们需要把表头和将表体分成两个容器,只让表体的容器滚动。这是一个常见的要求,易于实施,到目前为止一切顺利。图二:表头固定,表体滚动。然而,随着表格列数的增加,这一切的美好都变得有些模糊。因为页面的宽度受电脑屏幕的限制,所以我们经常对表格的宽度有限制,不能无限扩展。那么如果有很多列呢?显然,让表格左右滚动是一个很自然的想法。由于我们的标题和正文在两个不同的容器中,所以这有点复杂。如何让表头和表体同步左右滚动不是本文的重点,就不详细讨论了。简单的说,我们通过监听水平滚动事件,不断获取当前的scrollLeft来获取同步。比较麻烦的一点就是我们不想只通过滚动表体来滚动表格,还要滚动表头来左右滚动。这需要将标头的容器设置为overflow-x:auto。图3:由于表头和表体都需要水平滚动,所以引入了两个滚动条。这显然突破了大多数人对表格的认知。横向滚动的时候会有两条滚动条,一点都不美观。我们需要在此基础上进行优化。表体的水平滚动条没有问题。主要问题出在表头。我们希望能够水平滚动,但又不想看到该死的滚动条。我们应该做什么?想办法把他藏起来!首先我们设置containeroverflow-x:表头的scroll,保证不管是否需要横向滚动都会出现滚动条,方便我们简化状态的判断。接下来,我们可以设置表头的容器margin-bottom:-scrollBarWidth隐藏滚动条,让其parent帮忙吞下滚动条,一切就搞定了。但令人沮丧的是,滚动条的大小在不同的浏览器中是不同的,甚至在不同的系统中(比如Windows和Mac下的chrome)!我们不能通过设置一个固定的值来暴力地做到这一点,所以我们需要在表格渲染到页面后主动检测滚动条的宽度。constscrollbarMeasure={position:'absolute',top:'-9999px',width:'50px',height:'50px',overflow:'scroll',};letscrollbarWidth;constmeasureScrollbar=()=>{如果(typeofdocument==='undefined'||typeofwindow==='undefined'){return0;//如果文档不存在,证明不在浏览器环境中,直接返回,兼容nodeserverrender。}如果(滚动条宽度){返回滚动条宽度;//滚动条的宽度在固定环境下是不会变化的,所以只检测一次就可以优化性能了。}constscrollDiv=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);constwidth=scrollDiv.offsetWidth-scrollDiv.clientWidth;//获取滚动条的宽度,offsetWidth和clientWidth的区别,能解释清楚吗?document.body.removeChild(scrollDiv);//检测完成,销毁测试元素,减少对页面的影响。滚动条宽度=宽度;//缓存结果,优化性能returnscrollbarWidth;};问题二:表头和数据未对齐通过上面的方法,我们成功隐藏了表头的水平滚动条。滚动一点,一切正常,一切按预期执行,直到滚动结束,问题来了,最后一列的表头和下面的数据居然没有对齐!!图4:当表体又可以左右滚动时,问题就变复杂了~这是怎么回事?原来是因为我们让表体可以上下滚动,所以在容器的右侧出现了一个垂直的滚动条。表头放在另一个容器中,因为我们希望它是固定的,这样可以防止它共享滚动条。因此,这使得表格主体被拉到头部时的scrollLeft恰好是头部到表格头部的位置,而两个头部位置的差就是一个滚动条的宽度!看到这里,可能有的朋友可能会开始自己练习,看看是不是这样,然后发现没有类似的问题,大喊作弊,看到的大致如下:图4:在某些Mac设置下,我看到的是另一番景象。这导致了Mac下的一个更有趣的小设置。Mac下显示滚动条时也可以配置,大致可以分为三类。具体配置可以在系统偏好设置->通用中看到。当我们选择滚动时,滚动条只有在滚动某个元素时才会显示,而且这个滚动条是悬浮在内容上的,不会占用体积,所以我们可以看到图3的场景。这样兼容性就上升到了系统的水平,这在PC上是比较少见的(笑)。所以这个问题只有在这种情况下不会出现,在Mac下选择Alwaysdisplay也会出现。那么如何解决这个问题呢?其实从上面的分析,我们也可以大致找到问题的根源。表头不共享表体的垂直滚动条,所以我们只需要想办法解决这个问题。这个说起来容易,但毕竟不在同一个容器里,怎么共享呢?解决方案一:插入一个与滚动条宽度相同的dom元素来充当滚动条,但是这样会带来另外一个问题,就是表头和表体的实际宽度不一样,造成很多各种滚动计算的麻烦。方案二:虽然滚动条在不同的系统、不同的浏览器中有不同的大小,但是它有一个好处,无论什么原因,只要出现在同一个系统、统一的浏览器中,它的大小总是一样的。始终如一。利用这个特性,我们可以设置header容器的overflow-y:scroll始终包含垂直滚动条,这样就可以不流血地解决这个麻烦的问题。图5:上面的问题可以通过将下面的滚动条与空的滚动条相连来解决。但这引入了一个新问题。虽然表体有滚动条的情况比较好解决,但是如果表体不滚动,遇到的问题只是反过来,又会出现错位。!图6:当表体没有垂直滚动条时,又会出现新的问题。有几种方法可以解决这个问题。更简单的方法可以效仿上面,给表体设置overflow-y:scroll,这样不管有没有滚动看起来都一样,不会错位。但是这个方法不是很美观,尤其是在windows下。所以在这个方案的基础上,我们增加了对表体垂直滚动的检测。当滚动区域的高度不大于容器的高度时,可以认为没有滚动。这时,同时设置表头和表体overflow-y:hidden,即可解决上述问题,保持不滚动的美观要求。遇到的问题3:fixedcolumn下的整体水平滚动解决了以上两个问题后,一个完整的支持固定表头和水平滚动的表格就完成了。接下来有一个新的需求,在原有表格的基础上,支持固定左右列。这也是表格中比较常见的需求。部分数据列或操作列使用频率较高,希望修复。这时候有两种方法来处理表体的横向滚动条。图7:支持固定左右列的方案A和B。A方案模仿表头的设计,将固定的列放在另一个容器中,将需要滚动的列单独放在一个可滚动的容器中。这种解决方案的问题在于,无论是固定的列宽还是列数都没有像表头那样固定和小。当固定区域较大时,会严重挤压中间滚动区滚动条的可操作区域,影响用户体验。同时他也会遇到和header一样的情况,滚动到最后一行没有对齐。方案B较好的解决了方案A的第一个问题,左右浮动在桌子的左右两边,覆盖对应的固定区域。水平滚动条可以覆盖整个表格,不受固定列宽的限制。方案B虽然好,但还有两个问题需要解决:如何在固定区域上下滚动整个表格。浮动固定列将覆盖全局水平滚动条。对于问题1,他的解法其实和刚才的header的解法类似。主要是通过适当设置margin来隐藏本应存在的滚动条,这里不再赘述。对应问题2,如果没有垂直滚动,也就是height:auto的情况下,经过测试不存在这个问题。浮动元素自然不会占据滚动条的体积,所以不会有遮挡。如果有垂直滚动,情况就稍微复杂一点。当浮动元素与下面的主表格高度相同时,它将被阻塞。图8:方案B引入的挡住滚动条的问题由于在同一个高度会出现挡住的问题,所以我们只需要相应的降低对应滚动条的高度即可。当解决第一个问题时,我们得到了一个大杀手。获取滚动条的高度,这里也可以使用。这个方案对于Mac下一些不显示滚动条的设置也很好。在不显示滚动条的情况下,我们得到的宽度为0,即没有作用。滚动时,滚动条会自动浮在最高位置,所以整个条仍然可见。那么,今天,您的浏览器“滚动”了吗?你笑对了吗?本文从实际组件需求出发,通过与滚动条相关的三个问题及其解决方法,与大家分享如何实现跨系统、跨浏览器兼容的滚动,尤其是滚动条。虽然问题以表为中心,但解决方法却不限于表。希望能给大家遇到类似问题时提供一些启发。本文涉及的固定职级和列相关的知识不是本文的重点,所以简单提一下。如果有兴趣了解实现细节,可以参考我们团队开源的PC端UI组件库UXCore中的代码和对应的组件表~