CSS & JS Effect – Simulation Position Sticky (用 JavaScript 实现 position sticky)
前言
在 CSS – Position 我有提到过, 原生的 sticky 有一些 limitation. 不是每次都闪的掉.
这篇主要是通过 JS 来模拟它, 突破那些限制.
Google Ads 的 table header sticky 也是通过 JS 实现的
The Limitation
场景
先来看看 sticky 在什么情况下会坏掉 (limitation).
HTML


<div class="container">
<div class="group1">
<div class="box1"><span>1</span></div>
<div class="box2"><span>2</span></div>
<div class="box3"><span>3</span></div>
<div class="box4"><span>4</span></div>
<div class="box5"><span>5</span></div>
<div class="box6"><span>6</span></div>
</div>
<div class="group2">
<div class="box1"><span>1</span></div>
<div class="box2"><span>2</span></div>
<div class="box3"><span>3</span></div>
<div class="box4"><span>4</span></div>
<div class="box5"><span>5</span></div>
<div class="box6"><span>6</span></div>
</div>
</div>
<footer></footer>
CSS Style


body {
margin: 5rem;
} .container {
display: grid;
gap: 1rem;
} [class*="group"] {
border: 1px solid black;
padding: 0.5em;
display: grid;
gap: 1rem;
} [class*="box"] {
background-color: green;
padding-block: 1em;
text-align: center;
span {
color: white;
font-size: 3rem;
}
} .group1 {
.box2 {
position: sticky;
top: 0;
}
} .group2 {
[class*="box"] {
background-color: pink;
}
} footer {
height: 100vh;
}
效果
2 个 group, 一个绿色, 一个粉红色
组里分别有 6 个 item.
绿组 item 2 sticky top 0. 所以 scroll 的时候会跟着走.
max area limitation
依据 stikcy 的规则, sticky element 的 parent 就是它的 max area container (可移动范围)
假设做一个 wrapper 给 box2
这时 box2 的 max area container 就变成了 wrapper
然后它的 sticky 效果就没有了.
因为 wrapper 的 height 是 auto 也就是 hug content 和 sticky 一样高, 也意味着没有任何可移动空间. 所以 sticky 效果就没了.
scroll container limitation
依据 stikcy 的规则, sticky element 往上层找, 第一个 scrollable element 就是它的 scroll container.
目前没有任何设置 overflow 的 style 那默认就是 document 咯
假设把 group1 设置成 overflow
.group1 {
overflow-x: auto;
}
不管是 overflow-x 还是 y 只要是 overflow 就会被认作为 sticky 的 scroll container
于是, sticky 再次失效了.
原因就是只有 scroll container 的 scroll 能操控 sticky, 而 group1 没有 vertical scroll. 移动的是 document scroll 所以 sticky 也就不会触发了.
multiple sticky 累加问题
当有多个 sticky element 的时候, 需要自己控制 top 值, 累加之前 sticky element 的高度是很麻烦的, 虽然算不上局限性, 但是职责应该让 sticky module 负责才对.
问题分析
max area container 的职责是限制 sticky element 的可移动范围
scroll container 的职责是监听 scroll, 同时作为 sticky 的定位坐标
当 sticky 和 scroll container 坐标一致, 同时 max area 有可移动空间时, 定位开始.
这个是它的规则, 而限制我们的是 max area 和 scroll container 的选择. 如果能自由选择哪一个 element 作为 max area 和 scroll container 那就灵活很多了.
要突破限制, 就需要实现自己的 sticky 定位, 然后允许自由选择 max area 和 scroll container. 而不是像默认那样.
什么叫 parent 就是 max area, 第一个 overflow 就是 scroll container. 这种规则太不灵活了吧. 完全限制住了 layout.
游览器是不太可能去改善这一点的. 它不会让 CSS 使用过于复杂. 所以如果需求闪不掉, 就只能靠 JS 来完成了.
解决方案
到目前为止, 我都没有找到任何 proper way 去完全模拟替代原本的 sticky 功能.
市场主要有 2 个方法实现类似的功能, 但是都属于 workaround / hacking 的方式. 衍生出来的问题不少, 或者只能在特定场景下可用.
方案 1: fixed + absolute
我们先把元素列出来, 方便了解
1. scroll container 最外面的黑框
2. max area container 蓝色 area
3. stikcy element 红色
4. scroll container 可见区域. 紫色的框 (也就是它的 height, 下面都是 overflow scroll 看不见的.)
滚动的时候大概是这个 feeling
紫色框外面的是 overflow 看不见的哦
在 4 个关键的时刻, 修改样式.
监听的方式是 scroll, 然后通过 bounding client 获取各个相关 element 的 coordinate.
1. start sticky (scroll down)
当紫色的框顶部碰到 sticky element 时, 开始 sticky (这个例子讲的都是 sticky top 哦, bottom 就反过来, horizontal 又再反过去)
给 sticky element position fixed 让它开始定位.
2. prevent over max area (scroll down)
当 hit 到 max area (蓝色) 底部时, 把 sticky element 切换到 absolute (或者 transform translate 也行)
3. back to max area (scroll up)
又在切换回 fixed
4. end sticky (scroll up)
又在切换回 static
衍生问题
性能方面是很好的,手机都没有问题,因为 fixed 是直接把 sticky element 定位到最上层 viewport,那原本 sticky scroll container 就影响不到它了。
虽然这个方案能用,但代价也不少,因为原本的 sticky scroll container 是有它的作用的,sticky 脱离了它虽然得到了一些特性,但也失去了另一些特性。而这些失去的特性我们是需要弥补回去的。
sticky 离开后的洞
sticky element 被 position fixed 以后就脱离了原本的 layout,相等于你把 sticky remove from DOM 对 layout 的影响,sticky 以下的 element 会移上来。
这当然不是我们期望的。
要解决这个问题,我们需要 create 一个 element 然后 insert before sticky element 作为弥补
当然我们还需要同步它们的高度。
另外,多了一个 element 也可能会破坏 CSS selector,比如 odd even 这类的 (因为 element 就是多了一个嘛)
overflow-x 失效
由于 sticky 脱离了原本的 layout,它也就不再受原本 ancestor 的 overflow-x 影响
这就导致可能原本已经被 overflow-y hide 起来的 sticky,显示出来,或者显示一半出来。
下图是一个 table header,
它的 horizontal scrollbar 已经移动了一些,此时 header 还没有 sticky,所以 email th 只显示了一半。
header sticky 以后变这样
全部 header 显示出来了,因为它们不再受到 horizontal scrollbar overflow-x 的影响。
要解决这个问题,我们可以用 clip-path 把多出来的地方剪掉,设置 width + overflow-x。
scroll 失效
sticky element position fixed 以后它就无法再控制它原本的 ancestor scrollbar。position fixed 以后,在 header scroll 会控制到 body 最上层的 scrollbar,而不是原本的 sticky ancestor scrollbar。
这些全部都是因为脱离了原本 layout 以后失去的特性。
要弥补回来这个 scroll 特性,我们可以监听 wheel 然后控制 div.scrollTop。
当然手机的处理更麻烦,需要监听 touchstart, touchmove,而且手机模拟 scroll 体验更难,因为 scroll 在手机是很丝滑而且有余力了。
封装难度
如果是针对个案去实现会容易许多,比如只是 sticky top,没有 bottom, left, right。
width 问题可能 100% 就解决了,height 可能是固定的,可以直接 hardcode。
如果是封装成 library 难度就大很多,需要顾虑许多奇葩状况 (重点是做 library 会要求假设各种奇葩它们会一起出现,但往往真实情况并不会一起出现。)
方案 2: transform translate
translate 的做法相对更简单, 监听 scroll > 计算 > set translate > 完事.
它没有 fixed 脱离 layout 的困扰. translate 属于原地移动 (灵魂出窍), 依然占据原本位置.
它也没有 fixed absolute 的局限性. horizontal, vertical 一起做也没问题.
但是它有一个超级无敌致命的问题. 性能
scroll + translate 就是那么卡的 (术语叫 scroll jank / jittery). 不管做什么优化都没用. 什么 will-change, translate3d 都是假的, 它的问题主要来自 scroll event.
卡还有分等级的:
body scroll (电脑也卡) > div scroll (电脑不卡) > div 手拉 scrollbar (电脑不卡)
我是在 ads.google.com 的 table 看到这个方案的. 它做的不卡.
在电脑, google 用的是 div scroll, 并且通过拦截 wheel event 替代了 scroll event, 自己控制了 scroll 节奏.
这就是它的黑科技, 用 DevTools 关掉它的 scroll listener 会发现它的 sticky 依然是 work 的. 但如果也把 wheel listener 关掉就不 work 了.
虽然它依然有监听 scroll 并且也用来做 sticky 但是如上面说的, div scroll 电脑不一定会卡, 所以其实它的 wheel 并不是必须的, 但是可以确定有会更好.
那手机呢?
更厉害, 把 scroll, wheel listener 都关掉后, 会发现 sticky 依然 work. google 是通过拦截 touchstart, touchmove, touchevent 来替代 scroll event 的, 所以它的 scroll 节奏是自己控制的.
对比左边原生 scroll 的余力, table 的明显比较生硬. 好像拆刹车那样, 突然就停了. 虽然 google 已经尽可能模拟的很好了.
小知识:为什么 fixed 不卡,translate 卡?
fixed 是把 sticky element 定位在 body viewport,它就定在那边一动也不动 (一直不动),自然不会卡。
translate 是一直修改自己的位置 (一直动),自然会卡。
总结
原生 sticky 是最省心的. 尽可能用它, 哪怕被迫修改 layout 满足 max area 和 scroll container 的要求.
如果真的没办法. 那就用 fixed, absolute 方案去解决. 但是不值得去封装它, 因为它开启了潘多拉, 很容易失控.
如果你遇到 table horizontal vertical 的情况, fixed 解决不了. 唯一的方法就是像 google 那样用 translate. 而且必须大动作, 替代 scroll event 自己控制节奏才能确保 sticky smooth.
这个就可以封装, 对比 fixed 方案, 它实现原理相对稳定, 不容易被其它影响.
CSS & JS Effect – Simulation Position Sticky (用 JavaScript 实现 position sticky)的更多相关文章
- 前端工程师面试问题归纳(一、问答类html/css/js基础)
一.参考资源 1.前端面试题及答案整理(一) 2.2017年前端面试题整理汇总100题 3.2018最新Web前端经典面试试题及答案 4.[javascript常见面试题]常见前端面试题及答案 5.W ...
- CSS & JS 制作滚动幻灯片
==================纯CSS方式==================== <!DOCTYPE html> <html> <head> <met ...
- html+css+js实现标签页切换
CSS部分: #Tab { margin:0 auto; width:640px; border:none; position:absolute; z-index:9; margin-left:320 ...
- CSS+JS实现兼容性很好的无限级下拉菜单
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DT ...
- CSS+JS下拉菜单和纯CSS下拉菜单
下拉菜单 (思路:先把二级定位到屏幕外,鼠标悬停重新定位回来:另一个就是ul浮动,li也浮动) 下拉菜单的一般思路就是把子导航嵌套在无序列表中,把列表定位到屏幕之外,当鼠标悬停在其父列表项上时,重新定 ...
- Google HTML/CSS/JS代码风格指南
JS版本参见:http://www.zhangxinxu.com/wordpress/2012/07/google-html-css-javascript-style-guides/ HTML/CSS ...
- 使用html+css+js实现弹球游戏
使用html+css+js实现弹球游戏 效果图: 代码如下,复制即可使用: <!doctype html> <head> <style type="text/c ...
- html/css/js 学习笔记 - 牛客网试卷:前端工程师能力评估
display属性 : block : CSS1 块对象的默认值.将对象强制作为块对象呈递,为对象之后添加新行 可以定义高度和宽度 none : CSS1 隐藏对象.与 visibility 属性 ...
- 文字添加响应事件,js动态加载CSS, js弹出DIV
文字添加响应事件,js动态加载CSS, js弹出DIV <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&qu ...
- DIV+CSS+JS实现色彩渐变字体
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
随机推荐
- [oeasy]python0010_怎么用命令行保存文件
编写 py 文件 回忆上次内容 上次 真的输出了 程序员的浪漫 Hello world! print函数 可以输出 字符串 但是 print这个词 别拼错 就连 大小写 也别能错 错了就改 也没事 ...
- oeasy教您玩转vim - 24 - 自定颜色
自定颜色 回忆上节课内容 这次我们研究了配色方案 找到了 colors 的位置 下载并应用了颜色方案 制作了自己的配色方案 下面我想修改配色方案的颜色 是否能成功??? 首先得有自己的颜色方案 #找到 ...
- 关于我自己的Gui界面库完善
仓库地址:https://gitee.com/GPRO/Gui 功能说明: 解析XML, 接入AngleScript. 接下来需要做的: 因为有了WPF,MFC,HTML甚至UE5的使用经验,这里我 ...
- 关于构建一个可视化+code系统的思路
思路是有参考UE的现有功能,加之前的逻辑. 大概分为三个模块: 底层, 即native层 ,这一层实际上分为三个部分: 1.GUI层的解析,2.数据存储 3.Code的解析 这三部分关键在于他们 ...
- RHCA rh442 005 (NICE FIFO RR) 资源强占与分配 cpuset
cgroup 容器 控制服务访问 limits 控制用户 进程管理 [root@servera ~]# ps -aux | more USER PID %CPU %MEM VSZ RSS TTY ST ...
- 如何通过PowerShell批量修改O365用户的office phone属性值
我的博客园:https://www.cnblogs.com/CQman/ 如何通过PowerShell批量修改O365用户的office phone属性值? 需求信息: 组织中的O365用户在创建时, ...
- 【Oracle】Windows-19C 下载安装
下载 Download 官网下载地址[需要Oracle账号]: https://www.oracle.com/database/technologies/oracle-database-softwar ...
- 【Spring-Security】Re08 Thymeleaf权限控制 与 退出功能
一.需要的组件支持: 新版本这里的组件有些问题: https://blog.csdn.net/qq_36488647/article/details/104532754 https://blog.cs ...
- 实现一个终端文本编辑器来学习golang语言:第二章Raw模式下的输入输出
从第二章开始,在每个小节的最后都会有一些代码实操作业,你可以选择自己完成(比较推荐),再对照我的实现方式,当然也可以直接看我的代码实现.不过,之后的各个功能实现,我都会基于我先前的代码实现版本,在它的 ...
- 大语言模型(LLM)运行报错:cannot import name 'AutoModel' from 'transformers'
解决方法: 安装pytorch即可,不过需要注意项目的README文件和requirements文件,安装对应版本的pytorch即可.