效果

当 scroll 到那些号码的时候, 号码从 0 开始跳动, 一直到最终的值.

实现思路

1. 一开始把号码 set to 0

2. 使用 IntersectionObserver 监听号码出现

3. 出现后开始累加, 一直到最终的 value. (注意, 虽然每个号码是不同的, 但是会在同一秒低到终点. 所以每个号码的累加速度是不一样的, 号码越大跑的就越快)

搭环境

HTML

<body>
<header>Lorem ipsum dolor sit.</header>
<main>
<p>
<span class="number">1280</span>
<span>px</span>
</p>
<p>
<span class="number">1366</span>
<span>px</span>
</p>
<p>
<span class="number">1560</span>
<span>px</span>
</p>
<p>
<span class="number">1920</span>
<span>px</span>
</p>
</main>
</body>

CSS Style

* {
padding: 0;
margin: 0;
box-sizing: border-box;
} header {
height: 80vh;
width: 100%;
background-color: pink;
display: grid;
place-content: center;
font-size: 4rem;
text-align: center;
} main {
height: 100vh;
display: grid;
place-content: center; p {
font-size: 4rem;
}
}

效果

还没有加入 JS 所以完全没有效果.

JavaScript Step by Step

创建最终的 setup 函数

export function setupStatisticsCounter(): void {}
setupStatisticsCounter();

definition & startup

export function setupStatisticsCounter(): void {
const duration = 2500;
const interval = 50;
const counters = Array.from(
document.querySelectorAll<HTMLElement>(".number")
);
}

1. 累加一共耗时 2.5秒, 每 50ms 跳动一次. 这里是控制体验.

2. 把需要的 setup counter elements 找出来

set number to zero

for (const counter of counters) {
counter.dataset.endNumber = counter.textContent!;
counter.textContent = "0";
}

把当前的号码 set 成 0. 需要把号码保存起来哦. 不然等下就不知道要累加到多少了.

Setup IntersectionObserver

const io = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
const element = entry.target as HTMLElement;
io.unobserve(element);
startAccumulate(element, +element.dataset.endNumber!);
}
}
});
counters.forEach((counter) => io.observe(counter)); function startAccumulate(element: HTMLElement, endValue: number): void {
console.log("do accumulate", [duration, interval, element, endValue]);
}

当 counter intersecting 的时候开始执行累加. 累加函数只有接口还没有具体实现.

每一个 counter 都需要 observe 哦. 而且一旦开始累加就可以 unobserve 了.

累加函数

function startAccumulate(element: HTMLElement, endValue: number): void {
const increment = endValue / (duration / interval);
const intervalNumber = setInterval(() => {
let currentNumber = +element.textContent!;
if (currentNumber < endValue) {
element.textContent = Math.ceil(
(currentNumber += increment)
).toString();
} else {
element.textContent = endValue.toString();
clearInterval(intervalNumber);
}
}, interval);
}

一个 interval 不断累加, 直到达到最终值. 唯一要注意的是它的 increment.

通过 endValue / (duration / interval) 就可以计算出不同 counter 的 increment, 这样就可以确保不同号码的 counter 都会在同一时间结束.

因为每一个 counter 的 increment 是不相同的, 越大的 endValue increment 也越大.

Final code

export function setupStatisticsCounter(): void {
const duration = 2500;
const interval = 50;
const counters = Array.from(
document.querySelectorAll<HTMLElement>(".number")
); for (const counter of counters) {
counter.dataset.endNumber = counter.textContent!;
counter.textContent = "0";
} const io = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
const element = entry.target as HTMLElement;
io.unobserve(element);
startAccumulate(element, +element.dataset.endNumber!);
}
}
});
counters.forEach((counter) => io.observe(counter)); function startAccumulate(element: HTMLElement, endValue: number): void {
const increment = endValue / (duration / interval);
const intervalNumber = setInterval(() => {
let currentNumber = +element.textContent!;
if (currentNumber < endValue) {
element.textContent = Math.ceil(
(currentNumber += increment)
).toString();
} else {
element.textContent = endValue.toString();
clearInterval(intervalNumber);
}
}, interval);
}
}
setupStatisticsCounter();

效果

字体宽度的问题

仔细看会发现, 累加的时候字体的宽度是一直在变化的. 从 0 到 1920 宽度自然增加了.

这种跳动的体验有时候不太好.

解决思路

1. 一开始的时候先获取最终值时的 width, before set to zero

2. 然后把这个 width apply 到 span 上去. 这样 set to zero 后, width 依然是最终的 width.

3. 在累加完后移除 width

难点

由于字体加载需要时间, 所以不可以一开始就获取 width, 需要等待字体加载完后才是最终的 width. 可以使用 CSS Font Loading API

即便如此, 如果不是使用 等宽字体, 最终的 width 依然不一定满足累加时的 width 最大值. 所以还是可能会出现号码超出 width 的情况.

所以呢, 最完美的情况是, 使用等宽字体. 要不然不管怎么搞最终都不完美.

方案一, set 最终值的 width, 缺点累加时可能超出这个 width.

方案二, 用 ch unit 配上 length 做 width, 缺点最终值可能小于这个 width

下面是加了方案一的 JS 代码

export function setupStatisticsCounter(): void {
// note 隐患:
// 如果是不等宽字体, 在累加的时候号码可能会超出 width 哦, right way 是用等宽字体, 比如 Roboto // note 解忧:
// 需要等 fonts 加载好才能 set, 不然会跳一下.
document.fonts.ready.then(() => {
const duration = 2500;
const interval = 50;
const counters = Array.from(document.querySelectorAll<HTMLElement>('.number')); for (const counter of counters) {
counter.dataset.endNumber = counter.textContent!;
// note 解忧:
// 给 inline-block 是因为要 set width
if (window.getComputedStyle(counter).display === 'inline') {
counter.style.display = 'inline-block';
}
// 给 width 是为了不要累加的时候会跳
counter.style.width = `${counter.offsetWidth}px`;
counter.textContent = '0';
} const io = new IntersectionObserver(entries => {
for (const entry of entries) {
if (entry.isIntersecting) {
const element = entry.target as HTMLElement;
io.unobserve(element);
startAccumulate(element, +element.dataset.endNumber!);
}
}
});
counters.forEach(counter => io.observe(counter)); function startAccumulate(element: HTMLElement, endValue: number): void {
const increment = endValue / (duration / interval);
const intervalNumber = setInterval(() => {
let currentNumber = +element.textContent!;
if (currentNumber < endValue) {
element.textContent = Math.ceil((currentNumber += increment)).toString();
} else {
element.textContent = endValue.toString();
clearInterval(intervalNumber);
element.style.removeProperty('width');
element.style.removeProperty('display');
}
}, interval);
}
});
}

CSS & JS Effect – Statistics Counter的更多相关文章

  1. 使用html+css+js实现计算器

    使用html+css+js实现计算器,开启你的计算之旅吧 效果图: 代码如下,复制即可使用: <!DOCTYPE html><html lang="en"> ...

  2. CSS & JS 制作滚动幻灯片

    ==================纯CSS方式==================== <!DOCTYPE html> <html> <head> <met ...

  3. 【转】Maven Jetty 插件的问题(css/js等目录死锁)的解决

    Maven Jetty 插件的问题(css/js等目录死锁,不能自动刷新)的解决:   1. 打开下面的目录:C:\Users\用户名\.m2\repository\org\eclipse\jetty ...

  4. Css Js Loader For Zencart

    Css Js Loader 描述:这个插件很早就出来了,可能知道人非常少 这个插件的功能是整合所有的网站的CSS和JS内容到一个文件里边. 因为CSS和JS文件到了一个文件,加快了程序的运行 在配合其 ...

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

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

  6. vs合并压缩css,js插件——Bundler & Minifier

    之前做了一个大转盘的抽奖活动,因为比较火,部分用户反馈看不到页面的情况,我怀疑js加载请求过慢导致,所以今天针对之前的一个页面进行调试优化. 首先想到的是对页面的js和css进行压缩优化,百度了下vs ...

  7. nginx资源定向 css js路径问题

    今天玩玩项目,学学nginx发现还不错,速度还可以,但是CSS JS确无法使用,原来Iginx配置时需要对不同类型的文件配置规则,真是很郁闷,不过想想也还是很有道理.闲暇之际,把配置贴上来.#user ...

  8. IIS7的集成模式下如何让自定义的HttpModule不处理静态文件(.html .css .js .jpeg等)请求

    今天将开发好的ASP.NET站点部署到客户的服务器上后,发现了一个非常头疼的问题,那么就是IIS7的应用程序池是集成模式的话,ASP.NET项目中自定义的HttpModule会处理静态文件(.html ...

  9. 网站加载css/js/img等静态文件失败

    网站加载css/js/img等静态文件失败,报网站http服务器内部500错误.而服务器中静态文件存在且权限正常. 从浏览器中直接访问文件,出来乱码.这种问题原因在于iis中该网站mime配置报错,不 ...

  10. 【前端】Sublime text3 插件HTML/CSS/JS prettify 格式化代码

    1.首先安装插件 菜单的preference->packages control,然后输入install .. 回车,再输入HTML/CSS/JS prettify 再回车,重启后就可以了. 2 ...

随机推荐

  1. 如何删除Git中缓存的用户名和密码

    昨天在上传代码的时候提示输入用户名密码,结果输错了3次就没有提示框了,就一直报错(身份验证失败),没办法提交代. 在使用git的过程中,我们也会经常遇到以前保存在git的用户名密码忘记了,或者不用了. ...

  2. oeasy教您玩转vim - 54 - # 匹配替换

    ​ 查找细节 回忆上节课内容 我们学习了 替换 substitude 替换单行 :s/shiyanlou/oeasy 加上range :3,5s/shiyanlou/oeasy :%s/shiyanl ...

  3. C# WinForm遍历窗体控件的3种方法

    1.循环遍历 private void GetControls(Control fatherControl) { Control.ControlCollection sonControls = fat ...

  4. java web 开发框架编

    学习web 框架上开发需要的是安装  mysql 8.0  idea 2022 git  2.2.23  node 16以上 (新版本不好拉有些库了)jdk 最好是17以上 jdk8也是行的,反正不管 ...

  5. Diffutoon下载介绍:真人视频转动漫工具,轻松获得上千点赞

    最近在刷短视频的时候,偶尔能看到一些真人转动漫风的作品,看起来给人一种新鲜感,流量都还不错,简简单单跳个舞,就能获得上千个点赞~ 那么,这种视频是怎么制作的? 本期给大家介绍一款AI转绘工具Diffu ...

  6. Scratch全套Q版三国人物角色素材包免费下载

    全新Q版三国人物角色素材包,内含142张细腻可爱的Q版风格图片,涵盖三国名将.士兵.场景等丰富元素,为scratch爱好者提供多样选择,适合各类三国主题创作. 免费下载:www.xiaohujing. ...

  7. scratch源码下载 | 飞天厨师

    程序说明: <飞天厨师>是一款使用Scratch平台制作的游戏程序.在这个游戏中,玩家将控制一名厨师角色,他在天空中不断掉落.玩家需要利用方向键左右移动厨师,以便他能够准确地踩在空中的食物 ...

  8. Jmeter函数助手27-urlencode

    urlencode函数用于将字符串进行application/x-www-form-urlencoded编码格式化. String to encode in URL encoded chars:填入字 ...

  9. 【Java】单号创建服务

    需求:ERP项目存在若干个业务功能,每个业务的单子的单号创建规则需要被统一规划 1.每个业务有自己对应的标识 2.业务单号必须以英文字母为前缀,长度在2 - 4个字符 3.单号的组成 = [ 前缀 ] ...

  10. Typora配置自动上传图片到图床

      在多平台发布文章时,如果遇到图片不能导入的问题,推荐使用图床!推荐使用阿里云或腾讯云,免费的不用考虑了! PicGo下载 链接:https://pan.quark.cn/s/2ec95402631 ...