背景 :在上一篇文章 认识下 ANIS,写个简版 chalk 我们系统的学习了 ANSI 关于颜色的知识,并简单实现了一个 chalk,然而 ANSI 剩下的一大部分关于光标的我们没有讲,这次我们就来学习 ANSI 关于光标的知识,并简单的实现一个 progress。
目录
- ANSI 光标
- 进度指示器
- progress 的实现
ANSI 光标
关于 ANSI 的基本语法就不讲了,可以去看 认识下 ANIS,写个简版 chalk 里面有非常详细的讲解,直接进入光标功能讲解。
关于光标移动操作最常见的是下面四个:
编码 | ANSI | 名称 | 作用 |
---|---|---|---|
{n} A |
\u001b[{n}A |
光标上移(Cursor Up) | 光标向指定的方向移动 n(默认1)格。如果光标已在屏幕边缘,则无效。 |
{n} B |
\u001b[{n}B |
光标下移(Cursor Down) | 光标向指定的方向移动 n(默认1)格。如果光标已在屏幕边缘,则无效。 |
{n} C |
\u001b[{n}C |
光标前移(Cursor Forward) | 光标向指定的方向移动 n(默认1)格。如果光标已在屏幕边缘,则无效。 |
{n} D |
\u001b[{n}D |
光标后移(Cursor Back) | 光标向指定的方向移动 n(默认1)格。如果光标已在屏幕边缘,则无效。 |
为了,更好的观察光标移动的现在,我们先来个小案例热身下。在使用 Webpack 等打包工具,我们启用服务的时候,一般都会有进度指示器,从 0% ~ 100% 从而给我们个心理预期。现在就来实现它。
进度指示器
思路,要想实现进度指示器,首先要搞定数字的事情,这个简单,一个循序搞定:
for(let i = 0; i <= 100; i++) {
console.log(i);
}
运行程序控制台换行顺序输出结果:
第二步的思路很关键:
- 程序输出 1 结束的时候,我们应该把光标拉到行的开头,重写当前行,数字为 2。
- 程序输出 2 结束的时候,我们应该把光标拉到行的开头,重写当前行,数字为 3。
- 以此类推
- 程序输出 99 结束的时候,我们应该把光标拉到行的开头,重写当前行,数字为 100。
总结就两个步骤:
- 前移动光标。
- 从光标开始,重写当前行。
前移动光标我们使用 \u001b[{n}D
。重写行我们使用 NodeJs 的 process.stderr.write
。
好了,结果输出最长位为 100%
四位,为了保证光标能前移到行首,我们给的 n = 4
。
// 同步睡眠函数
const asyncSleep = (ms) => {
const startTime = Date.now();
let nowTime = startTime;
while (nowTime - startTime < ms) {
nowTime = Date.now();
continue;
}
}
const loading = () => {
for(let i = 0; i <= 100; i++) {
asyncSleep(1000)
// 光标前移四格
process.stderr.write("\u001b[4D");
asyncSleep(1000)
process.stderr.write(`${i}%`);
}
}
loading()
运行程序,中间加了睡眠函数,来模拟加载或打包过程,能很直观的看到光标和重写的变化,动图只录制了十秒:
2021-11-17 17-11-01.2021-11-17 17_12_21.gif上面只是为了给你观察光标的移动,我们可以简单修改下代码,移动光标立刻重写,以达到完全模拟进度提示。
// 改了 loading 的代码
const loading = () => {
for(let i = 0; i <= 100; i++) {
asyncSleep(100)
// 光标前移四格
process.stderr.write(`\u001b[4D${i}%`);
}
}
看下完美的运行效果:
2021-11-17 17-18-07.2021-11-17 17_18_36.gif刺激不,看到百分比在函数执行完之前从1%
到无缝变化100%
。
OK,我们学会了前移操作,那上下后移,甚至清行、清屏对你来说也是小意思了。
Wiki 上维护了一份不完整的 ANSI 控制序列列表如下,大胆的发挥你的想象去使用吧。
progress 的实现
上面的百分比实现有点简单,接下来我们实现一个难点的,实现一个类似 progress 的 barLoading。
2021-11-16 17-35-45.2021-11-16 17_36_09.gif代码不难写重要的是思路,我们来搞下思路:
- 光标定位到行首
- 进度条的长度是函数的参数。
- 进度条的长度渲染的是空格。
- 我们给空格加上背景色就完成了。
好了,我们来看代码:
// 同步延时器
const asyncSleep = (ms) => {
const startTime = Date.now();
let nowTime = startTime;
while (nowTime - startTime < ms) {
nowTime = Date.now();
continue;
}
}
const barProgress = (total) => {
for(let i = 0; i <= total; i++) {
// 1. 光标定位到行首
process.stderr.write(`\u001b[${total + 100}D`);
// 2. 睡眠 100ms
asyncSleep(100);
let left = i, right = total - i, whiteSpace = "", greenSpace = "";
// 加载完成的空格数
while (left) {
greenSpace += " ";
left--;
}
// 未加载的空格数
while (right) {
whiteSpace += " ";
right--;
}
// 填充背景色
const leftBg = `\u001b[42m${greenSpace}\u001b[49m`;
const rightBg = `\u001b[47m${whiteSpace}\u001b[49m`;
// 输出终端
process.stderr.write(`${leftBg}${rightBg}${i}%`);
}
}
使用的时候直接调用就行了,比如说:barProgress(20);
,你可以随便改变传入的参数来决定 progress 的长度。
除了我们演示的 progress 我们还知道一种井号进度条,有时候安装 npm 包的时候会出现,演示效果如下:
2021-11-17 18-57-40.2021-11-17 18_58_34.gif这个实现很简单了,去掉颜色填充,左边填充变井号,右边仍然保持空格,立刻就能见到效果。
const poundProgress = (total) => {
for(let i = 0; i <= total; i++) {
// 1. 光标定位到行首
process.stderr.write(`\u001b[${total + 100}D`);
// 2. 睡眠 100ms
asyncSleep(50);
let left = i, right = total - i, whiteSpace = "", greenSpace = "";
// 加载完成的空格数
while (left) {
greenSpace += "#";
left--;
}
// 未加载的空格数
while (right) {
whiteSpace += " ";
right--;
}
// 输出终端
process.stderr.write(`[${greenSpace}${whiteSpace}]${i}%`);
}
}
poundProgress(100);
掌握了精髓我们想怎么玩就怎么玩,比如五角星的 progress:
2021-11-17 22-22-38.2021-11-17 22_23_22.gif总结
基于上篇文章 认识下 ANIS,写个简版 chalk 留的知识点,我们系统学习了光标的用法,并带大家实践了平时常见的加载器和 progress,行文最后推荐个基于 ANSI 实现的 npm 包,ansi-escape-sequences。
网友评论