Open chenshenhai opened 4 years ago
经常用系统终端进行技术开发的过程会看到一些终端的动画效果,例如这个
最近刚好好奇的了解了一下,才知道实现这个能力是利用了终端支持的 ANSI转义序列,任何语言只要能调用终端的标准输入/输出(stdin/stdout),都可以直接使用ANSI转义序列的规范制作对应的终端动画。
ANSI转义序列
stdin/stdout
ANSI是一种字符代码,为使计算机支持更多语言,通常使用 0x00~0x7f 范围的1个字节来表示 1个英文字符。超出此范围的使用0x80~0xFFFF来编码,即扩展的ASCII编码 [1]
0x00~0x7f
0x80~0xFFFF
ANSI转义序列是一种带内信号的转义序列标准, 相关视频终端控制光标屏幕位置、文本显示、显示样式等操作。[2]
注意,需要在Linux/MacOS 的环境下学习和使用
echo -e "\x1b[31m helloworld\x1b[0m"
\x1b[31m 后面的文本字体色为红色,其中31m就是代表ANSI中的红色
\x1b[31m
31m
ANSI
\x1b[0m 关闭所有属性
\x1b[0m
前景色范围在 40 - 49
40 - 49
echo -e "\x1b[41m helloworld\x1b[0m"
\x1b[41m 后面的后景色为红色,其中41m就是代表ANSI中的红色
\x1b[41m
41m
文本颜色范围在 40 - 49
echo -e "\x1b[5m helloworld\x1b[0m"
\x1b[5m
const process = require("process"); const frame = "▊"; /** * 等待操作 * @param {number} time */ async function sleep(time = 10) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, time) }) } /** * 换行操作 */ function printNewLine() { process.stdout.write(`\x1b[0C \x1b[K\r\n`); } class Progress { async run(time = 1000, percent = 100, modulo = 2) { const count = Math.floor(percent / modulo); for (let i = 0; i < count; i ++) { await sleep(time / count); this._print(); } printNewLine(); } _print() { // 控制打印进度条每帧的内容 // 重复使用的时候打印开始会从上次结束的位置开始 process.stdout.write(`\x1b[K${frame}`); } } const progress = new Progress(); progress.run(1000, 100);
const process = require("process"); /** * 等待操作 * @param {number} time */ async function sleep(time = 10) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, time) }) } /** * 换行操作 */ function printNewLine() { process.stdout.write(`\x1b[0C \x1b[K\r\n`); } /** * 清行操作 * @param {number} len 原有文本长度 */ function clearLine(len) { process.stdout.write(`\x1b[${len}D`); } const frame = "▓"; const backgroundFrame = "░"; class Progress { async run(time = 1000, percent = 100, modulo = 2) { const count = Math.floor(percent / modulo); for (let i = 0; i < count; i ++) { await sleep(time / count); const progressLength = this._printProcess(i, count, modulo); if (i < count - 1) { clearLine(progressLength); } } printNewLine(); } /** * 打印进度条 * @param {number} index 进度条当前帧索引位置 * @param {number} count 进度条帧数 * @param {number} modulo 进度条帧率变化模数 */ _printProcess(index, count, modulo) { let progressLength = 0; for (let i = 0; i < count; i ++) { if (i <= index) { progressLength += this._print(frame); } else { progressLength += this._print(backgroundFrame); } } let percentNum = (index + 1) * modulo; percentNum = Math.min(100, percentNum); percentNum = Math.max(0, percentNum); progressLength += this._print(` ${percentNum}%`); return progressLength; } /** * 打印终端文本 * @param {string} text 文本 * @param {number} leftMoveCols 光标左移动位数 */ _print(text, leftMoveCols) { let code = `\x1b[K${text}`; if (leftMoveCols >= 0) { code = `\x1b[${leftMoveCols}D\x1b[K${text}`; } process.stdout.write(code); return code.length; } } const progress = new Progress(); progress.run(1000, 100);
const process = require("process"); /** * 等待操作 * @param {number} time */ async function sleep(time = 10) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, time) }) } /** * 换行操作 */ function printNewLine() { process.stdout.write(`\x1b[0C \x1b[K\r\n`); } class Loading { constructor() { this._intervalId = -1; this._beforeLength = 0; this._isDuringLoading = false; this._loadingIndex = 0; } /** * Loading动画开始操作 * @param {number} speed Loading每帧速度 */ start(speed = 100) { if (this._isDuringLoading === true) { return; } this._intervalId = setInterval(() => { this._printLoadingText(); }, speed); this._isDuringLoading = true; } /** * Loading 停止操作 */ stop() { clearInterval(this._intervalId); printNewLine(); this._isDuringLoading = false; this._loadingIndex = 0; } /** * 打印 Loading 操作 */ _printLoadingText() { const max = 10; if (this._loadingIndex >= max) { this._loadingIndex = 0; } const charList = []; for (let i = 0; i < max; i++) { if (i === this._loadingIndex) { charList.push('==>'); } else { charList.push(' '); } } const loadingText = [...['['], ...charList, ...[']']].join(''); this._print(loadingText); this._loadingIndex += 1; } _print(text) { const code = `\x1b[${this._beforeLength}D \x1b[K ${text}` process.stdout.write(code); this._beforeLength = code.length; } } async function main() { const loading = new Loading(); loading.start(); await sleep(2000); loading.stop(); } main();
[1] 百度百科: ANSI [2] 维基百科: ANSI转义序列
前言
经常用系统终端进行技术开发的过程会看到一些终端的动画效果,例如这个
最近刚好好奇的了解了一下,才知道实现这个能力是利用了终端支持的
ANSI转义序列
,任何语言只要能调用终端的标准输入/输出(stdin/stdout
),都可以直接使用ANSI转义序列
的规范制作对应的终端动画。什么是ANSI
ANSI是一种字符代码,为使计算机支持更多语言,通常使用
0x00~0x7f
范围的1个字节来表示 1个英文字符。超出此范围的使用0x80~0xFFFF
来编码,即扩展的ASCII编码 [1]什么是ANSI转义序列
ANSI转义序列是一种带内信号的转义序列标准, 相关视频终端控制光标屏幕位置、文本显示、显示样式等操作。[2]
历史 [2]
规范历史
系统支持历史
基本使用
输出带前景色的文本
\x1b[31m
后面的文本字体色为红色,其中31m
就是代表ANSI
中的红色\x1b[0m
关闭所有属性前景色范围在
40 - 49
输出带后景色的文本
\x1b[41m
后面的后景色为红色,其中41m
就是代表ANSI
中的红色\x1b[0m
关闭所有属性文本颜色范围在
40 - 49
输出闪烁的文本
\x1b[5m
后面的文本闪烁\x1b[0m
关闭所有属性广泛支持的 ANSI 转义序列
Node.js例子
例子一: 实现一个简单的控制台进度条
实现效果
实现源码
例子二: 实现一个带背景和数字变化的进度条
实现效果
实现源码
例子三: 实现一个等待Loading效果
实现效果
实现源码
参考资料
[1] 百度百科: ANSI [2] 维基百科: ANSI转义序列