记一次前端文本对齐的问题
前段时间处理了一个在网页中文本对齐的问题,发现了一些之前关于字体未曾了解的知识点,颇有意思,总结一下。
<!--more-->
1. 背景
业务中需要在网页中展示
pandas
读取excel后的输出内容
import pandas as pd
import sys
pd.set_option('display.unicode.ambiguous_as_wide', True)
pd.set_option('display.unicode.east_asian_width', True)
data = pd.read_excel("./销售订单.xlsx", sheet_name="订单数据")
# sys.stdout = open("1.log", "w") # 测试输出重定向
print(data)
控制台打印的效果十分完美
在浏览器中使用
pre
标签展示输出内容时,却发现文本完全没有像控制台那样对齐
下面是原始输出内容
<pre> 订单号 商品ID 商品名 品牌 类别 规格 单价 数量 总价 下单时间
0 98232019040002 700009 芹菜味薯片 开口哭牌 零食 200克 20 2 40 2019-04-01 08:00:15
1 98232019040003 700006 没有一点味口香糖 开口哭牌 零食 100克 15 2 30 2019-04-01 08:45:10
2 98232019040004 700013 火龙果可乐 君再来牌 饮料 550毫升 5 5 25 2019-04-01 10:03:38
3 98232019040005 700003 芹菜味口香糖 开口哭牌 零食 100克 15 1 15 2019-04-01 10:58:03
4 98232019040006 700009 芹菜味薯片 开口哭牌 零食 200克 20 1 20 2019-04-01 11:35:19
.. ... ... ... ... ... ... ... ... ... ...
300 98232019040302 700011 蟹黄味薯片 开口哭牌 零食 200克 20 5 100 2019-04-30 13:29:20
301 98232019040303 700016 夏夜雨可乐 君再来牌 饮料 550毫升 5 1 5 2019-04-30 14:05:13
302 98232019040304 700015 青草味可乐 君再来牌 饮料 550毫升 5 1 5 2019-04-30 14:45:06
303 98232019040305 700015 青草味可乐 君再来牌 饮料 550毫升 5 1 5 2019-04-30 17:46:43
304 98232019040306 700005 蟹黄味口香糖 开口哭牌 零食 100克 15 2 30 2019-04-30 22:07:13
[305 rows x 10 columns]</pre>
最开始以为是在复制文本时导致空格被合并了,因此使用
sys.stdout
将输出重定向到文本中,然后使用VSCode打开,发现居然也是错乱的
2. 使用严格半角的字体
经过非常严格和认真的对比,我发现这些文本是通过填充不同的空格进行对齐的,换言之,如果需要对齐,字体需要满足下面的条件
- 英文字体等宽,且与一个空格的宽度相等
- 中文字体等宽
- 一个中文字符等于两个空格的宽度
这里需要配置符合下面要求的严格半角字体,参考:
在第二个回答中找到了Mac系统自带的一种字体
PCMyungjo
,试了一下
pre {
font-family: PCMyungjo;
}
尽管部分标签符号和汉字看起来有点奇怪,居然能满足要求!!而这也仅仅需要一行简单的CSS代码。
当然,随之而来的就是兼容性问题,并不能保证所以机器上都安装了该字体,且该字体并不能通过UI那关,因此尝试去寻找了一些其他符合条件的字体。
后来发现如
SimHei
等黑体也可以满足条件,且汉字展示要美观得多
@font-face {
font-family: "SimHei";
src: url("SimHei.ttf") format("truetype");
font-weight: normal;
font-style: normal;
font-display: swap;
pre {
font-family: SimHei;
}
中文自定义字体的缺点在于体积往往会非常大
因此还需要寻找其他不依赖于特定字体的其他方案。
这里补充一下关于字体的一些知识
3. 等宽字体
参考: 等宽字体 - 维基百科
等宽字体(英语:Monospaced Font)是指字符宽度相同的电脑字体。与此相对,字符宽度不尽相同的电脑字体称为比例字体。
由于早期打字机和显示器等技术局限,字符一般也是等宽的。在传统西文印刷中,比例字体可以提高单词的可读性。目前由于技术突破,比例字体的使用也比较普及
大部分程序员选择的代码字体一般都是等宽的,等宽字体在处理缩进对齐、统一字符间距等方面更占优势;此外,东亚字体中的方块字基本上都作为等宽字体处理。
4. 全角半角字体
参考:
主要原因是 符号冲突
比如英文逗号","与中文逗号",",用眼睛就可以看出长度与大小是不一样的。当在键盘上输入逗号时,中文输入法不确定你想要的是哪种逗号(中/英),所以就提供了全角半角模式,英文半角输出英文逗号,其它模式就是中文逗号,这样,我们用一种输入法就能打出两种符号,而不用切换成其它输入法
5. 控制每个中文字符的宽度
由于VSCode编辑框与终端默认配置的是相同的字体,因此编辑框和终端展示结果不一致应该不是字体的问题。那为啥终端会展示完全对齐的效果呢?
后来发现了一个类似的issue: Print data.frame with Chinese strings column aligned
其中提到了一个解决办法是手动控制设置每个中文字符的宽度~咋一看貌似挺不靠谱,转念一想:咦?貌似值得一试
function getTextSize(parent, ch = 'a') {
const span = document.createElement('span')
const result = {}
result.width = span.offsetWidth
result.height = span.offsetHeight
span.style.visibility = 'hidden'
// span.style.fontSize = fontSize
// span.style.fontFamily = fontFamily
span.style.display = 'inline-block'
parent.appendChild(span) // 使用继承自父元素的字体样式
if (typeof span.textContent !== 'undefined') {
span.textContent = ch
} else {
span.innerText = ch
result.width = parseFloat(window.getComputedStyle(span).width) - result.width
result.height = parseFloat(window.getComputedStyle(span).height) - result.height
parent.removeChild(span)
return result
}
然后控制每个字符的宽度,为了减少内联style导致HTML内容过于复杂,可以使用CSS变量
let preDom = document.querySelector('#pre')
let preTextSize = getTextSize(preDom)
preDom.setAttribute("style", `--char-width:${preTextSize.width}px`);
然后将所有的中文字符添加一个特定的样式类
char-cn
.char-cn {
font-family: monospace;
display: inline-block;
text-align: center;
width: calc(var(--char-width) * 2);
}
最后将pre标签内容中的所有中文字符添加
char-cn
类,同时替换内容
function replaceChineseChar(str) {
const re = /[\u4e00-\u9fa5]/gi