本文介绍三种方式做移动端吸顶,记录不同方式下遇到的问题及思考.
1-sticky做吸顶
sticky定位没有导致元素脱离文档流
sticky失效
sticky兼容性查询:好用的查询网址
什么时候,会导致粘贴定位失效呢?(下面这两种是网上已有的)
1.父级元素设置了overflow:hidden
2.包裹的最近的父级元素高度小于等于sticky元素的高度会失效
自己遇到的一个sticky失效原因竟然是双层tab吸顶问题
原因:
第一层tab是手写的样式,第二层tab使用的是ant.design中的tab组件,显然出现问题原因是,第二层tab导致的内容过长出现了滚动条,目前这个sticky定位的父元素是高于sticky元素的,但是定位还是失效了。
sticky文字抖动问题
这是需要在IOS系统上,使用小于100%的比例或者大于100%比例才会出现的问题
详细见链接视频:
B站视频
sticky定位,当使用rem单位时,会出现细线问题
这个在IOS手机上很明显,在安卓机上,有时能看的出来,细线出现的原因是后面有背景色,rem设置高度的元素不能紧凑的连接起来,而且由于抖动问题,所以细线就此产生
使用变量控制类名,从而转换样式
如果ts报错,不能把null赋值给element,
使用下面这条语句,就能没有ts类型错误
使用IntersectionObserver对象监听是否吸顶
useEffect(() => {
fetch('http://localhost:80/admin/story', {
method: 'get',
}).then((response) => response.json())
.then((res) => {
// console.log(res.data.rows)
handlerStory(res.data.rows)
}).catch((err) => {
console.log(err, 'error');
})
const dom: HTMLElement = watchDOM.current as unknown as HTMLElement;
let isVisibility = true;
const io = new IntersectionObserver(() => {
isVisibility = !isVisibility;
setIsTop(isVisibility)
})
io.observe(dom);
return () => {
io.disconnect();
}
}, [])
为什么使用类名比使用dom修改样式更好?
chrome 中渲染流水线的流程
一个 html 是怎么被处理成一个页面的。html 中 dom 部分生成 dom tree,css 部分生成 stylesheet,dom tree 在解析完后会等待 stylesheet 构建完再渲染。stylesheet 根据默认样式、样式继承、css 选择器规则、样式优先级等规则,找到对应的 dom 节点赋予它样式,形成 dom 结构+样式的 render tree。其中有些节点是不可见的(不是 opacity: 0,而是诸如 display: none 这样的),它们不会影响其他节点的位置,在渲染时不需要考虑,所以过滤掉这些节点之后生成 layout tree,layout tree 会根据节点之间的相互影响生成它们的位置信息(reflow 回流/重排)。
dom tree —>stylesheet —> render tree —> 节点是否可见—> layout tree(节点的位置信息)
你以为到了 layout tree 这一步终于可以渲染了吗,还远没有。layout tree 会根据某些 css 属性分层,比如 position: absolute; position: fixed 等等。如果它们发生更新,不需要连带其他节点 reflow,所以分层有利于单独处理。然后每个图层会生成各自的绘制指令列表(repaint 重绘),很底层的命令,描述了每一个点每一条线如何绘制。
layout tree —> 分层 ----> repaint–>合成层(优先处理可视区图块)–>biz组件–>显示器的后缓冲区–>前后缓冲区交换,显示下一帧
gpu绘制:图块的绘制命令会通过光栅化线程池交给 gpu 绘制
你以为生成了绘制命令终于可以渲染了吗,还远没有。它们会被交给合成层,顾名思义,它是负责将那些图层合并的。它并不会全量的处理整个页面,而是优先处理可见视口附近的图块,如果页面过于复杂,它还会先给出低分辨率的位图。图块的绘制命令会通过光栅化线程池交给 gpu 绘制。合成层拿到 gpu 绘制出来的位图后,将它们合成为一张位图,这就是当前页面。你觉得终于渲染完了吗,并没有。它会将位图交给浏览器进程里的 biz 组件,biz 组件会交给显示器的后缓冲区,当显示器需要显示下一帧之前,前后缓冲区交换,屏幕上终于展示出新渲染的页面帧。(这里补个小知识,requestAnimationFrame 的背后原理就是显示器发送了 sync 信号,渲染进程将 requestAnimationFrame 回调放进消息队列,从而实现了 js 未阻塞的情况下 requestAnimationFrame 可以随帧调用)
可以发现浏览器想渲染一帧页面要经过如此多的步骤,是不是真为它的性能捏一把汗,这也是 HTML 方便开发带来的代价。我们还可以看出整个流程中最昂贵的步骤就是 reflow 和 repaint 了,至于合成层那边主要是和 GPU 打交道,不会占用渲染线程(也就是执行 js 的线程),并且 GPU 本来就十分擅长处理图片,所以合成层的工作很快。
因此,性能优化的核心思路其实就是尽可能减少 reflow、repaint 的工作,尽可能多利用合成层的工作。比如 css 硬件加速,包括 transform3D、opacity、willchange 等。拿 transform3D 来说,其实它只是图层的位移、转换,并不影响其他图层,所以不会经过 reflow 和 repaint,直接在合成层处理,GPU 处理这种变换非常快。因此硬件加速技巧可以极大的优化 css 性能。
下面谈一下滚动操作带来的影响在渲染流水线中的处于什么位置。首先滚动可能会产生滚动条,它的突然出现影响了其他元素的布局位置,会触发 reflow 以及后面的所有流程。滚动过程中,前面说过合成层初始优先处理可见视口附近的图块,其他部分其实还没有处理,元素结构太复杂时滚动过快可能让合成层来不及处理,从而出现白屏区域。还有比如 position: fixed 的元素,会跟着滚动走,那么它也会在滚动中 repaint 的。
性能对比:
使用变量+IntersectionObserver+sticky
使用变量+scroll +sticky
使用变量+scroll +sticky+debounce(防抖500ms)
使用dom +scroll +sticky+debounce(防抖500ms)
使用dom +scroll +sticky
2-使用固定定位做吸顶-fixed
固定定位由于脱离文档流,所以计算监听元素与吸顶元素之间的距离比较难以计算,计算出现误差就会导致页面出现弹跳现象
fiexd脱离正常文档流
可以在吸顶的这个时候做样式修改
不需要在使用父盒子设置高度给吸顶元素做保底
还是会产生rem细线问题
但是文字不会产生抖动效果
脱离文档流后,宽度100%包含了滚动条的宽度
在移动端由于滚动条很小,所以影响很小,但是还是能够看出来,对不齐了
获取页面内滚动条宽度
function getScrollbarWidth() {
if (scrollbarWidth !== null) {
return scrollbarWidth;
}
if (typeof document !== 'undefined') {
const div = document.createElement('div');
const newStyles = {
width: '100px',
height: '100px',
position: 'absolute',
top: '-9999px',
overflow: 'scroll',
MsOverflowStyle: 'scrollbar',
};
Object.keys(newStyles).map((style) => {
// @ts-ignore
div.style[style] = newStyles[style];
});
document.body.appendChild(div);
scrollbarWidth = div.offsetWidth - div.clientWidth;
document.body.removeChild(div);
} else {
scrollbarWidth = 0;
}
return scrollbarWidth || 0;
}
fixed吸顶元素与监听元素的距离,监听元素高度过高,会导致内容跳动,高度过低,会导致吸顶元素与内容脱轨
3-使用内容fiexd,达到吸顶效果
当页面出现两个滚动条时,滚动触发时机无法掌控
设置overflow:scroll属性的元素,一定要设置高度,不然无法产生滚动条
可能第一个滚动条滚动到底,才开始滚动第二个滚动条,也可能第一个滚动条滚动一半,开始滚动第二个滚动条,还可能先滚动第二个滚动条,第一个滚动条不滚动,取决于鼠标所在位置.
鼠标在滚动第一个滚动条时,由于页面滚动,鼠标处于第二个滚动元素上面,如果此刻鼠标移动了,第二个滚动条开始滚动, 第二个滚动条滚动完,如果第一个滚动条还有未滚动的位置,第一个滚动条继续滚动;–1
如果鼠标不移动,第一个滚动条滚动,滚动完成之后,第二个滚动条不会滚动;----2
鼠标如果在第一个产生滚动的元素上面,只滚动第一个滚动条;----3
如果鼠标位于第二个产生滚动元素身上,则只有第二个元素滚动,第一个滚动条不滚动;----4
所以争对上面的效果,内容fixed无法达到效果,
不过如果是tab固定定位,这样实现效果也很好
参考资料:
https://mp.weixin.qq.com/s/hFG1ypsEckVIZb5OCv0H7g
https://www.hongkiat.com/blog/writing-better-css/