一. 前言
在上一章中,我们简单的实现了一下console的自定义和一些默认的方法。有兴趣的可以看上一篇文章在项目中,让你的console.log输出更炫酷。今天把上篇文章中的坑填一下。继续把这个hooks完善一下。

二. 打印图片
原则上来说,console.log只能用于输出文本信息
,不能只能用于输出图片
。但是我们可以通过几种方法实现一下。
2.1 直接引入URL
既然console可以接受css
的样式,那我们就可以通过css在background-image
中塞入一个图片的 URL
, 完成图片的显示。
回到Hooks
文件中,新增一个image
方法:
js
const image = (url: string) => {
const styles = `
background-image: url('${url}');
background-size: 20px;
background-repeat: no-repeat;
padding-left: 25px;
font-weight: bold;
`;
console.log("%c 占位", styles);
}
image: (url: string) => image(url)
在Vue
中引入一张图片,然后使用一下:
js
image('./assets/test.jpeg')

会发现图片并没有在console出来。这个原因是因为我们在占位符号中使用了 本地引用的资源
。
下面是我找到的一些说法,具体应该就是因为浏览器的安全限制
。所以这里我们使用第二种方法,把资源转为 Base64字符
。

2.2 Base64字符
其实原理也简单,就是把image
图片转为base64字符就可以了。步骤就是通过读取image
,然后把image画到canvas
上,最后调用toDataURL
方法就可以了。
js
// 图片url转为dataBase
const imageUrlToDataBase = (url: string): Promise<string> => {
const img = new Image();
// 异步执行的,用promise包裹一下
return new Promise((resolve, reject) => {
img.onload = () => {
const canvas: HTMLCanvasElement = document.createElement('canvas')
const ctx: CanvasRenderingContext2D | null = canvas.getContext('2d');
if (ctx) {
canvas.width = img.width;
canvas.height = img.height;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
const dataUrl = canvas.toDataURL('image/png');
resolve(dataUrl)
}
};
img.onerror = () => {
reject('图片读取失败')
}
img.src = url;
})
}
然后我们回到项目中,在Vue里面使用一下:
js
import imgSrc from './assets/11.jpeg';
image(imgSrc)

打印出来了,很好。
之后我们完善一下image
方法
js
const image = (url: string) => {
imageUrlToDataBase(url).then((res: string) => {
console.log(`%c 占位`, `
background-image: url(${res});
background-repeat: no-repeat;
`)
}).catch((err: string) => {
console.log(err)
})
}
我们去页面上看一下具体的效果:

额。。图片是出来了,但是为什么出现了这么一点点
?
原因是因为,在默认情况下,背景图可能只显示在文本 行高
范围内。也就是说图片的大小只会和你前面占位符的大小
相同。
我做了一下实验,如果在style
里面改变一下font-size
的大小,可以看一下效果:
js
const image = (url: string) => {
imageUrlToDataBase(url).then((res: string) => {
console.log(`%c 占位`, `
background-image: url(${res});
background-repeat: no-repeat;
font-size: 30px;
`)
}).catch((err: string) => {
console.log(err)
})
}

确实,图片变大了。但是尺寸还是有问题,而且可以看到'占位'
两个字还存在。所以这里要对style的样式进行调整一下
。
改变占位字
: 把占位的字变成透明色
。修改图片size
: 修改图片的size
,让图片可以完整展开。预留图片加载位置
要预先定好图片的加载位置。这里可以使用padding
(其实就是用padding把元素撑开
)
让我们重新修改代码。imageUrlToDataBase
这个方法要多reslove出来两个参数,图片的 宽度
和 高度
。
js
resolve({
dataUrl,
width: img.width,
height: img.height
})
然后修改一下image代码:
js
const image = (url: string) => {
imageUrlToDataBase(url).then((res: {dataUrl: string, width: number, height: number}) => {
console.log(`%c 占位`, `
background-image: url(${res.dataUrl});
background-repeat: no-repeat;
background-size: ${res.width}px ${res.height}px;
color: transparent;
padding: ${res.height}px ${res.width}px;
`)
}).catch((err: string) => {
console.log(err)
})
}
最后我们看一下效果。

图片出来了!但是还有个问题。
我用的图片的大小是5472
和3072
的,超过了我控制台可以显示出来的大小。这样就会导致一个问题,图片没有办法完全展示出来
。
所以针对这个问题,我们有必要加一个参数,可以对图片的大小、尺寸进行放大缩小处理
。
2.3 尺寸控制
我的想法是增加一个参数size
。size里面可以设置scale
、width
、height
三个参数。
scale
图片长、宽
改变的比例。width
图片长度
改变的值。height
图片宽度
改变的值。
修改一下image
里面的代码:
js
const image = (url: string, size: {scale?: number, width?: number, height?: number}) => {
const { scale = 1, width = 0, height = 0 } = size
imageUrlToDataBase(url).then((res: {dataUrl: string, w: number, h: number}) => {
const rw = (width ? width : res.w) * scale; // 处理之后的宽
const rh = (height ? height : res.h) * scale; // 处理之后的高
const pt = Math.floor(rh / 2);
const pl = Math.floor(rw / 2);
console.log(`%c 占位`, `
padding: ${pt}px ${pl}px;
background-image: url(${res.dataUrl});
background-repeat: no-repeat;
background-size: ${rw}px ${rh}px;
color: transparent;
`)
}).catch((err: string) => {
console.log(err)
})
}
默认scale为1、width为0, height为0,
。在解构里面处理一下,不传也设置为默认值。 这里padding不能直接设置为width
或者height
宽度。设置一半即可(因为padding是四个方面)
然后我们在页面上使用一下:
js
image(imgSrc, { scale: 0.1 })
这时候图片看起来就舒服很多了。

还有个小小的优化点,如果我设置了宽高
的话,可能会出现图片被压缩
、变形
的情况。
js
image(imgSrc, { scale: 1, width: 400, height: 400 })

这里可以看出来,图片是被拉长
了。这是因为我们设置了background-image
属性导致的,但是我觉得影响不是很大,这里就不做单独的处理了。
最后要注意一点的是,如果图片是一个外部的URL链接
,一定要保证这个图片被允许访问,否则的话就会报错403
。
三. 打印字符画
3.1 什么是字符画
字符画(ASCII Art) 是一种用计算机字符(如字母、数字、符号
)拼凑而成的图像或图案,最早诞生于计算机文本模式时代(如早期终端、电传打字机
)。它通过排列字符的密度、形状和位置来模拟图形效果
,无需依赖图像文件,仅用纯文本
即可呈现视觉内容。
这个其实蛮好玩的,有些网站也会打印出来。比如B站。

前端如何生成字符画
然后在console显示呢?
3.2 String.raw
因为 String.raw
会忽略转义字符,保持原始字符串格式,所以非常适合输出字符画。
可以举个简单的例子:
js
const str = String.raw`\n\n\n/n/n/n`;
console.log(str);

如果我们直接打印\n\n\n/n/n/n
,会看到前面的换行符号被执行了。

但是这里需要用户自己先定义一下字符画,通过String.raw
。想通过变量或者模版字符串的方式实现,会有很多问题。
3.3 动态生成字符画
除了用字符串转为字符画的方式之外,我们还可以通过算法,把图片转为字符画
。新建一个方法AscArt
js
const AscArt = (url: string) => {
const img = new Image();
img.crossOrigin = 'anonymous';
return new Promise((resolve, reject) => {
img.onload = async () => {
const asc = await generateAsciiArt(img)
console.log(asc)
};
img.onerror = () => {
reject('图片读取失败')
}
img.src = url;
})
}
通过图片,然后转为字符画。核心的算法是generateAsciiArt
js
const generateAsciiArt = (img: HTMLImageElement) => {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置画布尺寸(缩小图片以提高字符画清晰度)
const scale = 0.1; // 缩放比例
canvas.width = img.width * scale;
canvas.height = img.height * scale;
if (ctx) {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
const asciiChars = "@%#*+=-:. "; // 从黑到白的字符
let asciiArt = "";
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
const pos = (y * canvas.width + x) * 4;
// 计算灰度值(R+G+B 取平均)
const gray = (pixels[pos] + pixels[pos + 1] + pixels[pos + 2]) / 3;
// 映射到字符
const charIndex = Math.floor((gray / 255) * (asciiChars.length - 1));
asciiArt += asciiChars[charIndex];
}
asciiArt += "\n";
resolve(asciiArt)
}
}
})
}
其实这个算法就是类似灰度算法
,把元素字符内的rgb转换为acs,然后打印出来。
最终结果:

3.4 库
如果实在不想手写,就可以用前端的库来生成。这里推荐几个库供大家使用figlet.js、 ascii-art
四. 结尾
终于写完了。写的比较粗,大家有兴趣的多多提意见吧!!最后放一下完整代码:
js
enum Color {
SUCCESS = '#67C23A',
WARNING = '#E6A23C',
DANGER = '#F56C6C',
NORMAL = '#909399',
PRIMARY = '#409EFF'
}
const success = (title: string, content: string) => {
basePrint(title, content, Color.SUCCESS)
}
const warning = (title: string, content: string) => {
basePrint(title, content, Color.WARNING)
}
const error = (title: string, content: string) => {
basePrint(title, content, Color.DANGER)
}
const normal = (title: string, content: string) => {
basePrint(title, content, Color.NORMAL)
}
const primary = (title: string, content: string) => {
basePrint(title, content, Color.PRIMARY)
}
const basePrint = (title: string, content: string, color: string) => {
const titleStyle = title ? `background:${color}; border:1px solid ${color}; padding: 1px; color: #fff; font-size: 13px` : ''
const contentStyle = content ? `border:1px solid ${color}; padding: 1px; color: ${color}; font-size: 13px` : ''
console.log(`%c ${title} %c ${content}`, `${titleStyle}`, `${contentStyle}`)
}
const matchStrToArray = (str: string) => {
const strArr = str ? str.split(',') : []
const result: {content: string, style: string}[] = []
strArr.forEach((cur: string) => {
const _arr = cur.split('&');
const content = _arr.length > 0 ? _arr[0].split('=').length > 0 ? _arr[0].split('=')[1] : '' : ''
const style = _arr.length > 1 ? _arr[1]!.match(/\{([^{}]*)\}/g)!.map((match: string) => match.slice(1, -1))[0] : ''
result.push({
content, style
})
})
return result
}
const arrayToPrintContent = (array: {content: string, style: string}[]): {content: string[], color: string} => {
let contentString: string[] = []
let colorString = ''
array.forEach(item => {
colorString = `${colorString} ` + `%c ${item.content}`
contentString.push(item.style)
})
return {
content: contentString,
color: colorString
}
}
const custom = (express: {content: string, style: string}[] | string) => {
let strArr: {content: string, style: string}[]
if (typeof express === 'string') {
const res = matchStrToArray(express)
strArr = res
}
else {
strArr = express
}
const {content, color} = arrayToPrintContent(strArr)
console.log(color, ...content) // 这里用扩展运算符把参数展开
}
const complicated = (express: any[] | {}) => {
if (Array.isArray(express)) {
console.table(express)
}
else {
const customArray = Object.entries(express).map(([key, value]) => ({
id: key,
value: value,
}));
console.table(customArray)
}
}
const image = (url: string, size?: {scale?: number, width?: number, height?: number}) => {
const { scale = 1, width = 0, height = 0 } = size ? size : { scale: 1, width: 0, height: 0}
imageUrlToDataBase(url).then((res: {dataUrl: string, w: number, h: number}) => {
const rw = (width ? width : res.w) * scale;
const rh = (height ? height : res.h) * scale;
const pt = Math.floor(rh / 2);
const pl = Math.floor(rw / 2);
console.log(rw)
console.log(rh)
console.log(`%c 占位`, `
padding: ${pt}px ${pl}px;
background-image: url(${res.dataUrl});
background-repeat: no-repeat;
background-size: ${rw}px ${rh}px;
color: transparent;
`)
}).catch((err: string) => {
console.log(err)
})
}
const imageUrlToDataBase = (url: string): Promise<{dataUrl: string, w: number, h: number}> => {
const img = new Image();
img.crossOrigin = 'anonymous';
return new Promise((resolve, reject) => {
img.onload = () => {
const canvas: HTMLCanvasElement = document.createElement('canvas')
const ctx: CanvasRenderingContext2D | null = canvas.getContext('2d');
if (ctx) {
canvas.width = img.width;
canvas.height = img.height;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
const dataUrl = canvas.toDataURL('image/png');
resolve({
dataUrl,
w: img.width,
h: img.height
})
}
};
img.onerror = () => {
reject('图片读取失败')
}
img.src = url;
})
}
const isEnv = () => {
return import.meta.env.MODE === 'production'
}
const generateAsciiArt = (img: HTMLImageElement) => {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置画布尺寸(缩小图片以提高字符画清晰度)
const scale = 0.1; // 缩放比例
canvas.width = img.width * scale;
canvas.height = img.height * scale;
if (ctx) {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
const asciiChars = "@%#*+=-:. "; // 从黑到白的字符
let asciiArt = "";
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
const pos = (y * canvas.width + x) * 4;
// 计算灰度值(R+G+B 取平均)
const gray = (pixels[pos] + pixels[pos + 1] + pixels[pos + 2]) / 3;
// 映射到字符
const charIndex = Math.floor((gray / 255) * (asciiChars.length - 1));
asciiArt += asciiChars[charIndex];
}
asciiArt += "\n";
resolve(asciiArt)
}
}
})
}
const AscArt = (url: string) => {
const img = new Image();
img.crossOrigin = 'anonymous';
return new Promise((resolve, reject) => {
img.onload = async () => {
const asc = await generateAsciiArt(img)
console.log(asc)
};
img.onerror = () => {
reject('图片读取失败')
}
img.src = url;
})
}
export const consoleExtension = () => {
return {
success: (title: string, content: string) => success(title, content),
warning: (title: string, content: string) => warning(title, content),
error: (title: string, content: string) => error(title, content),
normal: (title: string, content: string) => normal(title, content),
primary: (title: string, content: string) => primary(title, content),
custom: (array: {content: string, style: string}[] | string) => custom(array),
complicated: (express: any[] | {}) => complicated(express),
image: (url: string, size?: {scale?: number | undefined, width?: number | undefined, height?: number | undefined}) => image(url, size),
AscArt: (url: string) => AscArt(url),
}
}