Open closertb opened 4 years ago
演示Demo地址(手机端打开):https://closertb.site/Klotski/index.html#demo 演示Demo源码:https://github.com/closertb/klotski
最近做了一个需求,准确说是迭代需求:加了一个头部概览(类似下图),以更好的让用户观察到营销变化,故事的开头就这样悄悄的埋下了伏笔。
以前这个页面只是一个评价列表(可上拉加载),为了数据更易读,列表的头采用了固定布局。然而加了这个概览时,产品没提,我就简单粗暴的将这个列表头换成了相对布局,ok,提测。 但第二天,我发现上拉加载数据多了,列表头部被顶上去之后,想再做筛选,就要再把列表上滑才能看到,这个体验非常之差。于是同事就说要不问问产品,要不把概览加概览做成固定。
我第一反应就是,恐怕提了之后,产品会让我只把筛选列表头部做成固定,注意那个只。
只
然后就有了下面的对话:
果然怕什么,来什么。但就像同事说的,自己问的需求,含着泪也要接下。
后面经评论提点,又加入了sticky的方案,确实是最优解。
以下代码是页面的dom结构
dom
<div id="demo" className={style.demo}> <h3 id="title" className="title">这是一个概览头部</h3> <div id="content" className="content"> <div className="filter-bar"> <h3>这是列表头部</h3> <h3>可筛选</h3> <h3>下面是滚动列表</h3> </div> <ul className="list"> {arr.map(({ key, label }) => <li key={key}>{label}</li>)} </ul> </div> </div>
因为页面本身就有scroll事件监听,所以第一个念头是用JS完成,但当时已经下班,又是周五,感觉5分钟内搞不定,所以我就跑了。
现在来尝试用JS实现,先理一下思路:
JS 代码
useEffect(() => { const demo = document.querySelector('#demo'); const content = document.querySelector('#content'); const titleHeight = document.querySelector('#title').clientHeight; let fixed = false; demo.addEventListener('scroll', (e) => { // 添加吸顶 if (!fixed && e.target.scrollTop >= titleHeight) { fixed = true; content.classList.add('with-fixed'); } // 取消吸顶 if (fixed && e.target.scrollTop < titleHeight - 5) { content.classList.remove('with-fixed'); fixed = false; } }); }, []);
看起也不难,但其实离代码上线,还有很大优化的空间,后面会分析补充。
JS 看似很简单,但就像那句热门句子:这突如其来的噩耗,让本不富裕的家庭雪上加霜。在这种有下拉加载的页面,我们本来就在监听里面做了很多逻辑处理,所以能用CSS实现的,就尽量不要再去麻烦JS了。
这突如其来的噩耗,让本不富裕的家庭雪上加霜
首先理一下思路,深挖产品的需求:
列表是上拉加载的;
当理清上面思路时,我们发现,其实就是当列表很长时,隐藏概览头部,简单用伪代码表示就是(vh是视口单位 ,100vh代表整个屏幕可视高度):
if (titleHeight + filterBarHeight + listHeight > 100vh) { title.hide(); }
那又怎样实现概览头部隐藏,而筛选头和列表又正好出于视口呢?
filterBarHeight + listHeight = 100vh
当用户往上划,只需要内容(筛选头和列表)正好是一个视口高度(100vh)时,概览头就恰好被隐藏,而筛选头又正好吸顶,用CSS实现就是类似这样的:
// 不是完整代码,详情请看demo: .demo { :global { .title { height: 15vh; line-height: 15vh; text-align: center; border-bottom: 1PX solid #eee; background-color: #fff; } .filter-bar { height: 15vw; background-color: #888; display: flex; align-items: center; } .list { max-height: calc(100vh - 15vw);; // 这里的设置很重要 overflow: scroll; background-color: rgba(127, 255, 212, .8); }
在css中的定位(position)属性值中有个不常用的:sticky;
position
sticky
MDN官方文档摘录: 元素根据正常文档流进行定位,然后相对它的最近滚动祖先和最近块级祖先,包括table-related元素,基于top, right, bottom, 和 left的值进行偏移。粘性定位可以被认为是相对定位和固定定位的混合。元素在跨越特定阈值前为相对定位,之后为固定定位。
这里我们在沿用JS的方案上进行更改,只需要将filter-bar 的定位属性改为粘性定位,就可以去除对 JS 的依赖;
.demo { max-height: 100%; overflow: scroll; .filter-bar { position: sticky; top: 0; } }
demo类作用于最外层dom(<div className={style.demo}>)上,其视高为100vh,当内容超出高度时为滚动;filter-bar 元素采用粘性定位,当高度距离demo元素大于0时,其采用相对定位,即以正常文档流的形式定位;当高度小于等于0时,其采用固定定位,就达到吸顶的效果。
<div className={style.demo}>
是不是感觉CSS很简单,稍微设置一下即搞定,只是要想到内容高度正好是100vh需要一点经(yun)验(qi),经常写H5的,sticky的方案相信也是新手黏来。其实不光简便,对比JS至少还有两个个优点:
100vh
filterBar
当然,viewport方案还有个ios手机的兼容性问题,由于safari的头部和底部滑动时可见性会改变,所以当Bar可见时,实际的100vh高于屏幕可见高度,就会导致吸顶头部被遮挡。到目前为止,虽然网上有很多说height: -webkit-fill-available;,但针对这种场景是无效的;但只要依赖100vh,都面临这种困局,safari太奇葩,下一个IE就是它了.
height: -webkit-fill-available;
经过上面分析,100vh在IOS safari上的致命问题,会让这种100vh这种纯CSS的方案褪色。但PC页面,或者你和我一样,要编写的页面是运行在APP中(即没有bar存在),那这种方案就是可行的。所有的方案都要具体场景,具体分析,没有谁出生就是完美。这里只是提高一种思考方式,长点见识。
而sticky方案不依赖于100vh,其可以用100%的写法,所以没有这个担忧,所以相比之下,最优解就是sticky; 但height: 100% 是个无底洞,你需要从html 标签写起,一直写到具有滚动的容器元素。
height: 100%
如果对重绘重排有兴趣,建议观看Chrome的官方博文: 浏览器四部曲
说完局部弹性吸顶,再说一个常见的,选择性吸底:在页面内容不足100vh时,我们希望Footer是吸底的,当页面内容大于100vh时,Footer处于正常文档流,让内容可视区域更大,而又不会因为内容太少影响美观,见图:
Footer
像第一张图那样不做定位的还是大有人在,因为他们坚信自己网站的内容不会出现不够的时候,但以前更常见做法是底部固定定位。
弹性吸底利用min-height 加绝对定位,其实现很简单。核心代码不超过5行css:
min-height
body{ position: relative; min-height: 100vh; } footer { width: 100%; position: absolute; bottom: 0; }
原理就是内容区域最低高度为一个屏幕,然后底部相对屏幕进行绝对定位;当内容变多时,高度大于100vh,由于是依赖bottom: 0;,所以会一直吸底,其巧妙之处就在于此。
bottom: 0;
针对于这个场景,height: -webkit-fill-available 就是有效的。
height: -webkit-fill-available
更多关于-webkit-fill-available, 参见[https://allthingssmitty.com/2020/05/11/css-fix-for-100vh-in-mobile-webkit/];
vh 确实是个好东西,可以解决移动端的适配问题, ;sticky 这种非热门属性(经常写PC的人)值得花时间去学习。我个人觉得作为一个合格的前端,CSS 仍然是必备技能,偶尔看看张旭鑫,还是有必要的;不要对JS产生太多的依赖,不是不可以,而是好钢要用在刀刃上。
演示Demo地址(手机端打开):https://closertb.site/Klotski/index.html#demo 演示Demo源码:https://github.com/closertb/klotski
哪些想啥提啥的产品们
最近做了一个需求,准确说是迭代需求:加了一个头部概览(类似下图),以更好的让用户观察到营销变化,故事的开头就这样悄悄的埋下了伏笔。
以前这个页面只是一个评价列表(可上拉加载),为了数据更易读,列表的头采用了固定布局。然而加了这个概览时,产品没提,我就简单粗暴的将这个列表头换成了相对布局,ok,提测。 但第二天,我发现上拉加载数据多了,列表头部被顶上去之后,想再做筛选,就要再把列表上滑才能看到,这个体验非常之差。于是同事就说要不问问产品,要不把概览加概览做成固定。
我第一反应就是,恐怕提了之后,产品会让我
只
把筛选列表头部做成固定,注意那个只。然后就有了下面的对话:
果然怕什么,来什么。但就像同事说的,自己问的需求,含着泪也要接下。
后面经评论提点,又加入了sticky的方案,确实是最优解。
局部吸顶
以下代码是页面的
dom
结构JS 实现
因为页面本身就有scroll事件监听,所以第一个念头是用JS完成,但当时已经下班,又是周五,感觉5分钟内搞不定,所以我就跑了。
现在来尝试用JS实现,先理一下思路:
看起也不难,但其实离代码上线,还有很大优化的空间,后面会分析补充。
CSS Viewport实现
JS 看似很简单,但就像那句热门句子:
这突如其来的噩耗,让本不富裕的家庭雪上加霜
。在这种有下拉加载的页面,我们本来就在监听里面做了很多逻辑处理,所以能用CSS实现的,就尽量不要再去麻烦JS了。首先理一下思路,深挖产品的需求:
列表是上拉加载的;
当理清上面思路时,我们发现,其实就是当列表很长时,隐藏概览头部,简单用伪代码表示就是(vh是视口单位 ,100vh代表整个屏幕可视高度):
那又怎样实现概览头部隐藏,而筛选头和列表又正好出于视口呢?
当用户往上划,只需要内容(筛选头和列表)正好是一个视口高度(100vh)时,概览头就恰好被隐藏,而筛选头又正好吸顶,用CSS实现就是类似这样的:
最优实现 CSS Sticky
在css中的定位(
position
)属性值中有个不常用的:sticky
;MDN官方文档摘录: 元素根据正常文档流进行定位,然后相对它的最近滚动祖先和最近块级祖先,包括table-related元素,基于top, right, bottom, 和 left的值进行偏移。粘性定位可以被认为是相对定位和固定定位的混合。元素在跨越特定阈值前为相对定位,之后为固定定位。
这里我们在沿用JS的方案上进行更改,只需要将filter-bar 的定位属性改为粘性定位,就可以去除对 JS 的依赖;
demo类作用于最外层dom(
<div className={style.demo}>
)上,其视高为100vh,当内容超出高度时为滚动;filter-bar 元素采用粘性定位,当高度距离demo元素大于0时,其采用相对定位,即以正常文档流的形式定位;当高度小于等于0时,其采用固定定位,就达到吸顶的效果。对比
是不是感觉CSS很简单,稍微设置一下即搞定,只是要想到内容高度正好是
100vh
需要一点经(yun)验(qi),经常写H5的,sticky的方案相信也是新手黏来。其实不光简便,对比JS至少还有两个个优点:filterBar
高度,来填补筛选头离开正常文档流;(解决方案就是在筛选头外多套一层dom,并给一个固定高度,这样筛选头脱离正常文档流,但高度依旧还在);当然,viewport方案还有个ios手机的兼容性问题,由于safari的头部和底部滑动时可见性会改变,所以当Bar可见时,实际的100vh高于屏幕可见高度,就会导致吸顶头部被遮挡。到目前为止,虽然网上有很多说
height: -webkit-fill-available;
,但针对这种场景是无效的;但只要依赖100vh,都面临这种困局,safari太奇葩,下一个IE就是它了.经过上面分析,100vh在IOS safari上的致命问题,会让这种100vh这种纯CSS的方案褪色。但PC页面,或者你和我一样,要编写的页面是运行在APP中(即没有bar存在),那这种方案就是可行的。所有的方案都要具体场景,具体分析,没有谁出生就是完美。这里只是提高一种思考方式,长点见识。
而sticky方案不依赖于100vh,其可以用100%的写法,所以没有这个担忧,所以相比之下,最优解就是
sticky
; 但height: 100%
是个无底洞,你需要从html 标签写起,一直写到具有滚动的容器元素。如果对重绘重排有兴趣,建议观看Chrome的官方博文: 浏览器四部曲
弹性吸底
说完局部弹性吸顶,再说一个常见的,选择性吸底:在页面内容不足100vh时,我们希望
Footer
是吸底的,当页面内容大于100vh时,Footer
处于正常文档流,让内容可视区域更大,而又不会因为内容太少影响美观,见图:像第一张图那样不做定位的还是大有人在,因为他们坚信自己网站的内容不会出现不够的时候,但以前更常见做法是底部固定定位。
弹性吸底利用
min-height
加绝对定位,其实现很简单。核心代码不超过5行css:原理就是内容区域最低高度为一个屏幕,然后底部相对屏幕进行绝对定位;当内容变多时,高度大于100vh,由于是依赖
bottom: 0;
,所以会一直吸底,其巧妙之处就在于此。针对于这个场景,
height: -webkit-fill-available
就是有效的。更多关于-webkit-fill-available, 参见[https://allthingssmitty.com/2020/05/11/css-fix-for-100vh-in-mobile-webkit/];
总结
vh 确实是个好东西,可以解决移动端的适配问题, ;sticky 这种非热门属性(经常写PC的人)值得花时间去学习。我个人觉得作为一个合格的前端,CSS 仍然是必备技能,偶尔看看张旭鑫,还是有必要的;不要对JS产生太多的依赖,不是不可以,而是好钢要用在刀刃上。