今天,你的瀏覽器 「滾動」 了嗎?

今天,你的瀏覽器 「滾動」 了嗎?

在 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 裏的代碼~

相關文章
相關標籤/搜索