控制台居然可以这么玩?五分钟带你上手ANSI指令,实现一个log工具包

目录

前言

基础知识

进阶实践

ANSI参数

ANSI类

JSLog类

工具的使用说明

配置相关

全局配置项

默认配置

基本用法

打印字符

添加全局配置项

清空所有样式及操作行为

校验传入的参数是否正确

样式控制

Node环境

浏览器中

光标控制指令

光标位置偏移

滚动条控制

其他用法

log函数的链式调用

同一行打印多种log

属性的指令集

ANSI相关的操作

ANSI编码指令拼接

颜色设置

字体样式

光标操作

光标移动

滚动条移动

ANSI颜色指令的进阶用法

兼容模式

真彩模式

光标坐标轴移动

写在最后

相关代码:


前言

在日志系统或者工程化打包插件中我们时常会看到控制台输出五颜六色的字体以及符号,这些字体大多是使用ANSI转义序列来实现的,ANSI转义序列(或ANSI转义码)是一种用于控制文本输出格式的标准化方法,通常用于终端和控制台应用程序的样式或者输出,比如:粗体、斜体、颜色、背景,光标控制等,本篇文章将通过JS工具库的形式与大家分享常用的ANSI指令及进阶用法

基础知识

先来了解一下ANSI的基础用法,以node控制台为例,当我们使用console.log或print输出字符串时,在字符串中插入一些特殊字符,比如:

const logAndReset = (...args: string[]) => console.log(...args.map(it => it + `\x1b[0m`));
logAndReset(`普通字体`, `\x1b[1m加粗`, `\x1b[3m斜体`, `\x1b[36m青色`, `\x1b[42m绿色背景`);

效果如下: 

上文中的`\x1b[变量m`就是通过ANSI转义码的形式输出对应指令,这些序列以Escape字符(ASCII码27,也就是Esc)开头,后面跟着一些特定的字符,用于控制终端的颜色、样式和其他属性。其中x1b代表16进制的17,此外,在node中我们还可以使用Unicode中的\u001b表示:`\u001b[1m`。m表示设置文本样式的参数,除了m外,还可以通过其他参数控制其他功能:A表示光标向上移动;2J表示清除整个屏幕等等。

更多参数指令使用方式可以参考这个文件:src/static.ts · 阿宇的编程之旅/js-log-lib - Gitee.com

进阶实践

有了上面的概念,我们可以尝试使用代码实现一个工具,将ANSI语义化,使用参数控制其样式和行为

ANSI参数

首先是收集基础的指令的文件static,这里面存放的是需要用到的ANSI参数


/**
0:重置所有样式
1:粗体(高亮)
2:暗淡(降低亮度)
3:斜体
4:下划线
5:闪烁
7:反显(交换前景色和背景色)
8:隐藏(不可见)
9:划掉(删除线)
21:关闭粗体(粗体关闭)
22:正常颜色和粗体(关闭粗体和暗淡)
23:关闭斜体
24:关闭下划线
25:关闭闪烁
27:关闭反显
28:关闭隐藏
29:关闭删除线
30 到 37:设置前景色(文本颜色)
38:设置前景色为RGB颜色
39:重置前景色为默认颜色
40 到 47:设置背景色
48:设置背景色为RGB颜色
49:重置背景色为默认颜色
90 到 97:设置高亮前景色
100 到 107:设置高亮背景色
**/export const ANSIParams = {// 2:真彩色模式; 5:兼容模式colorMode: {trueColor: "2",compatibility: "5",},// 48:背景颜色,38:字体颜色colorType: {foreground: "38",background: "48"},color: {black: 30,red: 31,green: 32,yellow: 33,blue: 34,magenta: 35,cyan: 36,white: 37,bgBlack: 40,bgRed: 41,bgGreen: 42,bgYellow: 43,bgBlue: 44,bgMagenta: 45,bgCyan: 46,bgWhite: 47,brightBlack: 90,brightRed: 91,brightGreen: 92,brightYellow: 93,brightBlue: 94,brightMagenta: 95,brightCyan: 96,brightWhite: 97,bgBrightBlack: 100,bgBrightRed: 101,bgBrightGreen: 102,bgBrightYellow: 103,bgBrightBlue: 104,bgBrightMagenta: 105,bgBrightCyan: 106,bgBrightWhite: 107,},textStyle: {reset: 0,// 重置所有样式bold: 1,// 粗体dim: 2,// 暗淡(降低亮度)italic: 3,// 斜体underline: 4,// 下划线inverse: 7,// 闪烁hidden: 8,// 隐藏strikethrough: 9,// 删除线noBold: 21,    // 关闭粗体noItalic: 23,  // 关闭斜体noUnderline: 24,  // 关闭下划线noBlink: 25,   // 关闭闪烁noInverse: 27, // 关闭反显noHidden: 28,  // 关闭隐藏noStrikethrough: 29,  // 关闭删除线},cursor: {savePosition: `s`, // 保存光标位置restorePosition: `u`, // 恢复光标位置reportPosition: `6n`, // 获取光标位置toStart: `H`, // 将光标移动到屏幕的左上角toLineStart: `E`, // 将光标移动到当前行的开头toLineEnd: `F`, // 将光标移动到当前行的末尾eraseDisplay: `2J`, // 清除整个屏幕eraseLine: `K`, // 清除从光标位置到行尾的内容direct: {custom: `H`,// 光标移动到指定行列up: `A`,// 光标向上移动down: `B`,// 光标向下移动right: `C`,// 光标向右移动left: `D`,// 光标向左移动}},scroll: {up: "S",down: "T"}
}

通过这些参数也可以直接使用console.log的形式打印出效果,可以看到,我将指令分成了六类:

colorMode:显示灰度色或RGB颜色,前者兼容性更好,后者颜色更丰富(具体用法在后文会讲到)
colorType:当使用了调色板时,设置前景色还是背景色
color:默认的颜色指令
textStyle:字体样式
cursor:光标控制相关的
scroll:滚动条相关

为了方便使用,我使用下面的ANSI类对上述指令做了规范化处理

ANSI类

围绕上面的基础指令,我们可以将对应的参数传入函数中达到效果

// ANSI 类,用于生成 ANSI 控制码
class ANSI {// moveTo 用于生成移动光标的 ANSI 控制码moveTo = (opts: IMoveParams) => {const { direct, position } = opts ?? {}if (!direct || !position) return ``let _p = ``if (direct === "custom" && typeof position === "object") {const { col, row } = position_p = `${row};${col}`} else if (typeof position !== "object") {_p = `${position}`}if (_p) return `${_p}${ANSIParams.cursor.direct[direct] ?? ""}`return ``}// scrollTo 方法,用于生成滚动屏幕的 ANSI 控制码scrollTo = (row: number) => {if (!row) return ``// 滚动条向下滚动,代码对应向上移动一行,相当于删除(backspace)row行let direct = ANSIParams.scroll.downif (row < 0) {// 滚动条向上滚动,代码对应向下移动一行,相当于回车(enter)row行direct = ANSIParams.scroll.up}return `${Math.abs(row)}${direct}`}// 生成光标相关的 ANSI 控制码getCursor = (cursor: ICursor) => {if (cursor) return `${ANSIParams.cursor[cursor] ?? ""}`else return ``}// 生成颜色和样式相关的 ANSI 控制码getColorMode = (mode: IColorMode) => `${ANSIParams.colorMode[mode] ?? "2"}`getColorType = (type: IColorType) => `${ANSIParams.colorType[type] ?? "38"}`getRGB = (options: Partial<IRGB>) => {const { type = "foreground", mode = "trueColor", red = 0, green = 0, blue = 0, color = 0 } = optionsconst cType = this.getColorType(type), cMode = this.getColorMode(mode)if (cMode === ANSIParams.colorMode.compatibility) return `${cType};${cMode};${color}`return `${cType};${cMode};${red};${green};${blue}`}getColor = (color: IColorStr) => {if (color) return `${ANSIParams.color[color] ?? ""}`else return ``}getTextStyle = (textStyle: ITextStyle) => {if (textStyle) return `${ANSIParams.textStyle[textStyle] ?? ""}`else return ``}// 生成最终的 ANSI 控制码字符getANSI = (params: string, isStyle: boolean = true) => params ? `\x1B[${params}${isStyle ? "m" : ""}` : ""// 重置所有样式reset = () => this.getANSI(`0`)
}

其中TS类型代码如下:


import { ANSIParams } from "./static.js"
// 颜色字符串的索引
export type IColorStr = keyof typeof ANSIParams.color
// 前(后)景色可以是 RGB 对象或颜色字符串
export type IColor = IRGB | IColorStr
// 文本样式
export type ITextStyle = keyof typeof ANSIParams.textStyle
// 光标的方向
export type IDirect = keyof typeof ANSIParams.cursor.direct
// 光标操作的索引
export type ICursor = keyof Omit<typeof ANSIParams.cursor, "direct">
export type IGlobalOpts = Partial<{text: string; // 要打印的文本reset: boolean; // 是否在末尾重置样式type: string; // console的类型split: boolean; // 是否拆分显示,在node中可以在一个console.log中分开显示,比如:console.log("1","2","3")和console.log("123"),前者在字符之间会有空格,split为true表示使用前者显示,反之使用后者,在浏览器环境下只能合并显示color: IColor[] | IColor; // 前(后)景色或其数组,方便传入多个颜色参数,但是有些控制台似乎不兼容textStyle: ITextStyle[] | ITextStyle; // 文本样式或样式数组cursor: ICursor; // 光标控制move: IMoveParams; // 光标移动参数scroll: number; // 滚动条行数style: Partial<CSSStyleDeclaration>; // CSS样式对象,仅支持浏览器环境
}>
export type IOpts = Omit<IGlobalOpts, "type" | "split">
// 颜色模式,2:真彩色模式; 5:兼容模式; null:默认颜色模式
export type IColorMode = keyof typeof ANSIParams.colorMode
// 颜色类型,48:背景颜色,38:字体颜色
export type IColorType = keyof typeof ANSIParams.colorType
export type IRGB = {red?: string | numbergreen?: string | numberblue?: string | numbertype?: IColorType // 字体颜色或背景颜色mode?: IColorModecolor?: string | number// 兼容模式下,256色调色板的颜色索引
}
export type IPosition = {col: number | string // 列row: number | string // 行
}
export type IMoveParams = {direct: IDirect// 移动方向position: IPosition | number | string// 移动到的位置,可以是坐标对象或数字
}

我们可以直接使用上面的类配合log输出样式到控制台

const ansi = new ANSI();
const { getANSI, getTextStyle, reset } = ansi;
console.log(getANSI(getTextStyle("bold")), "加粗", reset());

实际上只使用ANSI操作控制台,有一个ANSI类就够了,但是我在工具中使用了JSLog对ANSI进行了拓展

JSLog类

实现JSLog类的目的有以下几点

  • 日志格式化:根据传入参数的不同,使用不同的ANSI控制码和样式信息
  • 环境适配:通过判断是在Node环境还是浏览器环境,做出相应的处理
  • 全局选项配置:日志默认使用全局的配置,每个日志函数可以通过传入配置达到封装目的
  • 可读性和维护性:相对于ANSI类,更易读,方便拓展并提升维护性

下面是JSLog的代码

const defaultOpts: IGlobalOpts = {reset: true,type: "log",split: true
}
// JSLog 类,继承 ANSI 类,用于生成日志,并根据环境打印到控制台或执行其他操作
export class JSLog extends ANSI {readonly stylePlaceholder = "%c"; // 浏览器环境下字符串占位符:%cisNode: boolean = typeof process !== "undefined";private globalOpts: IGlobalOpts = defaultOpts// 全局选项作为参数constructor(globalOpts?: IGlobalOpts) {super()this.checkOptions(globalOpts)this.mixins(globalOpts)}// 合并全局选项mixins(opts: IGlobalOpts) {this.globalOpts = emptyObject({ ...this.globalOpts, ...opts })return this.globalOpts}// 清空全局样式及行为clear() {this.globalOpts = emptyObject(defaultOpts)}// 生成日志log = (...args: IOpts[]) => {if (args.length <= 0) return thisconst { logQueue, styleQueue } = this.createQueue(args)this.logFn(logQueue, styleQueue)return this}// 生成日志队列private createQueue = (args: IOpts[]) => {const styleQueue = [], logQueue: string[] = []const { isNode, stylePlaceholder, globalOpts } = thisargs.forEach(it => {this.checkOptions(it)const { text = "" } = itconst _opts = emptyObject({ ...globalOpts, ...it })const { reset, style = {} } = _optsconst hasStyle = !isEmptyObject(style)logQueue.push(`${this.formate(_opts)}${hasStyle ? stylePlaceholder : ""}${text}${reset ? this.reset() : ""}`)if (hasStyle) {const _style = this.formatStyle(style)_style && styleQueue.push(_style)}})if (isNode && globalOpts.split) {return { logQueue, styleQueue }}return { logQueue: logQueue.join(""), styleQueue }}// 格式化选项生成 ANSI 控制码private formate(opts: IOpts) {const { color, textStyle, cursor, move, scroll } = opts// TODO:控制渲染执行顺序const params = this.getANSI(`${this.formatCursor(cursor)}${this.moveTo(move)}${this.scrollTo(scroll)}`, false) + this.getANSI(`${this.formatColor(color)}${this.formatText(textStyle)}`)return params}// formatParams、formatText、formatColor方法,格式化样式和颜色选项private formatCursor(cursor: ICursor) {if (typeof cursor === "string") {return this.getCursor(cursor)}return ``}private formatText(textStyle: ITextStyle[] | ITextStyle) {if (typeof textStyle === "string") {return this.getTextStyle(textStyle)} else if (typeof textStyle === "object") {let __temp = ``;textStyle.forEach(it => __temp += this.getTextStyle(it))return __temp}return ``}private formatColor(color: IColor[] | IColor) {if (typeof color === "string") {return this.getColor(color)} else if (typeof color === "object") {if (getType(color) === "array") {let __temp = ``;(color as IColor[]).forEach(it => __temp += this.formatColor(it))return __temp} else {return this.getRGB(color as IRGB)}}return ``}// 格式化浏览器端的style样式属性private formatStyle(styles: Partial<CSSStyleDeclaration>) {let styleStr = ``Reflect.ownKeys(styles).forEach(k => {if (typeof k === "string") styleStr += `${toKebabCase(k)}:${styles[k]};`})return styleStr}// 执行打印操作private logFn(logQueue: string[] | string, styleQueue: string[]) {const { globalOpts } = thisconst { type } = globalOptsif (typeof logQueue === "string") {return console[type](logQueue, ...styleQueue)}return console[type](...logQueue, ...styleQueue)}// 校验options参数checkOptions(opts: IGlobalOpts = {}) {const { isNode } = thisconst { cursor, move, scroll, style, color, textStyle } = optsif (isNode && style) throw Error("node环境下无法使用style相关属性")if (!isNode && (cursor || move || scroll)) throw Error("window环境下无法使用光标相关属性")if (!isNode && style && (color || textStyle)) console.warn("请注意:样式可能会冲突")}
}

工具实现完毕,来看看下面的功能介绍 

工具的使用说明

工具说明同样可以参照:README.md · 阿宇的编程之旅/js-log-lib - Gitee.com

配置相关

全局配置项

参照 types.ts 中的 IGlobalOpts 类型,全局配置项可以传入以下属性

type IGlobalOpts = Partial<{text: string; // 要打印的文本reset: boolean; // 是否在末尾重置样式type: string; // console的类型split: boolean; // 是否拆分显示,在node中可以在一个console.log中分开显示,比如:console.log("1","2","3")和console.log("123"),前者在字符之间会有空格,split为true表示使用前者显示,反之使用后者,在浏览器环境下只能合并显示color: IColor[] | IColor; // 前(后)景色或其数组,方便传入多个颜色参数,但是有些控制台似乎不兼容textStyle: ITextStyle[] | ITextStyle; // 文本样式或样式数组cursor: ICursor; // 光标控制move: IMoveParams; // 光标移动参数scroll: number; // 滚动条行数style: Partial<CSSStyleDeclaration>; // CSS样式对象,仅支持浏览器环境
}>;

默认配置

在index.ts中defaultOpts是默认配置,当开发者没有设置option时,会使用这里的样式和设置

基本用法

打印字符

const logger = new JSLog();
logger.log({ text: "阿宇的编程之旅" }); // 阿宇的编程之旅

添加全局配置项

全局配置的增加方式有两种,一是在实例化对象时传入构造函数,第二种是通过 mixins 添加

const logger = new JSLog({ type: "error", color: "red" });
logger.log({ text: "我是个错误提示" });
logger.mixins({ type: "info", color: "green" });
logger.log({ text: "我是个成功提示" });

清空所有样式及操作行为

const logger = new JSLog({ type: "error", color: "red" });
logger.log({ text: "阿宇" });
logger.clear();
logger.log({ text: "阿宇" });

校验传入的参数是否正确

const logger = new JSLog();
// node中
logger.checkOptions({ style: {} }); // node环境下无法使用style相关属性

样式控制

Node环境

在node环境下使用color控制文字的前(后)景色,使用textStyle控制文字形态样式,如加粗,斜体等(不同的控制台对样式兼容性不相同,可能会导致冲突或失效问题)

const logger = new JSLog();
logger.log({text: "hello world",color: "cyan", // 字体青色textStyle: "bold", // 加粗
});

 

浏览器中

浏览器环境下也可以使用color,textStyle控制文字样式,但是使用style属性可以支持更多的样式设置,具体可以参考CSSStyleDeclaration类型,如果style与上述的color,textStyle同时设置了,可能会导致样式冲突

logger.log({text: "hello world",style: { color: "lightblue", background: "#333", margin: "10px" },
});

光标控制指令

光标控制只支持在Node环境下使用,和样式调整类似,其原理也是使用ANSI编码在控制台输出对应指令来完成的

const logger = new JSLog();
logger.log({ text: "i m here" });
logger.log({ text: "i m here" });
logger.log({ text: "i m here", cursor: "eraseDisplay" }); // 清除屏幕

光标位置偏移

光标移动与上述的光标指令相同,只不过在指令中传入了移动距离

const logger = new JSLog();
logger.log({move: {direct: "right",position: 5,},text: "编程之旅", // 先打印后面的字符},{move: {direct: "left",position: 14,},text: "阿宇的", // 将前面的字符插入到左边}
);

滚动条控制

借助之前写的TimerManager定时器,或者直接使用setinterval实现一个向上滑动滚动条的效果,每500毫秒打印一次行数

import { TimerManager } from "utils-lib-js";
const logger = new JSLog();
const timerManager = new TimerManager();
let scroll = 0;
const timer = timerManager.add(() => {if (scroll <= -5) return timerManager.delete(timer);scroll--;logger.log({ text: scroll.toString(), scroll });
}, 500);

其他用法

除了上述核心用法之外,JSLog还支持下面的进阶用法

log函数的链式调用

由于log函数返回了当前类,这就使开发者可以直接调用本身的其他函数进行操作

const logger = new JSLog();
logger.log({ text: "hello" }).log({ text: "world" });

同一行打印多种log

const logger = new JSLog({ split: false });
logger.log({ text: "阿宇", color: "red" },{ text: "的编程", color: "brightCyan" },{ text: "之旅", color: "bgBrightMagenta" }
);

属性的指令集

上面我们提到了options可以传入一种指令控制样式或者行为,某些属性支持传入一个数组,批量设置属性

logger.log({text: "hello world",color: ["red", "bgBrightGreen"], // 红色字体,亮绿背景textStyle: ["bold", "italic", "strikethrough"], // 加粗,斜体,删除线
});

ANSI相关的操作

上面我们说到JSLog类继承于ANSI类,所以一些ANSI指令操作在JSLog中也是支持的,下面就举例说说常用的指令函数

ANSI编码指令拼接

通过getANSI和ANSI编码达到控制样式的效果,使用console或者直接使用JSLog直接输出getANSI的转移字符可以设置对应的指令

const logger = new JSLog();
const { getANSI } = logger;
console.log(getANSI(`35`), "hello world"); // 输出紫色的字符串
logger.log({text: getANSI(`34`) + "hello world",
}); // 输出蓝色的字符串

 

颜色设置

使用下面的getColor函数可以设置对应的颜色样式

const logger = new JSLog();
const { getANSI, getColor } = logger;
logger.log({text: getANSI(getColor("bgBlue")) + "hello world",
}); // 输出蓝色背景的字符串

字体样式

使用getTextStyle函数可以设置对应的字体样式

const logger = new JSLog();
const { getANSI, getTextStyle } = logger;
logger.log({text: getANSI(getTextStyle("bold")) + "hello world",
}); // 输出加粗效果的字符串

光标操作

如果你想直接操作光标,不妨直接使用getCursor函数

const logger = new JSLog();
const { getANSI, getCursor } = logger;
logger.log({ text: "----------------------------" },{text: getANSI(getCursor("toLineStart"), false) + "hello world",}
); //移动到行首并输出字符

光标移动

使用moveTo函数可以直接移动光标

const logger = new JSLog();
const { getANSI, moveTo } = logger;
logger.log({ text: "hello" },{text: getANSI(moveTo({ direct: "right", position: 5 }), false) + "world",}
); //向右移动5次光标

滚动条移动

使用scrollTo函数可以移动滚动条

const logger = new JSLog();
const { getANSI, scrollTo } = logger;
logger.log({text: getANSI(scrollTo(-2), false) + "hello world",
}); //向上移动2行滚动条

重置样式

通过运行reset函数可以重置样式,由于jslog默认配置了reset参数,所以样式只会在一个log队列中生效,要取消重置可以在实例化时传入reset: false,这里我直接使用console.log展示效果

const logger = new JSLog();
const { getANSI, getColor, reset } = logger;
console.log(getANSI(getColor("bgBrightGreen")),"i m bgBrightGreen",reset(),"i m reset"
);

ANSI颜色指令的进阶用法

上面说到了前后景色的设置,使用color属性可以对log的字符设置对应的颜色属性,然而由于颜色的指令数有限,有许多其他颜色无法表示,所以ANSI提供了两种设置颜色参数的方式

兼容模式

第一种是256种色调色板的形式,又叫做兼容模式,设置该模式需要将ANSI的第二个参数 mode 设置为5:

const ANSI = `\x1B[38;5;<color>m`;

它支持设置数字0-255:

0-15是标准颜色,也就是之前用到的颜色变量

16-231有216种颜色,这些颜色按照六阶的RGB立方体规则排列,提供了各种颜色的组合

232-255是灰度颜色,这些颜色表示不同灰度级别,从黑到白。232表示最暗的灰色,255表示最亮的灰色。

通过下面的代码我们可以打印出全部256种色阶:

const logger = new JSLog();
const { getANSI, getRGB, reset } = logger;
let color = ``;
for (let i = 0; i < 255; i++) {color += `${getANSI(getRGB({ color: i, mode: "compatibility", type: "background" }))}  `;
}
console.log(color, reset());

 

真彩模式

另一种设置颜色的方式是使用TrueColor(真彩色)模式。在TrueColor模式下,可以通过指定RGB(红绿蓝)值来准确设置颜色,而不仅仅依赖于预定义的颜色索引,就像是css中的rgb方法。该模式需要将ANSI的第二个参数mode设置为2:

const ANSI = `\x1B[38;2;<r>;<g>;<b>m`;

它支持设置数字 256^3 种(16777216 种颜色),在真彩模式下可以通过指定每个颜色通道的具体强度值来准确定义颜色

const logger = new JSLog();
logger.log({ text: "hello", color: { red: 255 } },{text: "阿宇的",color: { green: 255 },},{ text: "编程之旅", color: { blue: 255, type: "background" } }
);

下面是一个使用rgb实现的简易调色板,这里我将步长设置为26

const logger = new JSLog();
const { getANSI, getRGB, reset } = logger;
let color = ``
for (let r = 0; r <= 255; r += 26) {for (let g = 0; g <= 255; g += 26) {for (let b = 0; b <= 255; b += 26) {color += `${getANSI(getRGB({ red: r, green: g, blue: b, mode: "trueColor", type: "background" }))} ${reset()}`}}
}
console.log(color);

光标坐标轴移动

通过配置move参数我们还可以实现指针坐标位移的功能

下面的代码中我们实现了一个斜着输出hello world字符串的功能

import { TimerManager } from "utils-lib-js";
const logger = new JSLog();
const timer = new TimerManager(); // 创建定时器
const str = "hello world";
const position = {row: 0, // 行col: 0, // 列
};
logger.log({ cursor: "eraseDisplay" }); // 清屏
timer.add(() => {const _str = str[position.col];if (!_str) timer.clear();position.col++;position.row++;move(_str);
}, 100);const move = (str) =>logger.log({text: str,move: {direct: "custom",position,},});

写在最后

本篇文章通过ANSI控制码入手,由浅及深的介绍了其用法,在控制台中我们可以使用ANSI来设置字符样式,光标移动等等,接着我实现了一个浏览器和服务端共用的JS工具库,帮助大家更好的操控log以及理解ANSI操作样式的原理。

以上就是文章的全部内容了,感谢你看到了最后,如果觉得文章不错的话,还望三连支持一下!谢谢~

相关代码:

日志工具:js-log-lib: 浏览器和node共用的log样式调整工具,可以自定义输出样式

文中的定时器:https://gitee.com/DieHunter/timer-manager-lib

工具包:utils-lib-js: JavaScript工具函数,封装的一些常用的js函数

以上代码均可以使用(p)npm下载,期待你的建议

主仓库:myCode: 基于js的一些小案例或者项目

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/2130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

消费增值:革新你的消费体验,让每一分钱都更有价值

亲爱的顾客们&#xff0c;你们好&#xff01;今天&#xff0c;我想为大家介绍一种革新性的消费模式——消费增值&#xff0c;它赋予每一次购物以额外的价值&#xff0c;让消费过程变得更加丰富多彩。 过去&#xff0c;我们的消费观念往往是“一手交钱&#xff0c;一手交货”&am…

LCD彩屏显示方案选型攻略:从接口到GUI开发工具的全面评估

在现代人类社会&#xff0c;彩色显示技术是科技王国里最为绚丽夺目的技术奇葩&#xff0c;LCD彩屏通过显示实时信息并提供交互式的体验&#xff0c;将信息时代打扮得多姿多彩。无论是智能家电还是医疗健康设备领域&#xff0c;精美直观的LCD彩屏显示&#xff0c;往往能够为用户…

【Linux学习】初始冯诺漫体系结构

文章目录 认识冯诺依曼系统 认识冯诺依曼系统 什么是冯诺依曼体系结构&#xff1f; 冯诺依曼体系结构是一种将程序指令和数据以二进制形式存放在主存储器中&#xff0c;由中央处理器统一控制和执行的计算机系统结构。冯诺依曼体系结构实现了程序的可编程性和硬件与软件的分离&…

wordpress建网站主题案例推荐

wordpress企业网站主题案例 https://www.mymoban.com/wordpress/ wordpress公司官网主题案例 https://www.wowsoho.com/jianzhan wordpress外贸主题案例 https://www.wpniu.com/moban

用户中心 -- 插件使用 插件使用思路

易错注意点 1 5.1启动类 & 入口类 需保持一致 网址&#xff1a; 第一节课&#xff0c;用户管理--后端初始化&#xff0c;项目调通。二次翻工2-CSDN博客 一、 用户管理 框架 网址&#xff1a; 用户管理 --汇总 -- 明细-CSDN博客 1.2 更改路径&#xff0c;并生效 网址…

基于SpringBoot的“家具销售电商平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“家具销售电商平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 管理员登录界面 管理员功能界面 家具分类管理…

戴尔g15删掉的数据怎么恢复?介绍几种常用方法

随着科技的发展&#xff0c;电脑已成为我们日常生活和工作中不可或缺的一部分。其中&#xff0c;戴尔G15作为一款高性能的笔记本电脑&#xff0c;受到了许多用户的青睐。然而&#xff0c;就像任何电子设备一样&#xff0c;戴尔G15也难免会遇到数据丢失的问题。因此&#xff0c;…

【算法刷题】手撕LRU算法(原理、图解、核心思想)

文章目录 1.LRU算法1.1相关概念1.2图解举例1.3基于HashMap和双向链表实现1.3.1核心思想1.3.2代码解读1.3.3全部代码 1.LRU算法 1.1相关概念 LRU&#xff08;Least Recently Used&#xff0c;最近最久未使用算法&#xff09;&#xff1a; 定义&#xff1a;根据页面调入内存后的…

(一)、SQL进阶——神奇的SQL

一、CASE表达式 1、CASE表达式概述 case表达式有简单case表达式和搜索case表达式两种写法 -- 简单case表达式 case sex when 1 then 男 when 0 then 女 else 其他 end -- 搜索case表达式 case when sex1 then 男 when sex1 then 男 else 其他 end 这两种写法执行的结…

跨平台手机号:微信手机号授权登录、微信授权登录双登录实现账户生态融合,新时代的身份密钥

小程序厂商的多样性体现在开发工具、服务领域、商业模式等多方面&#xff0c;各厂商凭借独特的技术优势、行业解决方案和市场策略&#xff0c;满足不同企业和用户需求。与此同时&#xff0c;随着移动互联网发展&#xff0c;手机号统一登录成为提升用户体验、简化登录流程的关键…

要养生也要时尚,益百分满足你的所有需求

要养生也要时尚&#xff0c;益百分满足你的所有需求 艾灸是个好东西&#xff0c;尤其是在近几年的时候&#xff0c;艾灸就像一阵浪潮席卷进了人们的日常生活之中&#xff0c;我们可以在街边看到大大小小的艾灸馆&#xff0c;有些评价比较高的艾灸馆门前甚至还排起了长长的队伍…

FasterViT:英伟达提出分层注意力,构造高吞吐CNN-ViT混合网络 | ICLR 2024

论文设计了新的CNN-ViT混合神经网络FasterViT&#xff0c;重点关注计算机视觉应用的图像吞吐能力。FasterViT结合CNN的局部特征学习的特性和ViT的全局建模特性&#xff0c;引入分层注意力&#xff08;HAT&#xff09;方法在降低计算成本的同时增加窗口间的交互。在包括分类、对…

【InternLM 实战营第二期笔记】Lagent AgentLego 智能体应用搭建

理论知识 Lagent 是什么 Lagent 是一个轻量级开源智能体框架&#xff0c;旨在让用户可以高效地构建基于大语言模型的智能体。同时它也提供了一些典型工具以增强大语言模型的能力。 Lagent 目前已经支持了包括 AutoGPT、ReAct 等在内的多个经典智能体范式&#xff0c;也支持了…

C语言指针+-整数、指针-指针、指针关系运算、指针和数组、二级指针、指针数组

文章目录 前言一、指针 - 整数二、指针 - 指针三、指针的关系运算四、指针和数组五、二级指针六、指针数组指针数组可以将几个一维数组模拟成二维数组 总结 前言 C语言指针整数、指针-指针、指针关系运算、指针和数组、二级指针、指针数组等介绍&#xff0c;还包括指针数组将几…

武汉大学博士,华为上班5年多,月薪多少。。。

最近&#xff0c;一位来自武汉大学的博士研究生透露了自己在华为公司工作五年后的薪酬情况。 据他透露&#xff0c;他在2018年加入华为时的月薪为2.4万&#xff0c;随着时间的推移&#xff0c;到了2023年&#xff0c;他的月薪已经增长至4.4万&#xff01;此外&#xff0c;他还透…

AI时代的GPU集群网络算力分析

浅谈GPU集群网络、集群规模和集群算力 引言在生成式AI&#xff08;GenAI&#xff09;和大模型时代&#xff0c;不仅需要关注单个GPU卡的算力&#xff0c;更要关注GPU集群的总有效算力。单个GPU卡的有效算力可以通过该卡的峰值算力来测算&#xff0c;例如&#xff0c;对于Nvidia…

网络工程师----第十一天

OSPF&#xff1a; 对称加密算法&#xff1a; 也称为私钥加密或单密钥算法&#xff0c;是一种加密方式&#xff0c;其中加密和解密使用相同的密钥。这种算法的优点包括加密解密速度快、计算量小&#xff0c;适用于大量数据的加密。然而&#xff0c;它的缺点是密钥的安全性难以保…

深度相机(3D相机)

传统的RGB彩色相机称为2D相机&#xff0c; 只能得到2D的图像信息&#xff0c; 无法得到物体与相机的距离信息&#xff0c;也就是深度信息。 顾名思义&#xff0c; 深度相机除了获取2D信息&#xff0c;还能得到深度信息&#xff0c;也叫RGBD相机&#xff0c; 或3D相机。 顺便提…

被删除的照片和视频能找回吗?如何恢复手机删除的照片和视频?

手机里的照片和视频是我们记录生活的每一个瞬间&#xff0c;也是工作学习等场合经常用到的东西&#xff0c;一旦不慎丢失&#xff0c;将对我们造成很大损失。那么我们该如何恢复手机删除的照片和视频呢&#xff1f;通过掌握正确的恢复方法&#xff0c;能够最大程度地保护手机中…

网络常识!!!

网络常识!!! 一:网络的发展史二:关键的概念三:IP地址四:端口号二级目录二级目录二级目录二级目录三级目录 一:网络的发展史 从游戏方面发展历程进行理解: 从单机游戏-----游戏支持局域网对战-------游戏支持广域网对战-------移动端 (1)局域网对战:在同一个网吧里,不同的游戏…