效果

当 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. Web1.0、Web2.0 和 Web3.0 的区别

    Web1.0.Web2.0 和 Web3.0 的区别主要体现在以下几个关键方面: 内容创作与交互: Web1.0:内容主要由网站所有者或少数专业人员创建,用户大多只是被动接收信息.例如,早期的雅虎.新 ...

  2. 用 Git 操作的数据库?这个项目火了!

    # 用 Git 操作的数据库?这个项目火了!> 超级特别又实用的数据库,快来补课!Git 是一个开源的分布式版本控制系统,可以敏捷高效地管理代码,让项目代码支持同时存在多个不同的版本和分支,是程 ...

  3. 玄机-第二章日志分析-apache日志分析

    前言 出息了,这回0元玩玄机了,因为只是日志分析,赶紧导出来就关掉(五分钟内不扣金币) 日志分析只要会点正则然后配合Linux的命令很快就完成这题目了,非应急响应. 简介 账号密码 root apac ...

  4. C#从6.0~9.0都更新了什么?

    一.C#6中新增的功能 get 只读属性 简洁的语法来创建不可变类型,仅有get访问器: public string FirstName { get; } public string LastName ...

  5. MySQL 实现 EF Code First TimeStamp/RowVersion 并发控制

    在将项目迁移到MySQL 5.6.10数据库上时,遇到和迁移到PostgreSQL数据库相同的一个问题,就是TimeStamp/RowVersion并发控制类型在非Microsoft SQL Serv ...

  6. 《HelloGitHub》第 100 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. github.com/521xueweihan/HelloG ...

  7. 解决Prism中对话框服务中对话框开启时回调函数不会触发的问题

    解决办法 新建一个类DialogServiceExtend,然后在再注册 public class DialogServiceExtend : DialogService { public Dialo ...

  8. lambda表达式用法

    (参数列表)->{代码块}; (int a,int b)->{return a+b;}; 本质为匿名函数 参数的类型可以省略: (a,b)->{return a+b;} 当参数只有一 ...

  9. iOS开发基础144-逐字打印效果

    在AIGC类的APP中,实现那种一个字一个字.一行一行地打印出文字的效果,可以通过多种方法来实现.下面是一些实现方法,使用Swift和OC来举例说明. OC版 1. 基于定时器的逐字打印效果 可以使用 ...

  10. 人形机器人从人类演示(demenstration)数据中学习人类行为(behavior)的几种方式

    人形机器人从人类演示(demenstration)数据中学习的几种方式 使用仿真环境,在仿真环境中生成近似人类的行为数据,然后利用仿真生成的数据训练机器人. 该种方式最为传统,也最为易行,但是由于仿真 ...