动手练一练,手写一个价格对比、固定表头滚动的表格

大家好,今天我们将一起实践下如何手写固定表头,那么什么是固定表头呢?就类似 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 技术制作一个响应式图片幻灯

专注分享当下最实用的前端技术。关注前端达人,与达人一起学习进步!

长按关注"前端达人"

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