動手練一練,手寫一個價格對比、固定表頭滾動的表格

大家好,今天我們將一起實踐下如何手寫固定表頭,那麼什麼是固定表頭呢?就類似 Excel 表格有個鎖定表頭的功能,方便用戶查閱數據進行數據項的對比。雖然有不少相關插件提供了類似的功能,比如 ScrollMagic.js,但是今天的實例,我們將用純原生的方式進行實現,當滾動條滾動至表格位置,固定表頭位置,表格內容查看完後,取消固定表頭的功能。

一、實踐一個功能價格對比的表格案例

功能對比是一個很常用的功能,尤其是當網站服務越來越多時,就需要一個類似的功能,讓用戶能夠直觀的感受到各種服務的差異,幫助用戶選擇適合自己的方案。今天我們將通過一個界面十分漂亮功能價格對比的表格,展示固定表頭的功能,實例操作展示如視頻所示,當滾動條滾動至表格位置,添加表頭固定樣式,當滾動至表格底部,移除固定表頭樣式。

二、案例相關知識點複習

這篇案例我們是通過JS代碼,判斷滾動條的位置,動態添加和移除表頭的固定樣式(fix屬性),這裏就需要運用幾個和位置相關 DOM API 才能順利完成本案例,相關 API 介紹如下所示:

1、Window pageXOffset 和 pageYOffset 屬性

pageXOffset 和 pageYOffset 屬性返回文檔在窗口左上角水平和垂直方向滾動的像素。

pageXOffset 設置或返回當前頁面相對於窗口顯示區左上角的 X 位置。pageYOffset 設置或返回當前頁面相對於窗口顯示區左上角的 Y 位置。

pageXOffset 和 pageYOffset 屬性相等於 scrollX 和 scrollY 屬性。

2、clientHeight、offsetHeight、scrollHeight、offsetTop、scrollTop

clientHeight包括 padding 但不包括border、水平滾動條、margin的元素的高度。對於inline的元素這個屬性一直是0,單位px,只讀元素。

offsetHeight包括padding、border、水平滾動條,但不包括margin的元素的高度。對於inline的元素這個屬性一直是0,單位px,只讀元素。

scrollHeight: 因爲子元素比父元素高,父元素不想被子元素撐的一樣高就顯示出了滾動條,在滾動的過程中本元素有部分被隱藏了,scrollHeight代表包括當前不可見部分的元素的高度。而可見部分的高度其實就是clientHeight,也就是scrollHeight>=clientHeight恆成立。在有滾動條時討論scrollHeight纔有意義,在沒有滾動條時scrollHeight==clientHeight恆成立。單位px,只讀元素。

scrollTop: 代表在有滾動條時,滾動條向下滾動的距離也就是元素頂部被遮住部分的高度。在沒有滾動條時scrollTop==0恆成立。單位px,可讀可設置。

offsetTop:當前元素頂部距離最近父元素頂部的距離,和有沒有滾動條沒有關係。單位px,只讀元素。

本部分內容摘自: https://imweb.io/topic/57c5409e808fd2fb204eef52 作者:IMWeb 吳浩麟

3、getBoundingClientRect

getBoundingClientRect 用於獲得頁面中某個元素的左,上,右和下分別相對瀏覽器視窗的位置。getBoundingClientRect是DOM元素到瀏覽器可視範圍的距離(不包含文檔卷起的部分)。

3.1、該函數返回一個Object對象,該對象有6個屬性:top,lef,right,bottom,width,height;

3.2、這裏的top、left和css中的理解很相似,width、height是元素自身的寬高;

3.3、但是right,bottom和css中的理解有點不一樣。right是指元素右邊界距窗口最左邊的距離,bottom是指元素下邊界距窗口最上面的距離。

本部分內容摘自:

https://juejin.im/entry/59c1fd23f265da06594316a9

作者:左鵬飛

三、創建 HTML 基礎結構

1、創建三個基礎的 ps 的區域

<p>...</p>
<p>...</p>
<p>...</p>

第一部分爲頁面標題內容,第三部分爲內容介紹區域,這兩部分非核心內容,只是用於內容佔位,方便第二部分表格區域的展示,滾動此區域表頭固定。

2、表格內容結構

我們將第二部分的表格放置在 container 的容器內,方便我們做響應式相關的設置,表格基礎結構的內容如下:

<div class="container">
  <div class="table-wrapper">
    <table>
      <thead>
        <tr>
          <th>...</th>
          <th>...</th>
          <th>...</th>
          <th>...</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>...</td>
          <td>...</td>
          <td>...</td>
          <td>...</td>
        </tr>
         
        <!-- more rows here -->
      </tbody>
    </table>
  </div>
</div>

該表格包含4列,代表產品服務的對比項目和服務的級別,服務級別包含:入門級,基礎級和專業級。

3、表頭內容結構

表頭部分應該很清楚的展示服務項目的介紹,讓用戶有購買服務計劃的衝動,界面展示如下所示:

相關的 HTML 結構如下所示:

<tr>
  <th>
    <div>
      Select your plan
      <div class="svg-wrapper">
        <svg viewBox="0 0 24 24"><path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm1 17v-4h-8v-2h8v-4l6 5-6 5z"/></svg>
      </div>
    </div>
  </th>
  <th>
    <div class="heading">...</div>
    <div class="info">
      <div class="amount">...</div>
      <div class="billing-msg">...</div>
      <button type="button">...</button>
    </div>
  </th>
  <th>
    <div class="heading">...</div>
    <div class="info">
      <div class="popular">...</div>
      <div class="amount">...</div>
      <div class="billing-msg">...</div>
      <button type="button">...</button>
    </div>
  </th>
  <th>
    <div class="heading">...</div>
    <div class="info">
      <div class="amount">...</div>
      <div class="billing-msg">...</div>
      <button type="button">...</button>
    </div>
  </th>
</tr>

4、表格相關的行

每行內容描述服務內容中相關的功能是否能用,這裏用 SVG圖標(對號,叉號)進行直觀展示,界面展示如下圖所示:

相關的 HTML 結構如下所示:

<tr>
  <td>...</td>
  <td>
    <svg class="starter" viewBox="0 0 24 24"><path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-1.959 17l-4.5-4.319 1.395-1.435 3.08 2.937 7.021-7.183 1.422 1.409-8.418 8.591z"/></svg>
  </td>
  <td>
    <svg class="essential" viewBox="0 0 24 24"><path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-1.959 17l-4.5-4.319 1.395-1.435 3.08 2.937 7.021-7.183 1.422 1.409-8.418 8.591z"/></svg>
  </td>
  <td>
    <svg class="professional" viewBox="0 0 24 24"><path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-1.959 17l-4.5-4.319 1.395-1.435 3.08 2.937 7.021-7.183 1.422 1.409-8.418 8.591z"/></svg>
  </td>
</tr>

四、定義樣式

1、定義基礎樣式

HTML結構準備好後,接下來我們定義相關基礎的 CSS 樣式,比如定義 CSS 自定義變量和常見的重置樣式,示例代碼如下:

:root {
  --white: white;
  --gray: #999;
  --lightgray: whitesmoke;
  --popular: #ffdd40;
  --starter: #f73859;
  --essential: #00AEEF;
  --professional: #FF7F45;
}
 
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
 
button {
  background: none;
  border: none;
  cursor: pointer;
}
 
table {
  border-collapse: collapse;
}
 
body {
  font: 18px/1.5 'Noto Sans', sans-serif;
  background: var(--lightgray);
}

由於文章篇幅有限,這裏不會將所有的 CSS 代碼進行羅列,這裏只介紹最核心的樣式內容。

2、定義表格樣式

首先定義表格最大寬度,然後讓其水平居中:

.container {
  max-width: 850px;
  padding: 0 10px;
  margin: 0 auto;
}
 
table {
  width: 100%;
}

接下來讓行的容器爲 flex 彈性盒子佈局

table tr {
  display: flex;
}

然後讓每列保持相同寬度,示例代碼如下:

table th,
table td {
  width: 25%;
  min-width: 150px;
}

最後爲了讓單元格區域便於識別,我們用灰色邊框進行區分,示例代碼如下:

--lightgray: whitesmoke;
 
table th .info,
table td:not(:first-child) {
  border-left: 1px solid var(--lightgray); 
}

五、編寫固定表頭的相關腳本

HTML結構和CSS完成後,接下來我們編寫腳本固定表頭。

1、定義DOM變量

首先我們先定義一些關鍵DOM元素的變量,比如獲取表格、表頭等元素,示例代碼如下:

const body = document.body;
const firstSection = document.querySelector("p:nth-child(1)");
const lastSection = document.querySelector("p:nth-child(3)");
const table = document.querySelector("table");
const thead = document.querySelector("table thead");
const mq = window.matchMedia("(min-width: 780px)");
const stickyClass = "sticky-table";
const sticky2Class = "sticky2-table";

2、獲取一些元素相關的值

  • 獲取表格的 offsetWidth 寬度

  • 獲取表格距離視口頂部的距離(getBoundingClientRect().top)

  • 獲取表頭的 offsetHeight 高度

基於這些我們定義相關的變量,獲取相關的值:

let tableWidth = table.offsetWidth;
let tableOffsetTop = table.getBoundingClientRect().top;
let theadHeight = thead.offsetHeight;

你可能注意到了這裏我們使用 let 定義變量,之所以用 let ,我們改變窗口的大小,這些相關的值也會發生變化,需要進行動態更新。

3、編寫滾動的相關邏輯

每次我們滾動時,就會執行我們定義的 scrollHandler 函數,我們這個函數只會在窗口寬度大於 780px 纔會執行固定表頭的邏輯,小屏設備則沒有相關效果。

  1. 獲取用戶從視口頂部滾動的距離(pageYOffset)

  2. 獲取最後一部分內容區域距離窗口頂部的高度(getBoundingClientRect().top)

  3. 檢測滾動條是否滾動到表格區域。

  4. 如果滾動到表格區域,獲取重置後的表頭寬度。

  5. 接下來我們來判斷第三部分內容區域距離視口頂部的高度是否大於表頭的高度。

  6. 如果還在滾動表格的內容,我們將添加固定表頭的樣式stickyClass,移除取消固定的樣式sticky2-table。

  7. 如果滾動條滾動至第三部分內容區域,我們將移除固定表頭的樣式stickyClass,添加移除固定表頭的樣式 sticky2-table。

  8. 如果屏幕寬度小於780px,取消固定表頭的邏輯,移除stickyClass,sticky2-table 相關的樣式

基於以上邏輯我們實現相關的代碼邏輯:

window.addEventListener("scroll", scrollHandler);
 
function scrollHandler() {
  if (mq.matches) {
    // 1
    const scrollY = window.pageYOffset;
    // 2
    const lastSectionOffsetTop = lastSection.getBoundingClientRect().top;
    // 3
    if (scrollY >= tableOffsetTop) {
      // 4
      thead.style.width = `${tableWidth}px`;
      // 5
      if (lastSectionOffsetTop > theadHeight) {
        // 6
        body.classList.remove(sticky2Class);
        body.classList.add(stickyClass);
        thead.style.top = 0;
        body.style.paddingTop = `${theadHeight}px`;
      } else {
        // 7
        body.classList.remove(stickyClass);
        body.classList.add(sticky2Class);
        thead.style.top = `calc(100% - ${theadHeight}px)`;
      }
    } else {
      // 8
      body.classList.remove(stickyClass, sticky2Class);
      body.style.paddingTop = 0;
      thead.style.width = "100%";
      thead.style.top = "auto";
    }
  }
}

編寫相關的樣式代碼,stickyClass 和 sticky2-table 控制表頭的固定和取消表頭的固定

table thead {
  transition: box-shadow 0.2s;
}
 
.sticky-table table thead {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
}
 
.sticky-table table thead {
  box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.12);
}
 
.sticky2-table table thead {
  position: absolute;
  left: 0;
}

六、編寫窗口大小發生變化的相關邏輯

由於窗口大小並非固定,我們會經常會拖動或調整窗口的大小,因此相關元素的寬度和視口高度都要重新計算,這裏我們需要添加 resize 事件進行監聽,示例代碼如下:

window.addEventListener("resize", resizeHandler);
 
function resizeHandler() {
  if (mq.matches) {
    tableWidth = firstSection.offsetHeight;
    tableOffsetTop = table.offsetTop; 
    theadHeight = thead.offsetHeight;
  } else {
    body.classList.remove(stickyClass, sticky2Class);
    body.style.paddingTop = 0;
    thead.style.width = "100%";
    thead.style.top = "auto";
  }
}

七、源碼及效果展示

最終的效果體驗,大家可以點擊文末 原文鏈接 進行體驗(手機橫屏體驗),由於文章篇幅有限,完整的源碼大家可以通過以下鏈接進行下載:

鏈接:https://pan.baidu.com/s/1qVdl6xe0o_ZrF8_DaP6Z4A 密碼:l4bv

小節

到此,我們一起完成了這個案例,通過本案例,我們學會了如何使用原生的方式動態實現固定元素,並在一定的時機取消固定。感謝你的閱讀,如果你喜歡我的分享,麻煩給個關注、點贊加轉發哦,你的支持,就是我分享的動力,後續會持續分享更實用的案例,歡迎持續關注。

文章來源:作者:George Martsoukos 網站:tutsplus 非直譯

延伸閱讀

使用 Vanilla JavaScript 框架創建一個簡單的天氣應用

動手練一練,使用 Flexbox 創建一個響應式的表單

動手練一練,用純 CSS 製作一款側滑顯示留言面板的網頁組件

使用 CSS Checkbox Hack 技術純手工擼一個手風琴組件

動手練一練,做一個響應式的後臺管理面板

動手練一練,用 CSS Checkbox Hack 技術製作一個響應式圖片幻燈

專注分享當下最實用的前端技術。關注前端達人,與達人一起學習進步!

長按關注"前端達人"

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章