手把手教你实现在页面顶部实时反馈当前阅读的进度条

前端Q

共 15083字,需浏览 31分钟

 · 2021-03-09


作者:行舟客
原文链接:https://blog.csdn.net/qq_43624878/article/details/109993036

背景

相信很多人都在项目中用过这么一个玩意 —— NProgress.js库,或者是其它类似的库,它们的作用很实用:页面加载进度提示。顾名思义,就是在刚进入页面或刷新或请求数据时在页面顶部有一个进度条给用户以反馈,使页面加载显得不那么“尴尬”。

但只有很少人见过 “在页面顶部实时反馈当前阅读进度” 的效果 —— 为什么?因为有滚动条。但不得不提的是:这真的很有用!不管是“在有些要求高的页面对自带滚动条很厌恶”还是“即使有滚动条也可以给用户更好看的提示效果”。当然,目前第一种场景比较多。

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

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

  • 滚动距离:document.documentElement.scrollTop || document.body.scrollTop;
  • 获取文档真实高度:document.documentElement.scrollHeight;
  • 获取浏览器窗口可视高度:document.documentElement.clientHeight;

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

回到我们这个想法中:其实我们要展示出来的,不就是“当前向上滑动了多少”与“文档区域总高度”的比例吗?那如果实时监听变化并展示到页面上,不就成了?

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;
    top0;
    left0;
    width100%;
    height3px;
    background-color#DDD;
    overflow: hidden;
}
.pro_bar .bar_cess{
    position: absolute;
    left0;
    height100%;
    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{
    backgroundlinear-gradient(to right top, #0089f2 50%, #DDD 50%);
    background-size100% 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 canBut maybe you shouldn'tThis is an interesting consequence of a bunch of hacks held together with duct tapeIt uses z-index hacksgradient hacks and tricks with calc and viewport units.</p>
    <p>Having said thathacks are not always badI love hacks and many of us have made quite a good living selling floats and clearfixes.</p>
    <p>The techniques used here are well supportedif not conventionalIf you can read the CSSunderstand how it worksand how to change itand you think this works better for you than JavaScriptfeel free to implement itJust 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 metusDonec ullamcorper nulla non metus auctor fringillaNulla vitae elit liberoa pharetra augueDonec sed odio duiDonec id elit non mi porta gravida at eget metusPraesent commodo cursus magnavel scelerisque nisl consectetur et.</p>
    <p>Cras mattis consectetur purus sit amet fermentumDonec id elit non mi porta gravida at eget metusInteger posuere erat a ante venenatis dapibus posuere velit aliquetEtiam porta sem malesuada magna mollis euismodCum sociis natoque penatibus et magnis dis parturient montesnascetur ridiculus musDonec ullamcorper nulla non metus auctor fringilla.</p>
    <p>Aenean eu leo quamPellentesque ornare sem lacinia quam venenatis vestibulumFusce dapibustellus ac cursus commodotortor mauris condimentum nibhut fermentum massa justo sit amet risusDonec ullamcorper nulla non metus auctor fringillaSed posuere consectetur est at lobortisCras justo odiodapibus ac facilisis inegestas eget quamAenean lacinia bibendum nulla sed consecteturNulla vitae elit liberoa pharetra augue.</p>
    <p>Donec sed odio duiVivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctorCras mattis consectetur purus sit amet fermentumMaecenas 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 auctorSed posuere consectetur est at lobortisSed posuere consectetur est at lobortisMaecenas faucibus mollis interdumNullam id dolor id nibh ultricies vehicula ut id elitAenean lacinia bibendum nulla sed consecteturNullam quis risus eget urna mollis ornare vel eu leo.</p>
    <h3>Bibendum Aenean Dapibus Tristique</h3>
    <p>Cras mattis consectetur purus sit amet fermentumDonec id elit non mi porta gravida at eget metusInteger posuere erat a ante venenatis dapibus posuere velit aliquetEtiam porta sem malesuada magna mollis euismodCum sociis natoque penatibus et magnis dis parturient montesnascetur ridiculus musDonec ullamcorper nulla non metus auctor fringilla.</p>
    <p>Aenean eu leo quamPellentesque ornare sem lacinia quam venenatis vestibulumFusce dapibustellus ac cursus commodotortor mauris condimentum nibhut fermentum massa justo sit amet risusDonec ullamcorper nulla non metus auctor fringillaSed posuere consectetur est at lobortisCras justo odiodapibus ac facilisis inegestas eget quamAenean lacinia bibendum nulla sed consecteturNulla vitae elit liberoa pharetra augue.</p>
    <p>Donec sed odio duiVivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctorCras mattis consectetur purus sit amet fermentumMaecenas 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 auctorSed posuere consectetur est at lobortisSed posuere consectetur est at lobortisMaecenas faucibus mollis interdumNullam id dolor id nibh ultricies vehicula ut id elitAenean lacinia bibendum nulla sed consecteturNullam 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 fermentumDonec id elit non mi porta gravida at eget metusDonec id elit non mi porta gravida at eget metusAenean lacinia bibendum nulla sed consectetur.</p>
</main>
html,body { margin:0;padding0;}
@supports (height: 100vh) {
    body{
        backgroundlinear-gradient(to right top, #0089f2 50%, #DDD 50%);
        background-size100% calc(100% - 122vh + 129px); /** 这里“%”换成“vw”也可 */
        background-repeat: no-repeat;
    }
    body::before{
        content:'';
        position: fixed;
        top2px;
        bottom0;
        width100%;
        z-index: -1;
        background#fff/** 这个才是真正页面的背景!不管是颜色还是图片都在这里替换! */
    }
}

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

  1. 第二种方法:经过上面的折腾,我想我们已经掌握了“密码”:定位+覆盖。如果你不想直接在body中操作,你完全可以再定义一个元素,让它跟随body定位不就可以了?因为body是不会动的,相对body定位就可以达到fixed效果!
<!-- 在body下添加一个空标签 -->
<div class="scroll_fixed"></div>

body {
    position: relative;
}
.scroll_fixed {
    position: absolute;
    top0;
    right0;
    left0;
    bottom0;
    backgroundlinear-gradient(to right top, #0089f2 50%, transparent 50%) no-repeat;
    background-sizecalc(100% + 5pxcalc(100% - 100vh + 17px);
    z-index1;
    pointer-events: none;
    mix-blend-mode: darken;
}
.scroll_fixed::after {
    content'';
    position: fixed;
    top2px;
    bottom0;
    right0;
    left0;
    background#fff;
    z-index1;
}

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

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

这个小知识是笔者跟张鑫旭老师的 这篇文章(https://www.zhangxinxu.com/wordpress/2020/11/html-anonymous-text-color-change/) 学到的,嘿嘿,还挺好用



最后


  • 欢迎加我微信(winty230),拉你进技术群,长期交流学习...

  • 欢迎关注「前端Q」,认真学前端,做个专业的技术人...

点个在看支持我吧
浏览 28
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报