写点什么

用户体验 | 页面阅读进度提示

用户头像
云小梦
关注
发布于: 2021 年 03 月 28 日

相信很多人都在项目中用过这么一个玩意 —— NProgress.js 库,或者是其它类似的库,它们的作用很实用:页面加载进度提示。

顾名思义,就是在刚进入页面或刷新或请求数据时在页面顶部有一个进度条给用户以反馈,使页面加载显得不那么“尴尬”。


但只有很少人见过 “在页面顶部实时反馈当前阅读进度” 的效果 —— 为什么?因为有滚动条。

但不得不提的是:这真的很有用!不管是“在有些要求高的页面对自带滚动条很厌恶”还是“即使有滚动条也可以给用户更好看的提示效果”。当然,目前第一种场景比较多。


-----


用 JS 达到实时进度提示效果


这得益于 JS 的一些“别致”的 API ——


- 滚动距离:document.documentElement.scrollTop || document.body.scrollTop;

- 获取文档真实高度:document.documentElement.scrollHeight;

- 获取浏览器窗口可视高度:document.documentElement.clientHeight;


这里需要注意的是:scrollTop 指的是啥?页面滚动条纵坐标位置。也就是页面相对于窗口显示区左上角的 Y 坐标。所以有的地方也用下面的 API 代替:window.pageYOffset



回到我们这个想法中:其实我们要展示出来的,不就是“<font size="4" color="skyblue">当前向上滑动了多少</font>”与“<font size="4" color="skyblue">文档区域总高度</font>”的比例吗?

那如果实时监听变化并展示到页面上,不就成了?


let scrollHeight=document.documentElement.scrollHeight;let clientHeight=document.documentElement.clientHeight;document.addEventListener('scroll',function(e){	let scrollTop=document.documentElement.scrollTop || document.body.scrollTop;	const sizeHeight=+(scrollTop/(scrollHeight-clientHeight)).toFixed(2)*100;	console.log(sizeHeight)})
复制代码



然后将其展示到页面上 —— 采用 css 控制父元素位置、js 控制子元素 width 的方式渲染:


<div class="pro_bar">    <div class="bar_cess"></div></div>
复制代码


.pro_bar{    position: fixed;    top: 0;    left: 0;    width: 100%;    height: 3px;    background-color: #DDD;    overflow: hidden;}.pro_bar .bar_cess{    position: absolute;    left: 0;    height: 100%;    background-color: #0089f2;}
复制代码


let scrollHeight=document.documentElement.scrollHeight;let clientHeight=document.documentElement.clientHeight;let bar=document.querySelector('.bar_cess');document.addEventListener('scroll',function(e){    let scrollTop=document.documentElement.scrollTop || document.body.scrollTop;    const sizeHeight=+(scrollTop/(scrollHeight-clientHeight)).toFixed(2)*100;    bar.style.width=sizeHeight+"%";})
复制代码


但 js 实现难免会遇到比如“回流和重绘”一类的性能问题。通常一看到监听 resize 之流就会下意识的“防抖”、“节流”、“脱离文档流渲染”...如临大敌。

其实 dark 不必:


-----


用 CSS 实现进度提示


这个的实现原理也很简单,是笔者之前提过的 —— linear-gradient ,嘿嘿,没想到吧?让我来带各位逐步分析:

首先,是 linear-gradient 的第一个参数。它是规定方向的 —— 这里需要注意的是:它实际相当于“划”了一条线,按你规定的方向从你规定的比例中将页面给分割开(这一点具体的参见我这篇文章),比如 linear-gradient(to right top, #0089f2 50%, #DDD 50%) ,效果如下:


有了这么一个“蓝色三角形”,你有没有想到什么?


如果控制宽高把这部分大小设置为“滚动条拉到最底部时蓝色区域的最底部也刚好到页面顶部”,就像这样:

那岂不就接近我们想要的效果了?

这有两种实现方式!


1. 第一种方式:


body{    background: linear-gradient(to right top, #0089f2 50%, #DDD 50%);    background-size: 100% calc(100% - 122vh + 129px);    background-repeat: no-repeat;}
复制代码


通过 background-size 设置了渐变的大小:


进一步说,我们需要的是一个顶部的滚动条,而不是全屏的三角块 —— 大方向已经确定了,这时候就可以通过伪元素去覆盖三角形背景区域,使之只在顶部一小块区域内展示出来!


为什么要用伪元素呢?其实更确切地说还是 ::before/::after,因为伪元素不在文档流之内,方便渲染和控制,而且,这里设置 linear-gradient 的是 background,我们如果想要实现“覆盖”,也就只能用一个“属于相同元素自身”的属性去实现。综合来说,在类似场景下,伪元素是最好的选择了。


实现代码:


<main>    <h2>I was interested to see if I could make a scroll indicator  with just CSS.</h2>    <p>You can! But maybe you shouldn't. This is an interesting consequence of a bunch of hacks held together with duct tape. It uses z-index hacks, gradient hacks and tricks with calc and viewport units.</p>    <p>Having said that, hacks are not always bad. I love hacks and many of us have made quite a good living selling floats and clearfixes.</p>    <p>The techniques used here are well supported, if not conventional. If you can read the CSS, understand how it works, and how to change it, and you think this works better for you than JavaScript, feel free to implement it. Just be aware of the z-index behaviour and possible conflict with other CSS using negative z-index.</p>    <hr>    <h3>Tristique Aenean Etiam Cras</h3>    <p>Donec id elit non mi porta gravida at eget metus. Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue. Donec sed odio dui. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.</p>    <p>Cras mattis consectetur purus sit amet fermentum. Donec id elit non mi porta gravida at eget metus. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Etiam porta sem malesuada magna mollis euismod. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec ullamcorper nulla non metus auctor fringilla.</p>    <p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Donec ullamcorper nulla non metus auctor fringilla. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Aenean lacinia bibendum nulla sed consectetur. Nulla vitae elit libero, a pharetra augue.</p>    <p>Donec sed odio dui. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Cras mattis consectetur purus sit amet fermentum. Maecenas sed diam eget risus varius blandit sit amet non magna.</p>    <ul>        <li>Ullamcorper Aenean Ornare</li>        <li>Ridiculus Lorem Malesuada Consectetur</li>        <li>Aenean Tristique Sit Lorem Purus</li>        <li>Vehicula Egestas Mollis Cursus Nibh</li>    </ul>    <p>Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Sed posuere consectetur est at lobortis. Sed posuere consectetur est at lobortis. Maecenas faucibus mollis interdum. Nullam id dolor id nibh ultricies vehicula ut id elit. Aenean lacinia bibendum nulla sed consectetur. Nullam quis risus eget urna mollis ornare vel eu leo.</p>    <h3>Bibendum Aenean Dapibus Tristique</h3>    <p>Cras mattis consectetur purus sit amet fermentum. Donec id elit non mi porta gravida at eget metus. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Etiam porta sem malesuada magna mollis euismod. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec ullamcorper nulla non metus auctor fringilla.</p>    <p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Donec ullamcorper nulla non metus auctor fringilla. Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Aenean lacinia bibendum nulla sed consectetur. Nulla vitae elit libero, a pharetra augue.</p>    <p>Donec sed odio dui. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Cras mattis consectetur purus sit amet fermentum. Maecenas sed diam eget risus varius blandit sit amet non magna.</p>    <ul>        <li>Ullamcorper Aenean Ornare</li>        <li>Ridiculus Lorem Malesuada Consectetur</li>        <li>Aenean Tristique Sit Lorem Purus</li>        <li>Vehicula Egestas Mollis Cursus Nibh</li>    </ul>    <p>Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Sed posuere consectetur est at lobortis. Sed posuere consectetur est at lobortis. Maecenas faucibus mollis interdum. Nullam id dolor id nibh ultricies vehicula ut id elit. Aenean lacinia bibendum nulla sed consectetur. Nullam quis risus eget urna mollis ornare vel eu leo.</p>    <h2>Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.</h2>    <p>Cras mattis consectetur purus sit amet fermentum. Donec id elit non mi porta gravida at eget metus. Donec id elit non mi porta gravida at eget metus. Aenean lacinia bibendum nulla sed consectetur.</p></main>
复制代码


html,body { margin:0;padding: 0;}@supports (height: 100vh) {    body{        background: linear-gradient(to right top, #0089f2 50%, #DDD 50%);        background-size: 100% calc(100% - 122vh + 129px); /** 这里“%”换成“vw”也可 */        background-repeat: no-repeat;    }    body::before{        content:'';        position: fixed;        top: 2px;        bottom: 0;        width: 100%;        z-index: -1;        background: #fff; /** 这个才是真正页面的背景!不管是颜色还是图片都在这里替换! */    }}
复制代码


这种方法当然也有一丝缺点:它需要“只能偏大不能偏小”的控制background-size的宽度,但是这显然不适应精确在不同分辨率大小且需要响应式的屏幕中展示!


2. 第二种方法:


经过上面的折腾,我想我们已经掌握了“密码”:定位+覆盖。

如果你不想直接在 body 中操作,你完全可以再定义一个元素,让它跟随 body 定位不就可以了?因为 body 是不会动的,相对 body 定位就可以达到 fixed 效果!


<!-- 在body下添加一个空标签 --><div class="scroll_fixed"></div>
复制代码


body {    position: relative;}.scroll_fixed {    position: absolute;    top: 0;    right: 0;    left: 0;    bottom: 0;    background: linear-gradient(to right top, #0089f2 50%, transparent 50%) no-repeat;    background-size: calc(100% + 5px) calc(100% - 100vh + 17px);    z-index: 1;    pointer-events: none;    mix-blend-mode: darken;}.scroll_fixed::after {    content: '';    position: fixed;    top: 2px;    bottom: 0;    right: 0;    left: 0;    background: #fff;    z-index: 1;}
复制代码


这种方式对于第一种方式的缺点非常完美的解决了,但是这种方式对于页面整体背景没有强制要求颜色或图片的还可以,但在我为“不小心”为 body 加了背景图后,它就不行了、没有效果了。(因为上面说了,这种方式实现的特点是同级 z-index 覆盖伪元素的“伪页面”)



这里面有个mix-blend-mode: darken;,这是个非常好用的属性 —— 规定被规定的元素和它下面的背景相比谁的颜色就用谁的颜色!(就是这个玩意引起的问题)


这个小知识是笔者跟张鑫旭老师的 这篇文章 学到的,嘿嘿,还挺好用


发布于: 2021 年 03 月 28 日阅读数: 8
用户头像

云小梦

关注

求知若渴,虚心若愚 2019.05.11 加入

江湖人称“云小梦”。一个大前端路上还未“毕业”的“小学生”。爱好分享、执着探索、乐于开源;着迷于vue、node、css、可视化、前端智能化以及原生js技术。csdn链接:https://yunxiaomeng.blog.csdn.net/

评论

发布
暂无评论
用户体验 | 页面阅读进度提示