大家好,今天我们将一起实践下如何手写固定表头,那么什么是固定表头呢?就类似 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) {// 1const scrollY = window.pageYOffset;// 2const lastSectionOffsetTop = lastSection.getBoundingClientRect().top;// 3if (scrollY >= tableOffsetTop) {// 4thead.style.width = `${tableWidth}px`;// 5if (lastSectionOffsetTop > theadHeight) {// 6body.classList.remove(sticky2Class);body.classList.add(stickyClass);thead.style.top = 0;body.style.paddingTop = `${theadHeight}px`;} else {// 7body.classList.remove(stickyClass);body.classList.add(sticky2Class);thead.style.top = `calc(100% - ${theadHeight}px)`;}} else {// 8body.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 技术制作一个响应式图片幻灯

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

长按关注"前端达人"

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

  1. vue 使用fs_模仿vue-cli,手写一个脚手架

    vue-cli 在vue的开发的过程中,经常会使用到vue-cli脚手架工具去生成一个项目.在终端运行命令vue create hello-world后,就会有许多自动的脚本运行. 为什么会这样运行呢 ...

  2. 仿照源码,手写一个自定义 Spring MVC 框架

    毫无疑问,Spring 框架目前已经成为 Java 开发的行业标准,Spring MVC 作为其 Web 解决方案,是所有 Java 开发者都必须掌握的基本技能,理解其底层原理,才能更好地应用它进行实 ...

  3. 手写一个原神祈愿分析工具

    手写一个原神祈愿分析工具 之前一直通过游创工坊来进行祈愿抽卡数据分析,但是广告太多,而且担心auth_key泄露,于是自己花了一天时间动手实现了个数据分析工具,数据永久保存在本地,没有信息泄露风险,话 ...

  4. 用 Node.js 手写一个 DNS 服务器

    DNS 是实现域名到 IP 转换的网络协议,当访问网页的时候,浏览器首先会通过 DNS 协议把域名转换为 IP,然后再向这个 IP 发送 HTTP 请求. DNS 是我们整天在用的协议,不知道大家是否 ...

  5. Java 手写一个SQL分页

    Java手写一个类似PageHelper的分页SQL 目前分页插件众所周知的莫过于和mybatis完美融合的PageHelper了,简单两行代码就实现了sql分页,配合PageInfo类,将数据总数量 ...

  6. 【javascript】手写一个webpack loder

    [javascript]手写一个webpack loder 手写一个loader 为什么需要loader?  webpack 实际上只能处理js文件,那么对于除了js文件的其他类型的文件 比如 css ...

  7. 【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理

    动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 在解 ...

  8. vue @click 赋值_vue 手写一个时间选择器

    vue 手写一个时间选择器 最近研究了 DatePicker 的实现原理后做了一个 vue 的 DatePicker 组件,今天带大家一步一步实现 DatePicker 的 vue 组件. 原理 Da ...

  9. 肝一波 ~ 手写一个简易版的Mybatis,带你深入领略它的魅力!

    零.准备工作 <dependencies><dependency><groupId>mysql</groupId><artifactId>m ...

最新文章

  1. 总结 | 2020年TOP 10计算机视觉论文:代码,解读,还有demo视频!
  2. jupyter扩展插件Nbextensions的安装、使用
  3. Python 解析配置模块之ConfigParser详解
  4. 【转】在生产环境中部署前端代码
  5. 静态链接和动态链接优缺点
  6. Deno 兼容浏览器具体指的是什么?
  7. python爬虫1——获取网站源代码(豆瓣图书top250信息)
  8. 一个 WordPress 安装,多个博客
  9. 在Eclipse上安装pydev开发工具
  10. 基于asp网上书店购物商城计算机毕业设计网站作品
  11. 答粉丝问|火狐浏览器插件简介
  12. vue+echarts 地图实现三级下钻
  13. 系统性能优化策略案例
  14. HTTP 错误 404.2 - Not Found 由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面。
  15. 干货干货~C语言版学生成绩管理系统【数据结构课程设计,百行代码实现功能强化版(内附源码)】
  16. Google,微软,科大讯飞的语音识别引擎对比
  17. socket整理复习
  18. DSG-01-2B3B-A110-51T、DSG-01-2B3B-A100-70电磁控制换向阀
  19. 用Python从wind获取数据,转换成dataframe格式,并保存成excel文件
  20. 经典爬虫:爬取百度股票

热门文章

  1. IT历史:计算机业简史
  2. 关于设立“链改青岛链湾综合试验区”的复函
  3. 全国计算机竞赛保送清华,保送清华北大,带你看看信息学奥林匹克竞赛的魅力...
  4. 来自沪江、滴滴、蘑菇街架构师的 Docker 实践分享
  5. Windows桌面图标被白块挡住大部分
  6. 3d量测怎么学距离_3D虚拟仿真技术提升物流设备应用能力
  7. iWO(联通3G详单及套餐使用情况查询工具)更新至v0.7
  8. 仿生学: 尺度和坐标轴
  9. 面试:你看见一分硬币捡不捡?计算秒薪的回答亮了,网友:我都要
  10. 中国将自主建造宇宙空间站