今天是 2021 年的最后一天了,明天就是 2022 年。回顧過去一年,要特別感謝大家對我的支持。
人生不過幾十年,每一年都值得紀念和祝福,所以我想用 Node.js 控制臺動畫送上一份我的新年祝福:
視頻
2021 年的最后一天,我們來學點 cli 的技術吧。
實現原理
動畫都需要一幀幀的刷新,控制臺動畫也不例外。
那控制臺是怎么刷新的呢?
控制臺中有一種叫做 TTY,特點是可以設置顏色,可以清除或修改某個位置的內容。平時我們用的 Terminal 大多都是這種。
Node.js 里面可以通過 process.stdout.isTTY 來查看是否是 TTY 類型的標準輸出流,然后提供了 readline 這個包來操作它。
比如用 readline.cursorTo(stream, x, y) 來移動光標位置, readline.clearLine(stream) 來清除某行的內容,用 readline.clearScreenDown(stream)來清除某個位置之后的所有內容。
能夠移動光標位置,能夠清除內容,也就能夠刷新、能夠做任意的繪制,這是控制臺動畫的基礎。
繪制用 readline.wrtie(data) 來輸出字符,可以指定字符的顏色(用 chalk 這個包)。
只是輸出帶顏色的字符么?那張圖片和那個藝術字呢?
其實那也是字符來做的,只不過給上了不同的顏色而已,控制臺只能顯示字符。
左邊的這張圖片的顯示原理是拿到圖片的像素信息,然后轉成不同顏色的字符。可以用 console-png 這個包。
右邊的藝術字的顯示原理是固定的一些字符信息,設置上顏色。用的是 cfonts 這個包。
小結一下:
TTY 類型的控制臺可以設置顏色、可以在任意位置清除和修改內容,這是控制臺動畫能一幀幀刷新的基礎,Node.js 提供了 readline 模塊來做這些。
控制臺只能顯示字符,圖片可以拿到像素信息然后用帶顏色的字符來顯示,藝術字是提前準備好字符數組來繪制,綜合把這些內容繪制在不同的位置,然后定時一幀幀刷新就構成了控制臺動畫。
思路通了之后,我們來寫代碼實現一下。
代碼實現
首先,我們會用到 readline 這個內置模塊,用它來做一幀幀的刷新。
調用 readline.createInterface 來創建一個實例,指定輸入輸出流為 stdin、stdout。
stdin 是標準輸入流,是指鍵盤。
stdout 是標準輸出流,是指顯示器。
- const readline = require('readline');
- const outStream = process.stdout;
- const rl = readline.createInterface({
- input: process.stdin,
- output: outStream
- });
然后清除整個控制臺的內容,把光標移動到開始,然后 clear:
- readline.cursorTo(outStream, 0, 0);
- readline.clearScreenDown(outStream);
然后開始繪制文字:
準備一個數組放要繪制的文字,然后定時在不同的位置顯示這些文字
- const textArr = ['2021', '感謝', '大家的', '支持','2022', '我們','一起','加油!'];
- (async function () {
- for(let i = 0; i< textArr.length; i++) {
- readline.cursorTo(outStream, ...randomPos());
- rl.write(randomStyle(textArr[i]));
- await delay(1000);
- readline.cursorTo(outStream, 0, 0);
- readline.clearScreenDown(outStream);
- }
- })();
- function delay(time) {
- return new Promise((resolve) => setTimeout(resolve, time));
- }
我用了 async await 的方式來組織代碼,基于封裝了一個 delay 方法。
其中位置、樣式都是隨機的:
- const chalk = require('chalk');
- function randomPos() {
- const x = Math.floor(30 * Math.random());
- const y = Math.floor(10 * Math.random());
- return [x, y];
- }
- function randomStyle(text) {
- const styles = ['redBright','yellowBright', 'blueBright', 'cyanBright','greenBright', 'magentaBright', 'whiteBright'];
- const color = styles[Math.floor(Math.random() * styles.length)];
- return chalk[color](text);
- }
前面的文字動畫就做完了:
然后是圖片和藝術字的顯示:
圖片需要用 console-png 來把圖片像素信息取出來轉成字符的形式:
- const consolePng = require('console-png');
- consolePng.attachTo(console);
- const image = fs.readFileSync(__dirname + '/headpic.png');
- console.png(image);
藝術字用 cfonts 來繪制,拿到字符之后顯示在右邊的位置:
- const CFonts = require('cfonts');
- const prettyFont = CFonts.render('|HAPPY|NEW YEAR', {
- font:'block',
- colors: ['blue', 'yellow']
- });
- let startX = 60;
- let startY = 0;
- prettyFont.array.forEach((line, index) => {
- readline.cursorTo(outStream, startX + index, startY + index);
- rl.write(line);
- });
cfont.render 的第一個參數里的豎線是指換行,第二個參數的 font 是指定樣式,colors 指定顏色。
然后對返回的字符數組做光標的偏移之后再顯示。
最后,在右下方顯示公眾號的標記:
- readline.cursorTo(outStream, 120, 25);
- rl.write(chalk.yellowBright('---神光的編程秘籍'));
這樣,最后這一幀就繪制完了:
大功告成!我們再來看下整體的效果:
視頻
代碼上傳到了 github:https://github.com/QuarkGluonPlasma/cli-exercise
也在這里貼一份:
- const readline = require('readline');
- const chalk = require('chalk');
- const CFonts = require('cfonts');
- const consolePng = require('console-png');
- const fs = require('fs');
- consolePng.attachTo(console);
- const outStream = process.stdout;
- const rl = readline.createInterface({
- input: process.stdin,
- output: outStream
- });
- function delay(time) {
- return new Promise((resolve) => setTimeout(resolve, time));
- }
- function randomStyle(text) {
- const styles = ['redBright','yellowBright', 'blueBright', 'cyanBright','greenBright', 'magentaBright', 'whiteBright'];
- const color = styles[Math.floor(Math.random() * styles.length)];
- return chalk[color](text);
- }
- function randomPos() {
- const x = Math.floor(30 * Math.random());
- const y = Math.floor(10 * Math.random());
- return [x, y];
- }
- readline.cursorTo(outStream, 0, 0);
- readline.clearScreenDown(outStream);
- const image = fs.readFileSync(__dirname + '/headpic.png');
- const textArr = ['2021', '感謝', '大家的', '支持','2022', '我們','一起','加油!'];
- (async function () {
- for(let i = 0; i< textArr.length; i++) {
- readline.cursorTo(outStream, ...randomPos());
- rl.write(randomStyle(textArr[i]));
- await delay(1000);
- readline.cursorTo(outStream, 0, 0);
- readline.clearScreenDown(outStream);
- }
- console.png(image);
- await delay(1000);
- const prettyFont = CFonts.render('|HAPPY|NEW YEAR', {font:'block', colors: ['blue', 'yellow']});
- let startX = 60;
- let startY = 0;
- prettyFont.array.forEach((line, index) => {
- readline.cursorTo(outStream, startX + index, startY + index);
- rl.write(line);
- });
- readline.cursorTo(outStream, 120, 25);
- rl.write(chalk.yellowBright('---神光的編程秘籍'));
- })();
總結
TTY 類型的終端支持設置字符顏色和在任意位置清除和修改內容,這是控制臺動畫可以刷新的基礎。
我們通過把圖片的像素轉為有顏色的字符來顯示圖片,通過預置的字符數組來顯示藝術字,在不同的位置繪制這些內容就可以達到豐富的顯示效果。
其中,控制臺的光標位置修改和內容的清除使用 Node.js 的 readline 內置模塊,其余的是第三方的包。藝術字使用 cfonts 的包,圖片顯示使用 console-png,字體顏色使用 chalk。
控制臺是我們每天都用的,前端的大多數工具都是 cli 的形式。深入學習 cli 顯示各種內容和做動畫的知識,有助于更好的理解一些 cli 工具和寫出更好的 cli 工具。
最后,再次感謝大家過去一年的支持,明年一起加油呀~
原文鏈接:https://mp.weixin.qq.com/s/pPBeTqbZqoKFpuLusYE7Vg