这是上周工作中写到的一个功能,大概的效果就是页面中有几处数字,统计公司的一些业务信息,需要在第一次出现的时候,做一个从0开始增长,大概2秒自动增长到真实数值,并停止增长的效果。这个问题的重点在于解决如何保证不同大小的数字都在2秒左右的时间自动增长完成,以及还有考虑延迟初始化的处理。后面这一点是为了保证,只有当数字第一次进入浏览器可视区域的时候,才会看到效果,因为这些数字有可能不在首屏的内容内,必须保证当用户滚动操作将数字显示出来的那一刻才能看到效果。本文分享我自己的实现思路,要是您有更好的方法,欢迎指点与修正 : )

demo地址:

http://liuyunzhuge.github.io/blog/numerGrow/dist/html/demo.html

代码地址:

https://github.com/liuyunzhuge/blog/tree/master/numerGrow

代码运行说明见git项目内的readme.md。

本文内容有补充修改,详见本文最后的补充说明!

1. 实现思路

先来看看如何保证大小不同的数字都在规定的时间内都能增长完成。

这个问题可以类比到曾经物理课的一些知识,就是速度路程与时间的关系。在这个问题中,最终的数字大小代表路程,单位时间内每个数字增长的值代表速度。由于路程不同,要走的时间相同,所以每个数字的增长速度也就不会相同。要解决这个问题,只要求出速度即可,因为时间和路程都是已知的。

但是在物理里面,速度的基本单位都是以秒或者小时为单位的,比如3m/s,30km/h,在程序里面显然是不能用秒或者小时的,因为这些单位太大了,而且要解决我们的问题,显然要用到计时器,计时器的单位是毫秒,所以在计算速度的时候,要以ms为单位。比如要显示的数值如果是100,规定的时间为2s,也就是2000ms,那么每ms要增加的数值就是100/2000,根据这个设想,可以得出如下的程序实现:

function NumberGrow(element, options) {
options = options || {}; var $this = $(element),
time = options.time || $this.data('time'),//总时间
num = options.num || $this.data('value'),//要显示的真实数值
step = num / (time * 1000),//每1ms增加的数值
start = 0,//计数器
interval,//定时器
old = 0; //step为每1ms增加的数值 interval = setInterval(function () {
start = start + step;
if (start >= num) {
clearInterval(interval);
interval = undefined;
start = num;
} var t = Math.floor(start); //t未发生改变的话就直接返回
//避免调用text函数,提高DOM性能
if (t == old) {
return;
} old = t;
$this.text(old);
}, 1);
}

这个实现虽然从理论上是可行的,但是实际运行的时候,会发现这个增长的效果会远远超过规定的时间,原因可能在于setInterval里面的函数执行也是需要耗费时间的,而且不一定能在定时器的间隔内就执行完,所以这些额外执行的时间跟每次执行的间隔累计起来就会超过规定的时间。要解决这个问题,我想到了一篇文章里面提到的关于帧率的问题:

http://www.ruanyifeng.com/blog/2015/09/web-page-performance-in-depth.html

如果网页动画能够做到每秒60帧,就会跟显示器同步刷新,达到最佳的视觉效果。这意味着,一秒之内进行60次重新渲染,每次重新渲染的时间不能超过16.66毫秒。

我们可以把setInterval的每次执行都看成是一帧,然后把setInterval的执行间隔改成16,只要它的回调函数执行时间不超过16ms,那么这个计时器累计运行的时间就只跟间隔时间有关系,而跟回调函数的执行时间没有关系,因为回调函数是在回调间隔时间内执行完的!这就是解决前面问题的关键:

1)把计时器的间隔改成16ms

2)把速度从每ms增加的数值改成每16ms增加的数值

最终正确的实现如下(对应的代码是https://github.com/liuyunzhuge/blog/blob/master/numerGrow/src/js/mod/numberGrow.js):

function NumberGrow(element, options) {
options = options || {}; var $this = $(element),
time = options.time || $this.data('time'),//总时间
num = options.num || $this.data('value'),//要显示的真实数值
step = num * 16 / (time * 1000),//每16ms增加的数值
start = 0,//计数器
interval,//定时器
old = 0; //每帧不能超过16ms,所以理想的interval间隔为16ms
//step为每16ms增加的数值 interval = setInterval(function () {
start = start + step;
if (start >= num) {
clearInterval(interval);
interval = undefined;
start = num;
} var t = Math.floor(start); //t未发生改变的话就直接返回
//避免调用text函数,提高DOM性能
if (t == old) {
return;
} old = t;
$this.text(old);
}, 16);
}

基于这个实现去测试,会发现最终的运行结果与规定的时间只有几十ms的差别,基本上已经达到我们的要求了。这几十毫秒的差距,我觉得来自于浏览器对于setInterval的管理,如果想要十分精准地在规定时间内完成这个效果,我还没有想到好的方法,希望有这个思路的朋友愿意分享出来。

事实上,定时器的间隔不用16,用8, 9, 10, 18, 20, 24也都可以,效果跟16差不多,因为定时器的回调函数执行在浏览器正常的情况下肯定不需要8ms,里面啥都没干呢。。。用8, 9, 10, 18, 20, 24还是16的区别在于数字变化的速度看起来不一样而已,间隔越小变化越快,间隔越大变化越慢,所以给人的视觉体验不同。用16是因为它比较接近于16.66ms这个数值。

以上部分是关于如何保证大小不同的数字都在规定的时间内都能增长完成的说明,下面来看看如何做滚动时的懒加载。

我的思路考虑地相对简单,借助滚动事件,监听各个元素是否完全进入浏览器的可视区域,只有当它完全在浏览器可视区域的时候才初始化,并且只执行一次,当某个类型的组件全部都初始化以后,还会做一个destroy的处理,以便提供页面性能。

这部分的实现对应的代码是:https://github.com/liuyunzhuge/blog/blob/master/numerGrow/src/js/mod/scrollLazyInit.js

其中有几个关键点可以再在博客里说明一下:

1)options

scrollLazyInit提供了两个option,一个ns,表示命名空间,用来注册scroll事件,因为这个组件可能不只有numberGrow才会用到,页面当中其它耗时的组件也可以利用这个组件来做简单的懒初始,有了这个个ns就可以管理不同的组件了;还有一个delay就是滚动回调节流时的间隔,一般不会用到。

在使用scrollLazyInit的时候,必须先实例化才能使用,实例化的时候可以传递ns和delay参数:

2)add方法

每个srollLazy的实例都有两个实例方法,其中一个就是add方法,用来将要延迟初始化的功能添加到scrollLazy来管理:

add方法有两个参数,第一个是要延迟初始化的dom元素,要用它来判断是否完全进入可视区域,第二个是当元素完全进入可视区域时回调,在这个回调里面来做组件初始化,就如上图所示。

3)start方法

每个scrollLazy实例的另外一个实例方法就是start,这个其实就是添加滚动监听而已。在把所有的延迟初始化的组件都add完之后,再调用这个方法即可:

由于它的实现并不复杂,而且也不属于本文重点,原本这一部分功能是在numberGrow里面的,后来考虑到职责分离,才单独写成了另外一个组件,代码只有60行,相信您的能力,肯定能直接看明白源码。

另外还值得一说的是,这个scrollLazy还有优化的地方,就是在判断初始化的时机这一块,因为目前是判断元素完全进入可视区域的时候才初始化,这对于一些高度很小的元素来说,没有问题,但是对于高度可能超过可视区域的元素来说,肯定是不行的,所以在使用的时候要注意这个点。

2. 使用说明

这个功能在使用的时候,可以直接通过data属性来注册,因为这种效果型的功能,基本上都没有业务逻辑,不必要放到跟业务逻辑相关的js里面去,所以只要在html上注册即可:

第一个data-ride=”numberGrow”不能省,因为在numberGrow.js里面,是通过这个属性来找到需要自动注册的元素的。后面的value和time分别表示要增长的真实数值和增长的有效时间。

3. 本文小结

本文介绍了自己关于一个简单的网页效果的实现思路,因为觉得那个类比物理中的速度时间路程的点比较有趣,所以把它分享出来,希望对您有所参考价值,谢谢阅读:)


补充于2016-06-02

本文中提供的实现有瑕疵,虽然在demo中看到的运行结果跟预期一致,但是存在问题,这个问题要感谢 上位者的怜悯在评论中帮我指正出来,并提出了一个更佳的实现方式。这个问题是:本文的实现思路,会受到代码执行时间的影响,这个代码执行时间包括定时器回调函数的执行时间以及页面中其它js代码的执行时间,代码执行时间越长,会导致最终的效果持续时间越偏离规定的运行时间。

虽然从思路上来说,本文的想法很好,类比了物理中速度与时间以及路程的关系,但是这毕竟是代码执行环境,无法保证“匀速”增长。更好的实现方式是,上位者的怜悯在评论区中提出的按照比例来计算当前数值的方法,计算公式是:当前值 = 总数值 * (已经运行的时间 / 总时间)。

我把他的代码重新整理了一下,并封装为了numberGrowBetter.js,放在js/mod文件夹下,源码请查看:

https://github.com/liuyunzhuge/blog/blob/master/numerGrow/src/js/mod/numberGrowBetter.js

这个实现对应的demo:

http://liuyunzhuge.github.io/blog/numerGrow/dist/html/demo2.html

评论区,有关于这个问题跟实现的交流,有兴趣的可以查看。

数字限时增长效果实现:numberGrow.js的更多相关文章

  1. Vue.js大屏数字滚动翻转效果

    ================================ 大屏数字滚动翻转效果来源于最近工作中element后台管理页面一张大屏的UI图,该UI图上有一个模块需要有数字往上翻动的效果,以下是最 ...

  2. CountUp.js 数字跳转效果小插件

    CountUp.js  实现数字跳转效果的小插件 //调用方法 const easingFn = function (t, b, c, d) { var ts = (t /= d) * t; var ...

  3. 网页3D效果库Three.js初窥

    网页3D效果库Three.js初窥 背景 一直想研究下web页面的3D效果,最后选择了一个比较的成熟的框架Three.js下手 ThreeJs官网 ThreeJs-github; 接下来我会陆续翻译 ...

  4. 横竖两个数字塔的效果BAT批处理怎么写?

    横竖两个数字塔的效果BAT批处理怎么写?@echo offfor /l %%a in (0,1,1) do (        for /l %%i in (0,1,9) do (        for ...

  5. jq数字翻页效果,随机数字显示,实现上下翻动效果

    最近在做一个项目,需要实时展示一串数字,要有类似于日历翻页的效果,在网上找寻了一番,发现dataStatistics这个插件http://www.jq22.com/jquery-info8141能实现 ...

  6. 基于jquery fly插件实现加入购物车抛物线动画效果,jquery.fly.js

    在购物网站中,加入购物车的功能是必须的功能,有的网站在用户点击加入购物车按钮时,就会出现该商品从点击出以抛物线的动画相似加入购物车,这个功能看起来非常炫,对用户体验也有一定的提高.下面介绍基于jque ...

  7. 购物车数字加减按钮HTML+CSS+JS(有需要嫌麻烦的小伙伴拿走不谢)

    之前在写详情页的时候,如下图 因为自己嫌麻烦,就去看其他网站是怎么写的,想直接拿来用,后来看来看去觉得写得很麻烦,于是最后还是决定自己写,附上HTML+CSS+JS代码,一条龙一站式贴心服务2333 ...

  8. Sql Server 主键由字母数字组成并按照数字自动增长

    在SQL SERVER 中如果我们想要使主键按照一定规则自动增长我们可以这样做: 这里我们新建一张研究表,里面有研究ID,研究人员姓名和研究医院. 我们使SicentificId 设为主键 并且从1开 ...

  9. 图片切换效果,纯js

    公司有个技术很牛x的“老腊肉”,我向他请教,他给了我个网址,上面蛮多效果的,于是乎~我决定照着效果看能不能自己敲出来,我要变大神X3,重要的事说3遍. 它完成的效果是这样的: 好吧,一步一步来,先把框 ...

随机推荐

  1. ASP.NET Core 数据保护(Data Protection 集群场景)【下】

    前言 接[中篇],在有一些场景下,我们需要对 ASP.NET Core 的加密方法进行扩展,来适应我们的需求,这个时候就需要使用到了一些 Core 提供的高级的功能. 本文还列举了在集群场景下,有时候 ...

  2. [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)

    文章来自:http://www.hoohack.me/2016/02/15/understanding-phps-internal-array-implementation-ch 原文:https:/ ...

  3. Windows Azure Storage (20) 使用Azure File实现共享文件夹

    <Windows Azure Platform 系列文章目录> Update 2016-4-14.在Azure VM配置FTP和IIS,请参考: http://blogs.iis.net/ ...

  4. 《Entity Framework 6 Recipes》中文翻译系列 (15) -----第三章 查询之与列表值比较和过滤关联实体

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-8与列表值比较 问题 你想查询一个实体,条件是给定的列表中包含指定属性的值. 解 ...

  5. Oozie 快速入门

    设想一下,当你的系统引入了spark或者hadoop以后,基于Spark和Hadoop已经做了一些任务,比如一连串的Map Reduce任务,但是他们之间彼此右前后依赖的顺序,因此你必须要等一个任务执 ...

  6. Redux

    redux是Flux的一种实现方式,但还是和Flux有些不同. React控制视图层,要想做一个完整的数据流,必须要用react-redux. 官方demo,自己收集了一下: demo1http:// ...

  7. VS-项目发布失败的解决方案1

    报错信息 错误 1 未能将文件 Script\easyui\themes\gray\images\Thumbs.db 复制到 obj\Release\Package\PackageTmp\Script ...

  8. EF-DbUpdateException--实体类和数据库列不对应的解决方案

    错误信息 1.VS实体类里面的字段 2数据库里面的字段 猜测是因为字段数不匹配导致的 3删除多余字段 5.结果 错误信息贴上: -------------------------Log_Header- ...

  9. 【WP 8.1开发】如何动态生成Gif动画

    相信如何为gif文件编码,很多朋友都会,而难点在于怎么让GIF文件中的帧动起来,也就是创建gif动画. Gif文件编码方法 先简单介绍一下编码的方法. 1.调用BitmapEncoder.Create ...

  10. [转]自己写PHP扩展之创建一个类

    原文:http://www.imsiren.com/archives/572 比如我们要创建一个类..PHP代码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ...