这个进度条是网上一个实例,原实例使用jQuery实现的查看,最近在用vue-cli,所以就用vue实现该组件查看。
这个进度条有有意思的地方是:用户的一系列操作都和导航息息相关。一般来说,普通的导航,只是通过点击显示不同的内容或滚动到其他指定位置,至于用户在页面上的其他操作(滚动等)是和导航很少有关系,但是这个导航不一样,用户在页面上的一切操作都和导航相关。
废话不多说了,我们来分析一下这个导航是如何实现的以及这里面有哪些玄机?
样式我们就不多说了,这个基本都会。
首先我们分析一下页面的动画效果:
①当页面滚动超过导航所在位置时,导航固定到顶部,否则导航按顺序排列在页面中。
②当页面滚动到每个指定的文本段落时,对应的导航节点处于激活状态。
③随着页面在指定文本节点之间滚动时,导航条的长度是跟着增加的。
④导航节点悬浮时的节点圆圈颜色和导航条的颜色一致,并且文字会上移。
⑤当页面宽度小于700px,导航条消失。
现在我们总结上面的结果,并设置合理的流程。我们不难发现上面的动画和页面的滚动事件有关系,所以我们需要在页面dom渲染完毕时,监听页面滚动。对于上面效果,我们可以用以下实现:
①获取导航距离页面顶部的距离,通过滚动事件及时检测页面滚动距离是否大于等导航距离页面顶部的距离,如果是则固定导航,否则取消固定。
②获取每个文本段落到页面顶部距离,当滚动距离超过节点距离就激活对应的导航节点,否则取消激活状态
③当滚动距离小于第一个文本段落到顶部的距离时,导航激活条的长度为0;当滚动距离小于最后一个文本段落到顶部的距离时,导航激活条的长度为100%;否则计算页面滚动距离和对应的导航激活条的长度
④通过检测页面已经滚动超过的文本段落,可以给对应的导航节点圆圈添加激活状态;文字上移就是transform: translateY();
⑤通过适配就可以完成
注意,这里的难点:
①导航固定时,要给一个占位元素代替导航的高度否则页面会闪现,并对页面滚动的计算也不利。
②计算导航激活条的长度:
/**
* 计算活动线的长度
* 如果当前页面滚动的距离没有达到计算的起始距离,活动线的宽度重置为0,且不进行任何操作
* 如果当前距离超出页面的结束距离,则活动线的宽度等于最大宽度
* 否则根据页面滚动的距离计算活动线的长度
* 计算规则:
* 首先你要明白页面的滚动的距离所占百分比和活动线每段的百分比不相同:例如:
* 活动线长100,有6个文本节点和导航节点,导航节点平分为5段,每段长度为16.67%;
* 但是文本的长度并不平均分配,假设第一节点和第二节点之间的总长度也为100,但是5段距离为12 18 26 24 20;
* 这时候页面滚动的距离和超出第一节点25,这时候要计算活动线的长度?
* 首先我们先看文本节点的间距中的1个长度分别表示多少活动线的长度
* 第一段:1 / 12 * 16.67%;
* 第二段:1 / 18 * 16.67%;
* …
* 所以文本的每一段长度代表的活动线的长度并不一致。
* ①已知长度与第一段的距离相比,是否大于此段长度
* ②如果是则现在导航长度加上16.67%,已知长度减去此段文本长度
* ③否则将(已知长度/此段文本长度)*导航平均长度
* ④重复上面几个步骤,知道已知长度小于文本长度
* 所以根据上面的规则:
* let width = 0;
* 25 > 12; width += 16.67%; 25 - 12 = 13;
* 13 < 18; width += 13 / 18 * 16.67%;
*/
最后给出js源码:

<script>
export default {name: 'Navs',data() {return {titles: ['Gouda', 'Brie', 'Manchego', 'Camembert', 'Edam', 'Gorgonzola'],activeTitle: null,activeLine: {startLength: 0,endLength: 0,maxLength: 0,maxWidth: '',activeWidth: '',averageWidth: ''},progressBar: {fixed: false,offsetTop: 0},nodes: [],docScrollTop: 0}},created() {this.init();},mounted() {this.$nextTick(() => {this.initDom();});},methods: {init() {this.initNodePosition();},initDom() {this.getNodePosition();this.setDocumentScroll();this.initActiveLineData();console.log('progressBar top : ', this.progressBar.offsetTop);},initNodePosition() {/*** 初始化节点的信息*/let _titles = this.titles;let len = _titles.length;let item = {};for (let i = 0; i < len; i++) {item.name = _titles[i];item.offsetTop = 0;item.active = false;this.nodes.push(item);item = {};}console.log('nodes: ', this.nodes);},getNodePosition() {/*** 获取节点距离顶部的距离*/let doc = document;this.nodes.map((item, index, arr) => {item.offsetTop = doc.querySelector('#' + item.name).offsetTop;});},setDocumentScroll() {/*** 监听页面的滚动事件,并调用和页面滚动相关的函数* 使用函数节流防止滚动事件触发的频繁*/console.log('setDocumentScroll');let _self = this;let timer = null;// 获取页面导航以及导航节点距离页面顶部的距离this.progressBar.offsetTop = document.querySelector('.progress-bar').offsetTop;document.addEventListener('scroll', event => {if (timer) {return;}timer = setTimeout(() => {if (timer) {clearTimeout(timer);timer = null;}// 获取页面滚动的距离_self.docScrollTop = document.documentElement.scrollTop || document.body.scrollTop;_self.fixedNavToggle();_self.setActiveTitle();_self.computedActiveLineHeight();console.log('document scroll: ', _self.docScrollTop);}, 80);}, true);},initActiveLineData() {/*** 初始化导航活动线的数据* 计算第一个节点和最后一个节点的距离,这是计算的有效距离* 设置活动线的最大长度*/let _self = this;let _activeLine = _self.activeLine;let _nodes = _self.nodes;_activeLine.startLength = parseFloat(_nodes[0].offsetTop);_activeLine.endLength = parseFloat(_nodes[_nodes.length - 1].offsetTop);_activeLine.maxLength = _activeLine.endLength - _activeLine.startLength;_activeLine.maxWidth = '83.44%';_activeLine.activeWidth = 0;_activeLine.averageWidth = parseFloat(_activeLine.maxWidth) / (_nodes.length - 1) + '%';console.log('active line init data: ', this.activeLine);},fixedNavToggle() {/*** 导航固定到顶部或者取消固定* 将导航距离顶部的位置和当前页面滚动的距离相比较* 如果大于等于,固定导航;* 否则取消固定*/let _progressBar = this.progressBar;let scrollTop = this.docScrollTop;if (scrollTop >= _progressBar.offsetTop) {_progressBar.fixed = true;} else {_progressBar.fixed = false;}},setActiveTitle() {/*** 设置活动标题* 当页面的滚动距离大于等于页面节点的位置时,修改活动标题*/let scrollTop = this.docScrollTop;// 筛选出距离顶部距离小于等于页面滑动距离的节点let filt = this.nodes.filter((item, index, arr) => {if (item.offsetTop > scrollTop) {// 将滑动未超过的节点的active设置为falseitem.active = false;} else {// 将已经滑动超过的节点的active设置为trueitem.active = true;return item;}}, null);// 如果筛选的结果不为空,则最后一个元素就是符合的;if (filt.length > 0) {this.activeTitle = filt[filt.length - 1].name;} else {this.activeTitle = null;}console.log('set active title: ', this.activeTitle);},computedActiveLineHeight() {/*** 计算活动线的长度* 如果当前页面滚动的距离没有达到计算的起始距离,活动线的宽度重置为0,且不进行任何操作* 如果当前距离超出页面的结束距离,则活动线的宽度等于最大宽度* 否则根据页面滚动的距离计算活动线的长度* 计算规则:* 首先你要明白页面的滚动的距离所占百分比和活动线每段的百分比不相同:例如:* 活动线长100,有6个文本节点和导航节点,导航节点平分为5段,每段长度为16.67%;* 但是文本的长度并不平均分配,假设第一节点和第二节点之间的总长度也为100,但是5段距离为12 18 26 24 20;* 这时候页面滚动的距离和超出第一节点25,这时候要计算活动线的长度?* 首先我们先看文本节点的间距中的1个长度分别表示多少活动线的长度* 第一段:1 / 12 * 16.67%;* 第二段:1 / 18 * 16.67%;* ......* 所以文本的每一段长度代表的活动线的长度并不一致。* ①已知长度与第一段的距离相比,是否大于此段长度* ②如果是则现在导航长度加上16.67%,已知长度减去此段文本长度* ③否则将(已知长度/此段文本长度)*导航平均长度* ④重复上面几个步骤,知道已知长度小于文本长度* 所以根据上面的规则:* let width = 0;* 25 > 12; width += 16.67%; 25 - 12 = 13;* 13 < 18; width += 13 / 18 * 16.67%;*/let scrollTop = this.docScrollTop;let _activeLine = this.activeLine;if (scrollTop < _activeLine.startLength) {_activeLine.activeWidth = 0;return;} else if (scrollTop >= _activeLine.endLength) {_activeLine.activeWidth = _activeLine.maxWidth;} else {let _nodes = this.nodes;let _index = this.titles.indexOf(this.activeTitle);let distance = scrollTop - _activeLine.startLength;let currentWidth = 0;let _averageWidth = parseFloat(_activeLine.averageWidth);_nodes.map((item, index, arr) => {if (index > _index) {return;}let dis = arr[index + 1].offsetTop - item.offsetTop;if (distance >= dis) {currentWidth += _averageWidth;distance -= dis;} else {currentWidth += distance / dis * _averageWidth;}});_activeLine.activeWidth = currentWidth.toFixed(2) + '%';}console.log('active line active width: ', _activeLine.activeWidth);},slideToNode(event) {/*** 点击导航滑动到对应的节点*/let e = event || window.event;let el = e.target || e.srcElement;if (!el || !el.getAttribute('data-title')) {return;}let title = el.getAttribute('data-title');if (this.titles.indexOf(title) < 0) {return;}this.activeTitle = title;this.scrollToNodePosition();console.log('click nav item: ', title);},scrollToNodePosition() {/*** 页面滑动到对应的节点* 不论距离有多远,最多500ms完成动画* 每次滚动的执行的间隔50ms*/// 保证requestAnimationFrame的兼容性window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;// 页面滚动使用的时间const ALLTIME = 400;// 定时器调用的间隔const DURATION = 20;let doc = document.body;let _self = this;let title = _self.activeTitle;// 获取当前点击的导航所对应的节点let item = _self.nodes[_self.titles.indexOf(title)];// 计算执行定时器的次数let count = Math.floor(ALLTIME / DURATION);// 计算每次页面滚动的平均距离const speed = Math.floor((item.offsetTop - _self.docScrollTop) / count);let animateId = null;let timer = null;function step() {doc.scrollTop += speed;--count;// 修正滚动的结果if (count < 1) {doc.scrollTop = item.offsetTop;}console.log('document scroll to special positon. scrollTop: ', doc.scrollTop, 'count: ', count);}timer = setInterval(() => {if (count < 2 && timer) {clearInterval(timer);timer = null;cancelAnimationFrame(animateId);animateId = null;}animateId = requestAnimationFrame(step);}, DURATION);}}
}
</script>

VUE“粘性”阅读进度条相关推荐

  1. vue 视频 时间进度条组件

    vue 视频 时间进度条组件 有些视频是以视频流的形式进行渲染的,没有视频滚动条,所以就写了24h的时间组件 实现思路: 1,24h的时间刻度线总宽度为12960px 2,点击24h线的某一点,获取这 ...

  2. vue 视频 时间进度条组件-使用npm组件

    vue 视频 时间进度条组件 有些视频是以视频流的形式进行渲染的,没有视频滚动条,所以就写了24h的时间组件 组件已上传到npm上,npm i as-time-line下载安装即可,最少需要1.2.0 ...

  3. vue 圆形百分比进度条_快速构建一个圆形的进度条

    在一些特别生的网站上,用户需要一个可视化的是示,以表明网站资源仍然在加载.从Spinner到Skeleton屏幕有不同的方法来解决这类的用户体验效果. 如果我们使用的是开箱即用的解决方案,它为我们提供 ...

  4. Vue 圆圈形进度条

    圆圈形进度条在开发中经常遇到,这里把他封装成一个组件,实现方式为使用svg画图. 下面的代码中涉及到了svg的viewBox属性,按照张鑫旭大神的说法:SVG就像是我们的显示器屏幕,viewBox就是 ...

  5. Vue使用NProgress进度条 zindex

    1.安装 npm i -S nprogress 2.在router.js中使用 import Vue from 'vue' import Router from 'vue-router' import ...

  6. Vue + Element 实现进度条 Progress

    Vue + Element 进度条 Progress 前言:由于在公司可视化数字大屏项目中用到了,所以在博客里记录一下!如果能帮到你,那么点个赞吧,哈哈哈 话不多说,先直接上效果图,有需要的大家可借鉴 ...

  7. vue实现斑马线进度条

    看下效果:  用到的图片: 先保存进度条的两种背景,第一种是底色黑色,第二种是用于展示进度的米黄色背景         整体思想: 用一个小单元作为背景图片,然后在style里面设置进度条的长度 这样 ...

  8. Vue实现渐变色进度条

    今天在封装一个个类似于下面这样的进度条组件 功能要求 进度条的总格子数可以自定义 当前件数的占比和当前蓝色格子数对应 格子的蓝色渐变要符合UI设计 这个渐变色的处理浪费了好一会功夫,下面看一下我的实现 ...

  9. VUE项目--nprogress进度条

    安装 npm install --save nprogress 引入 在src/api/requests.js文件中引入 // 引入进度条 import nprogress from 'nprogre ...

最新文章

  1. 函数sscanf小结
  2. 个人“乱七八糟”笔记和摘要之二
  3. 运筹说 第36期 | 算法介绍之运输问题
  4. Windows Server AppFabric安装教程
  5. 南京大学计算机考研信息汇总
  6. Bootstrap如何设置table样式
  7. 模拟CMOS集成电路设计基础 第一章 第二章开头
  8. python实现税后工资计算器_[宜配屋]听图阁
  9. 错误记录-java idea执行k8s https api报错 should not be presented in certificate_request
  10. SSH连接服务器Secure CRT技巧[Secure CRT连接ubuntu显示密钥交换失败][Ubuntu无法使用root用户登陆的解决办法]
  11. Cubic Curve
  12. 写在19年初的后端社招面试经历(两年经验): 蚂蚁 头条 PingCAP
  13. 算法分析:大O符号/大Ω符号/大Θ符号/小o符号/小w符号
  14. 安装瑞星全功能安全软件2009
  15. Appium报错:java.lang.SecurityException: uid 2000 does not have android.permission.WRITE_SECURE_SETTING
  16. 睡梦音乐声悠悠...
  17. 洛谷P1427 小鱼的数字游戏c语言
  18. 2017北京大学可视化发展前沿研究生暑期学校总结
  19. 计算机网络-网络层篇-BGP协议
  20. ai怎么平均排列,ai怎样可以按设定的距离进行平均分布排列

热门文章

  1. flutter 局部状态和全局状态区别_Android 开发者遇到 5G、AI,写给 Android 开发者的 Flutter 指南
  2. Java斗地主界面实现_通过Java实现斗地主
  3. 组策略 gpedit.msc 及修复“无法为文件 appv.admx (*.admx)找到适当的资源文件(错误=2)”报错
  4. Java爬取王者荣耀英雄壁纸
  5. RabbitMQ高级之如何保证消息可靠性?
  6. 影响云服务器租用价格的因素有哪些
  7. LocalBroadcastManager已被废弃
  8. 语音视频社交中回声消除技术是如何实现的
  9. java基础(1~100以内的质数)
  10. f_sync有大用但不可以滥用