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

news2024/11/23 12:57:22

目录

前言

基础知识

进阶实践

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.down
        if (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 } = options
        const 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 | number
    green?: string | number
    blue?: string | number
    type?: IColorType // 字体颜色或背景颜色
    mode?: IColorMode
    color?: 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"; // 浏览器环境下字符串占位符:%c
    isNode: 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 this
        const { logQueue, styleQueue } = this.createQueue(args)
        this.logFn(logQueue, styleQueue)
        return this
    }
    // 生成日志队列
    private createQueue = (args: IOpts[]) => {
        const styleQueue = [], logQueue: string[] = []
        const { isNode, stylePlaceholder, globalOpts } = this
        args.forEach(it => {
            this.checkOptions(it)
            const { text = "" } = it
            const _opts = emptyObject({ ...globalOpts, ...it })
            const { reset, style = {} } = _opts
            const 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 } = this
        const { type } = globalOpts
        if (typeof logQueue === "string") {
            return console[type](logQueue, ...styleQueue)
        }
        return console[type](...logQueue, ...styleQueue)
    }
    // 校验options参数
    checkOptions(opts: IGlobalOpts = {}) {
        const { isNode } = this
        const { cursor, move, scroll, style, color, textStyle } = opts
        if (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.coloradmin.cn/o/1617406.html

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

相关文章

Pytorch:张量的梯度计算

目录 一、自动微分简单介绍1、基本原理2、梯度计算过程3、示例&#xff1a;基于 PyTorch 的自动微分a.示例详解b.梯度计算过程c.可视化计算图 4、总结 二、为什么要计算损失&#xff0c;为何权重更新是对的&#xff1f;1、梯度下降数学原理2、梯度上升 三、在模型中使用自动微分…

力扣HOT100 - 199. 二叉树的右视图

解题思路&#xff1a; 相当于层序遍历&#xff0c;然后取每一层的最后一个节点。 class Solution {public List<Integer> rightSideView(TreeNode root) {if (root null) return new ArrayList<Integer>();Queue<TreeNode> queue new LinkedList<>…

element中file-upload组件的提示‘按delete键可删除’,怎么去掉?

问题描述 element中file-upload组件会出现这种提示‘按delete键可删除’ 解决方案&#xff1a; 这是因为使用file-upload组件时自带的提示会盖住上传的文件名&#xff0c;修改一下自带的样式即可 ::v-deep .el-upload-list__item.is-success.focusing .el-icon-close-tip {d…

vue 关键字变红

1.html <div v-html"replaceKeywordColor(item.title)" ></div> 2.js //value为搜索框内绑定的值 replaceKeywordColor(val) {if (val?.includes(this.value) && this.value ! ) {return val.replace(this.value,<font color"red&…

PyCharm 中的特殊标记

再使用 PyCharm 开发 Python 项目的时候&#xff0c;经常会有一些特殊的标记&#xff0c;有些是编辑器提示的代码规范&#xff0c;有些则为了方便查找而自定义的标记。 我在之前写过一些关于异常捕获的文章&#xff1a;Python3 PyCharm 捕获异常报 Too broad exception clause…

【电控笔记5.8】数字滤波器设计流程频域特性

数字滤波器设计流程&频域特性 2HZ : w=2pi2=12.56 wc=2*pi*5; Ts=0.001; tf_lpf =

块存储、文件存储、对象存储概念与区别

1. 块存储 块存储是将数据切分成固定大小的块&#xff0c;然后将这些块存储在物理设备&#xff08;如硬盘、固态硬盘&#xff09;上。每个块都有唯一的标识符&#xff0c;并且可以独立地被读取、写入或删除。块存储通常用于存储文件系统&#xff0c;例如操作系统的文件系统&am…

牛客周赛 Round 40(A,B,C,D,E,F)

比赛链接 官方讲解 这场简单&#xff0c;没考什么算法&#xff0c;感觉有点水。D是个分组01背包&#xff0c;01背包的一点小拓展&#xff0c;没写过的可以看看&#xff0c;这个分类以及这个题目本身都是很板的。E感觉就是排名放高了导致没人敢写&#xff0c;本质上是个找规律…

群辉安装python3教程

目录 群辉安装python3一、需求二、群辉套件安装python3三、ssh连接群辉&#xff08;一&#xff09;finshell连接群辉&#xff0c;root登录&#xff08;二&#xff09;安装pip3库&#xff08;三&#xff09;配置环境变量 四、测试 群辉安装python3 一、需求 需求&#xff1a;语…

【目标检测】YOLO系列-YOLOv1 理论基础 通俗易懂

为方便大家理解YOLO的原理&#xff0c;这里将YOLOv1的部分内容基础内容进行用比较直白的话和例子进行阐述&#xff0c;为后续大家学习YOLO作为铺垫。 1、模型所干的活 工作中&#xff0c;大家经常将 Word 文档 上传到某转换器&#xff0c;然后转换输出为PDF文档。目标检测中我…

认识rust中闻风丧胆生的命周期,不用过于担心,它对于所有人都是平等的

生命周期&#xff0c;简而言之就是引用的有效作用域。在大多数时候&#xff0c;我们无需手动的声明生命周期&#xff0c;因为编译器可以自动进行推导&#xff0c;用类型来类比下&#xff1a; 就像编译器大部分时候可以自动推导类型 <-> 一样&#xff0c;编译器大多数时候…

Rust Tracing 入门

Tracing 是一个强大的工具&#xff0c;开发人员可以使用它来了解代码的行为、识别性能瓶颈和调试问题。 Rust 是一种以其性能和安全保证而闻名的语言&#xff0c;在它的世界中&#xff0c;跟踪在确保应用程序平稳高效运行方面发挥着至关重要的作用。 在本文中探讨Tracing 的概…

4K Video Downloader v4.30.0.5644 一款专业级的4K视频下载器

4K Video Downloader 中文破姐版 本站所有素材均来自于互联网&#xff0c;版权属原著所有&#xff0c;如有需要请购买正版。如有侵权&#xff0c;请联系我们立即删除。

Multiscale Vision Transformers

1、引言 论文链接&#xff1a;https://arxiv.org/abs/2104.11227 Haoqi Fan[1] 等通过在 ViT[2] 中引入多尺度特征层次结构&#xff0c;提出了一种用于视频和图像识别的 Multiscale Vision Transformers(MViT)[1]。在视频识别任务中&#xff0c;它优于依赖大规模外部预训练的视…

react 基础学习笔记一

1、jsx语法过程 jsx使用react构造组件&#xff0c;通过bable进行编译成js对象&#xff0c;在用ReactDom.render()渲染成DOM元素&#xff0c;最后再插入页面的过程。 2、创建组件 组件的定义&#xff1a;将公用的代码组装成一个独立的文件&#xff0c;保持代码独立性&#xff0…

【QT学习】9.绘图,三种贴图,贴图的转换

一。绘图的解释 Qt 中提供了强大的 2D 绘图系统&#xff0c;可以使用相同的 API 在屏幕和绘图设备上进行绘制&#xff0c;它主要基于QPainter、QPaintDevice 和 QPaintEngine 这三个类。 QPainter 用于执行绘图操作&#xff0c;其提供的 API 在 GUI 或 QImage、QOpenGLPaintDev…

使用VPN后,浏览器访问不了国内地址解决办法

winR输入regedit 打开注册表 找到路径 计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings删除两个proxy代理

【Android】 四大组件详解之广播接收器、内容提供器

目录 前言广播机制简介系统广播动态注册实现监听网络变化静态注册实现开机自启动 自定义广播发送标准广播发送有序广播 本地广播 内容提供器简介运行时权限访问其他程序中的数据ContentResolver的基本用法读取系统联系人 创建自己的内容提供器创建内容提供器的步骤 跨程序数据共…

JavaSE:抽象

一&#xff0c;抽象是什么&#xff0c;抽象和面向对象有什么关系 抽象&#xff0c;个人理解&#xff0c;就是抽象的意思 我们都知道面向对象的四大特征&#xff1a;封装&#xff0c;继承&#xff0c;多态&#xff0c;抽象 为什么抽象是面向对象的特征之一&#xff0c;抽象和面…

Aigtek功率放大器电路的主要作用是什么

功率放大器是电子电路中的一个重要组成部分&#xff0c;它的主要作用是将输入信号的能量放大到更大的幅度&#xff0c;以便驱动负载或传输信号。功率放大器广泛应用于各种领域&#xff0c;如音频放大器、射频放大器、通信设备、无线电设备等。下面我们将详细介绍功率放大器电路…