純 CSS 解決H5佈局中的吸頂吸底

演示Demo地址(手機端打開):https://closertb.site/Klotski...
演示Demo源碼:https://github.com/closertb/k...
原文:https://github.com/closertb/c...css

哪些想啥提啥的產品們

最近作了一個需求,準確說是迭代需求:加了一個頭部概覽(相似下圖),以更好的讓用戶觀察到營銷變化,故事的開頭就這樣悄悄的埋下了伏筆。html

之前這個頁面只是一個評價列表(可上拉加載),爲了數據更易讀,列表的頭採用了固定佈局。然而加了這個概覽時,產品沒提,我就簡單粗暴的將這個列表頭換成了相對佈局,ok,提測。
20201017180423
但次日,我發現上拉加載數據多了,列表頭部被頂上去以後,想再作篩選,就要再把列表上滑才能看到,這個體驗很是之差。因而同事就說要不問問產品,要不把概覽加概覽作成固定。前端

我第一反應就是,恐怕提了以後,產品會讓我把篩選列表頭部作成固定,注意那個只。ios

而後就有了下面的對話:git

20201017175848

果真怕什麼,來什麼,畢竟是很常規的操做。但就像同事說的,本身問的需求,含着淚也要接下。github

局部吸頂

如下代碼是頁面的dom結構web

<div id="demo" className={style.demo}>
  <h3 id="title" className="title">這是一個概覽頭部</h3>
  <div id="content" className="content">
    <div className="filter-bar">
      <h3>這是列表頭部</h3>
      <h3>可篩選</h3>
      <h3>下面是滾動列表</h3>
    </div>
    <ul className="list">
      {arr.map(({ key, label }) => <li key={key}>{label}</li>)}
    </ul>
  </div>
</div>

JS 實現

由於頁面自己就有scroll事件監聽,因此第一個念頭是用JS完成,但當時已經下班,又是週五,感受5分鐘內搞不定,因此我就跑了。面試

如今來嘗試用JS實現,先理一下思路:瀏覽器

  • 監聽頁面的滾動,當ul元素頂部距離頁面頂部大於title 高度時,添加一個css類使篩選頭部吸頂;
  • 當ul元素距離頂部小於等於title 高度時,刪除添加的類,取消篩選頭部吸頂
JS 代碼
useEffect(() => {
  const demo = document.querySelector('#demo');
  const content = document.querySelector('#content');
  const titleHeight = document.querySelector('#title').clientHeight;
  let fixed = false;
  demo.addEventListener('scroll', (e) => {
    // 添加吸頂
    if (!fixed && e.target.scrollTop >= titleHeight) {
      fixed = true;
      content.classList.add('with-fixed');
    }
    // 取消吸頂
    if (fixed && e.target.scrollTop < titleHeight - 5) {
      content.classList.remove('with-fixed');
      fixed = false;
    }
  });
}, []);

看起也不難,但其實離代碼上線,還有很大優化的空間,後面會分析補充。less

CSS 實現

JS 看似很簡單,但就像那句熱門句子:這突如其來的噩耗,讓本不富裕的家庭雪上加霜。在這種有下拉加載的頁面,咱們原本就在監聽裏面作了不少邏輯處理,因此能用CSS實現的,就儘可能不要再去麻煩JS了。

首先理一下思路,深挖產品的需求:

  • 保持篩選頭在可視範圍以內(吸頂), 保證可篩選;
  • 當列表數據多時,儘量多展現列表,即概覽頭部就不必看到了;
  • 列表是上拉加載的;

當理清上面思路時,咱們發現,其實就是當列表很長時,隱藏概覽頭部,簡單用僞代碼表示就是(vh是視口單位 ,100vh表明整個屏幕可視高度):

if (titleHeight + filterBarHeight + listHeight > 100vh) {
     title.hide();
   }

那又怎樣實現概覽頭部隱藏,而篩選頭和列表又正好出於視口呢?

filterBarHeight + listHeight = 100vh

當用戶往上劃,只須要內容(篩選頭和列表)正好是一個視口高度(100vh)時,概覽頭就剛好被隱藏,而篩選頭又正好吸頂,用CSS實現就是相似這樣的:

// 不是完整代碼,詳情請看demo:
.demo {
 :global {
   .title {
     height: 15vh;
     line-height: 15vh;
     text-align: center;
     border-bottom: 1PX solid #eee;
     background-color: #fff;
   }
   .filter-bar {
     height: 15vw;
     background-color: #888;
     display: flex;
     align-items: center;
   }
   .list {
     max-height: calc(100vh - 15vw);; // 這裏的設置很重要
     overflow: scroll;
     background-color: rgba(127, 255, 212, .8);
   }

### 對比

是否是感受CSS很簡單,稍微設置一下即搞定,只是要想到內容高度正好是100vh須要一點經(yun)驗(qi)。其實不光簡便,對比JS至少還有三個優勢:

  • JS 若是隻是上面那樣,直接將篩選頭的定位改爲固定定位,眼力好的人,實際上是能感受到列表有跳變的一瞬間,就是列表會忽然上移filterBar高度,來填補篩選頭離開正常文檔流;(解決方案就是在篩選頭外多套一層dom,並給一個固定高度,這樣篩選頭脫離正常文檔流,但高度依舊還在);
  • 當用戶下拉加載不少數據時,還想看到概覽頭部,他就得費勁的往上劃,直到移除吸頂篩選頭的類;而CSS實現就不存在,他想看到對比數據時,只須要按住篩選頭,往下一拉,就直接看到篩選頭了(能夠在篩選頭部增長js來實現);
  • 當用JS來操做Dom元素重排時,這每一年面試官說的那些重繪重拍我就很少說了,這消耗的性能確定高於CSS實現;

固然缺點也是存在的:

  • 兼容性問題,倒不是說vh的兼容性,而是在ios手機上,這方案有bug。因爲safari的頭部和底部滑動時可見性會改變,因此當Bar可見時,實際的100vh高於屏幕可見高度,就會致使吸頂頭部被遮擋。到目前爲止,雖然網上有不少說height: -webkit-fill-available;,但針對這種場景是無效的;

20201024155007

通過上面分析,100vh在IOS safari上的致命問題,會讓這種純CSS的方案褪色。但PC頁面,或者你和我同樣,要編寫的頁面是運行在APP中(即沒有bar存在),那這種方案就是可行的。全部的方案都要具體場景,具體分析,沒有誰出生就是完美。

若是對重繪重排有興趣,建議觀看Chrome的官方博文: 瀏覽器四部曲

彈性吸底

說完局部彈性吸頂,再說一個常見的,選擇性吸底:在頁面內容不足100vh時,咱們但願Footer是吸底的,當頁面內容大於100vh時,Footer處於正常文檔流,讓內容可視區域更大,而又不會由於內容太少影響美觀,見圖:
20201025095027

像第一張圖那樣不作定位的仍是大有人在,由於他們堅信本身網站的內容不會出現不夠的時候,但之前更常見作法是底部固定定位。

彈性吸底利用min-height 加絕對定位,其實現很簡單。核心代碼不超過5行css:

body{
  position: relative;
  min-height: 100vh;
}

footer {
  width: 100%;
  position: absolute;
  bottom: 0;
}

原理就是內容區域最低高度爲一個屏幕,而後底部相對屏幕進行絕對定位;當內容變多時,高度大於100vh,因爲是依賴bottom: 0;,因此會一直吸底,其巧妙之處就在於此。

針對於這個場景,height: -webkit-fill-available 就是有效的。
20201025102807

更多關於-webkit-fill-available, 參見[https://allthingssmitty.com/2...];

總結

vh 確實是個好東西,能夠解決移動端的適配問題。我我的以爲做爲一個合格的前端,CSS 仍然是必備技能,不要對JS產生太多的依賴,不是不能夠,而是好鋼要用在刀刃上。

公衆號:前端黑洞

相關文章
相關標籤/搜索