设计元素选取
本来我设想的设计元素是仿niri和TUI。我希望采用卡片设计,平铺的一个个窗口本身就是一个个卡片。niri作为新兴的滚动式平铺布局有较高的热度,所以我选择了仿niri。
但之后在纸鹿大佬的文章中,提到用户有自己习惯的浏览方式,这种自作聪明的方式只会让用户感到不舒服,于是我放弃了仿niri。
::share-card
content: 谈谈不受欢迎的博客技术特征 url: https://blog.zhilu.site/2025/unpopular-blog-tech author: 纸鹿本鹿 website: 纸鹿摸鱼处 img: https://www.zhilu.site/api/avatar.png
::
这是我写了一半的仿niri布局组件,由于不再需要它,暂时搁置了。
我早期是写era basic的, 作为一个写文字黄油的,所以你搞仿Gal交互是干啥的?纯粹吸引人眼球的吗? 我觉得TUI的设计元素更适合我博客的内容。
由于缺乏设计经验,我也不知道怎么设计TUI的风格,导致上个项目写的是个半吊子...
后来找到了WebTUI CSS这个开源项目,借鉴了它的设计,确保了我能够写出来TUI风格。感谢WebTui项目!
因此,我选择了完全拥抱TUI的设计元素,放弃了仿niri的设计元素。
框架选择
作为只会写 Vue 的开发者,Nuxt4是我唯一的选择了。
我之前经验不足对于Nuxt4的刻板印象是只能做ssr,我也没啥服务器做ssr...之后发现ssg也就是ssr渲染进行与渲染,二者没啥区别。
于是我开始了Nuxt4的探索之路。 ssr好香...原来找一个托管商就行了
Nuxt4不仅有高质量的文档,同时也有比较优秀的插件。本博客使用了以下插件。
- color-mode:提供了高度自定义化的暗黑模式和亮色模式的切换功能。
- content: 提供了强大的Markdown解析和内容管理功能,支持多种Markdown扩展语法。
- mdc: 用于在没有Markdown文件的情况下渲染Markdown片段。
- remark-reading-time: 这是nuxt content的一个插件,用于计算Markdown内容的阅读时间。
得益于这些丰富的插件,使我的开发效率得以提升。
样式编写
不要使用过于艳丽的图片
我的博客是基于TUI设计元素的,存在大量的文本/字符堆砌,高文字密度是不可避免的。
过于艳丽的图片本身也是一种高信息密度,会导致用户难以抓住重点,整体看上去非常乱。
避免使用高饱和度颜色
新人画画画白脸都喜欢给脸涂个接近#ffffff的颜色,然后没法画高光了。这里的饱和度也一样。
上来就用高饱和度,不仅无法凸显重点,还会上述的过于艳丽的图片一样,导致整体看上去非常乱。下图就是一个例子
使用oklch调色
oklch是一种基于人类视觉感知的颜色空间,能够更准确地表示颜色的亮度、色相和饱和度。
传统的hsl调色只是相对于机器而言,在某些值的颜色,它根本没有一个定义,导致就算亮度设置为相同的值,也会导致有着亮度差异。
参照上方高饱和度hsl调色调色的图,可以看出绿色是明显比紫色要亮一些的。
使用oklch调色可以解决这个问题,确保不同颜色之间的亮度一致,从而提升整体的视觉体验。
但oklch也不是万能的,没有定义的颜色会回落到周围的颜色,导致颜色有偏差。
下面的网站是oklch的在线调色工具,可以用它来确认可用的区域范围,同时上面也有详细的oklch的使用方法。
::share-card
content: 'OKLCH 颜色选择器和转换器' url: 'https://oklch.net/zh-CN' author: '' website: 'OKLCH 颜色选择器' img: 'https://oklch.net/favicon.ico'
::
我们可以发现,上述的避免使用高饱和度颜色还有个好处,它的颜色定义范围更广。
WebTui的编写
如果没有特殊要求,建议直接使用WebTUI CSS这个开源项目的样式,毕竟它是专门为WebTUI设计的。 我设计早期,由于有透明背景的需要,它的Box标题是依靠填充背景颜色来实现的,不兼容透明背景,于是自己开始瞎折腾了。
绪论
WebTUI的官方主页对WebTui主题的样式设计有详细的说明和示例。
::share-card
content: 'TUIs vs GUIs' url: 'https://webtui.ironclad.sh/start/tuis-vs-guis/' author: 'webtui' website: 'WebTUI' img: 'https://webtui.ironclad.sh/logo.svg'
::
最主要的有两个地方:
- 使用
Monospace字体来保持文本的对齐和一致性。 - 使用
ch,lh基于字体大小的单位来设置元素的宽度和行高。
Tui是基于终端显示的,它最小单元就是一个字符。要想做到Tui的感觉,就不能使用px,mm等单位了。必须使用ch,lh等基于字体大小的单位来设置元素的宽度和行高。
终端由于排版需要,字体都是等宽的,但浏览器上并不是所有字体都是等宽的,我们需要使用Monospace字体,也就是等宽字体。
Monospace每个字符占用相同的水平空间,这样可以确保文本的对齐和一致性。
实用技巧
字体
字体推荐选择Nerd Font,我之前有介绍过,它是专门为终端设计的字体,包含了大量的图标和符号,不需要额外安装图标库里。
突出重点
TUI中字体大小是固定的,无法通过字号来凸显重点。但我们还可以通过饱和度和颜色来进行区分。
方框绘制
善用::before和::after伪元素+border来实现边框。尽管在真正的TUI中,是使用制表字符来实现边框的,但我们可以通过border手动绘制。
只要尺寸看着和一个一个字符组成,视觉效果就和制表字符差不多。
仿Vim选中高亮
点击本博客的任意标题,你可以看到第一个字被选中高亮的效果。这是通过::highlight来实现的。
::highlight是一个可以通过代码选择自定义文字片段来施加额外样式的选择器。
::share-card
content: 'Highlight | Web API | MDN' url: 'https://developer.mozilla.org/zh-CN/docs/Web/API/Highlight' website: 'MDN' img: 'https://developer.mozilla.org/favicon.ico'
::
我的具体实现方式如下: 这也是从WebTUI CSS那里偷来的
onMounted(() => {
watchEffect(applyVimCursorHighlight)
})
function getFirstTextNode(element: Node): Node | undefined {
if (element.nodeType === Node.TEXT_NODE) return element
if (!element.hasChildNodes()) return undefined
for (const child of element.childNodes) {
const textNode = getFirstTextNode(child)
if (textNode?.textContent?.trim()) return textNode
}
return getFirstTextNode(element.firstChild!)
}
function applyVimCursorHighlight() {
if (!selected.value) return
if (!main.value) return
const textNode = getFirstTextNode(main.value)
if (!textNode?.textContent?.trim()) return
const firstNonWhitespace = textNode.textContent.split('').findIndex((c) => !/\s/.test(c)) ?? 0
const range = new Range()
range.setStart(textNode, firstNonWhitespace)
range.setEnd(textNode, firstNonWhitespace + 1)
CSS.highlights.set('vim', new Highlight(range))
}
&.selected > *::highlight(vim)
color: get-color-1()
background-color: get-color-3-r()
仿Vim栏装饰
下边的这个栏是仿VIM的状态栏设计的,使用了::before伪元素 + clip-path来实现的。
通过clip-path来裁剪出一个斜边的效果,来达到和VIM状态栏差不多视觉效果。 这还是抄WebTUI CSS的
结语
当初编写博客时,找Tui风格的网站找了好久。现在写成了,分享下经验,希望能给其他想要写Tui风格博客的人一些启发。
这个博客前前后后折腾了快一个月的时间...中途多次更换框架,还被研究生抓走干活去了,导致开发进度缓慢。现在还有阅读次数,评论区等功能没有做,后续有机会再完善了。 大四真的好忙,放个假还有这么多事!(╥﹏╥)

