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

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

在 Web 页面中,一个有高度或者宽度的容器是最多见的构成元素,而在其中的子元素有很大的几率超过父容器的尺寸限制,咱们称之为“溢出”。而应对“溢出”,隐藏或者滚动是最多见的处理方式。滚动,做为 FEers 最常常处理的一种行为,却由于不一样浏览器的各类表现形式让你们头痛不已,今天笔者从自身维护的组件出发,和你们分享一下本身在处理滚动和滚动条时遇到的问题,以及解决的办法,但愿可以给你在解决同类问题时带来一些启发。同时本文也是 “从零开始的 React 组件开发之路” 系列的第二篇 - 表格篇番外。node

遇到的问题 1:尴尬的双滚动条

笔者在团队中负责基础组件的开发和维护,做为一个 B 类业务较多的团队,表格是最经常使用和需求最为旺盛的组件,假设有下方这样一个最简单的表格结构。git

图1:最简单的表格结构github

由于空间有限,咱们但愿表格高度限定,这样势必引入表格上下滚动的状况。同时,为了查看的方便,咱们但愿表格头不会一块儿滚动,即表格头须要固定,只有表格体滚动,所以咱们须要把表格头和表格体放入两个容器中,而只让表格体的容器滚动。这是很普通的需求,也很容易实现,到目前为止一切都很顺利。chrome

图2:表头固定,表格体滚动segmentfault

然而这一切的美好,随着表格列数的增多,变的有了一点乌云。由于页面宽度受到电脑屏幕的限制,咱们每每对表格的宽度也有限制,不可能无限延展开。那么若是有不少的列呢?显然,让表格左右滚动是一个很天然的想法。因为咱们的表头和表格体在两个不一样的容器中,让这件事变的稍微麻烦一点。关于如何让表头和表格体同步左右滚动,不是这篇文章讨论的重点,因此不作详细讨论,简单来讲,咱们经过监听横向滚动的事件和不断获取当前的 scrollLeft 来得到同步。有一个麻烦的点是,咱们不但愿只能经过滚动表格体来实现表格滚动,也但愿能够经过滚动表头来实现表格的左右滚动,这就要求必须设置表头的容器为 overflow-x: autowindows

图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:对不齐的表头和数据

经过上面的方法,咱们成功地隐藏了表格头的横向滚动条。稍微滚动一下,一切正常,一切都按照预想的执行,直到一直滚动到头,问题出现了,最后一列的表头和下面的数据竟然是对不齐的!!app

图4:当表格体又能够左右滚动时,问题开始复杂起来~dom

这是怎么回事呢?

原来,由于咱们容许表格体上下滚动,使得在容器右侧出现了一个纵向的滚动条。而表头由于咱们但愿他是固定的,所以放在了另外一个容器中,这致使他不能共享这个滚动条。所以,这使得表格体拉到头的时的 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. 如何实如今固定区域也能够作到整个表格上下滚动
  2. 漂浮的固定列会遮盖住全局的横向滚动条。

对于问题 1,他的解决思路其实和刚才表头的解决思路相似。主要是经过适当地设置 margin 来隐藏本应存在的滚动条,这里再也不赘述。
对应问题 2,若是是没有纵向滚动,即 height: auto 这样的状况下通过测试不存在这个问题,float 的元素会很天然地不占用滚动条的体积,所以不会有遮挡。若是是有纵向滚动的,状况则复杂了一些,当 float 的元素和下面的主表格等高时会出现遮挡的状况。

图8:方案 B 引入的遮挡滚动条的问题

那既然同高会有遮挡的问题,只要咱们对应的减掉对应的滚动条高度就能够了,解决第一个问题时咱们获得的大杀器,获取滚动条的高度,也能够用在这里。这个方案也能很好地应该对 Mac 下有的设置不显示滚动条的状况,在不显示滚动条的状况下,咱们获取到的宽度是 0,即没有影响。而滚动时,滚动条会自动浮在最高的位置,所以仍然整条可见。

总结

那么,今天,你的浏览器 “滚动” 了吗?你是否已经笑对其中了呢~

本文从实际的组件需求出发,经过三个有关滚动条的问题出现和解决为线索,和你们分享了如何跨系统、跨浏览器地兼容滚动尤为是滚动条的问题,虽然是以表格为核心阐述,但解决方案不局限于表格之中,但愿能给你们在遇到相似问题时提供一些灵感。文中涉及的行列固定相关的知识,由于非本文重点,故一笔带过,若是有兴趣了解实现详情,能够参考咱们团队开源的 PC 端 UI 组件库 UXCore 和对应的组件 Table 里的代码~

相关文章
相关标签/搜索