有些话,得在没人看的时候才说得出口。
做这个页面的时候正好是夏天。那天我加班到后半夜,楼下烧烤摊都收了摊,马路对面只剩一盏路灯,照着空荡荡的街。我把站里那些旧文章一篇篇点开——版权那篇,动效那篇,还有几篇缩在角落里的技术心得。它们散得像一个没人收拾的书房,书脊上落了灰,读者找到一本算一本。我忽然想,是不是该给它们一个像样的书架。
那晚我想得很多。想这个站为什么存在,想那些半夜还在翻图的人。后来我在便签上写了一行字:做这个页面,不是给搜索引擎看的,是给那些真正愿意停下来读一会儿的人看的。
念头这种东西,起来的时候很轻,落地却很重。之后的两个星期,我几乎全搭在这个页面上了。
第二天我打开 Figma,准备大干一场。然后盯着那块空白画布,坐了差不多十分钟,什么都没有画出来。
▲ 图:对着空白画布翻了三分钟白眼——不是不想画,是真不知道从哪儿下手
设计这东西,难的从来不是动手,是不知道该往哪儿动。空白的画布最吓人——你心里有无数个方向,每一个都长得像终点,于是哪个都不敢先走。
那十分钟里,我的脑子不是空的,是吵的。左边一个声音说,要简洁,学学那些大厂;右边一个声音说,我们是小站,得做出点自己的味道;中间还有一个声音在劝,别做了,套个模板算了,反正没人细看。我盯着那块白,忽然想明白一件事:真正拦住我的不是不会设计,是我还没想清楚,这个页面到底是做给谁看的。
给谁看,这三个字比任何配色方案都重要。想清楚了给谁看,后面一半的答案,自己就出来了。
我后来想明白一件事:设计不是从像素开始的,是从一个念头开始的。念头落到纸上,就是几个词。我给这个页面定的词不多:
圆角这件事,我忍不住多啰嗦两句。以前的网页爱用直角,干脆利落,那是从印刷品那儿学的。可屏幕不是纸,屏幕上的字和色块,都是发光的光点堆起来的。直角搁在这种介质上,总显得有点生硬,像两个谁也不肯先低头的陌生人。圆角不是可爱,是谦和,它让人愿意多看一眼。这两年那些让人觉得“高级”的产品,翻来覆去,无非就是圆角、留白、统一的曲线、克制的颜色这几样。
这几个词,后来成了我的一把尺子。每次纠结“这个要不要”,我就拿它量一下,量完,答案多半自己就浮出来了。尺子这东西,平时用不上,可真到抉择的关口,它是唯一能让你不后悔的东西。你也许觉得这小题大做——做个小页面,至于吗?可恰恰是这种没人管的小页面,最容易长成一堆功能的乱炖。没有尺子,你会什么都想留。
写完词,我又写了一份功能清单。是那种写给自己的、像备忘录一样的东西:一个让人三秒看懂这里是哪的头;能按版权、设计、性能、前端分门别类的筛选;文章卡片;还有详情页里不能少的目录、代码复制、表格、公式;末尾是上一篇、下一篇,和一条版权声明。
功能和设计的关系,说穿了是先后关系。功能都没定,就去抠像素,是最容易白干的一件事——我见过太多人先画了一个惊艳的登录页,然后发现业务根本没这需求,白高兴一场。
列功能的时候我还有个诀窍:每个功能都得能回答“读者用它解决什么问题”。答不出来的,先记下,不急着删,但心里给它标个待定。等做完回头一看,待定的那些,多半都被划掉了。
举个真实的例子。做这页的时候,我很想塞一个“词云”,把文章里的高频词堆成一片彩色的云,配着动画飘。好看,太好看,我连它放哪儿都想好了。可拿尺子一量——动,但要克制;内容优先——它跟这两条都对不上。词云是个装饰,不是导航,读者不会靠它找到任何东西。我恋恋不舍地把它从清单上划掉了。后来页面上了线,没人发现它不在。你看,你以为重要的东西,读者根本不知道它存在过。
▲ 图:两星期后,这个页面长成了这样——那晚便签上的一句话,终于有了落脚的地方
接下来这一步,说出来可能有点不务正业:我让 AI 给我出初稿。
那天晚上我对着聊天框,把那些关键词翻成它听得懂的话——移动优先、圆角卡片、玻璃拟态、冷色调、留白。它倒是麻利,几十秒就吐给我好几版。
第一眼是惊艳的。那几版图,构图漂亮,配色舒服,一眼看过去,就是那种“像个像样的知识库”的模样。我当时心里一喜:成了。
然后我放大看。间距乱了,中文全是乱码,图标这儿一个那儿一个,像谁喝醉了撒了一桌。一瞬间从山顶掉到山脚,气得我差点把窗口关了。
▲ 图:把 AI 初稿放大细看——间距全乱、中文全是乱码,当场捂脸
可气归气,冷静下来,我又盯着它们看了很久。AI 画的图,其实很像人做的梦——梦里的场景再真切,醒来也经不起推敲,可它偏偏能告诉你,你心里想要的那东西,大概长什么模样。“Hero 左边是话,右边浮着几块透光的板子”——这个方向,就是从那几版梦里抓出来的。后来我在 Figma 里,照着这个“模样”画,而不是照着那张图描。
还有个小细节:它天生不懂什么叫留白,你让它做设计,它恨不得把每一寸都塞满,你得一遍遍说“再空一点、再空一点”,说到它终于肯留出一片干净的角落。那个过程,很像我小时候练字,先生逼着留天留地,说字要写在格子的中央,四周空出来。那时候不懂,后来才明白:留白不是浪费,是让该被看见的东西,真的被看见。
它只负责做梦,醒来画图的是我。这句话我记了很久。
后来我又让它出了几版,把我相中的那个方向喂给它,让它往细里再走一步。它交出来的东西仍然不能直接用,但有些局部——某块卡片的比例、某处留白的节奏——忽然就对了。我像个在沙地里捡石头的人,从它那堆半成品里捡出几颗能用的,再一块块砌到自己墙上。
后来有同事问我,AI 到底能不能直接出设计稿。我说能,但前提是你得先知道自己想要什么——它不会替你决定,它只会替你想象。你不知道方向的时候,它给你的,就只是一堆漂亮的废纸。
Figma 里,我把那个梦变成一张像样的图纸。说是图纸,其实薄薄一层,没什么玄的:
所谓设计系统,听着大得吓人,落到实处不过是一张表格加上一条纪律——写代码的时候,颜色不许写死,一律从变量里取。表大概长这样:
| 变量 | 亮色 | 暗色 |
|---|---|---|
| --background | #ffffff | #1a1a1a |
| --text-primary | #3e3f3f | #ffffff |
| --text-secondary | #666666 | #b3b3b3 |
| --border-color | rgba(200,190,180,0.22) | rgba(255,255,255,0.1) |
| --card-background | #ffffff | #2d2d2d |
所以后来这个页面的每一处颜色,都是 var(--xxx) 的模样。暗色模式想换,html 上加一个类,整组变量换掉,页面就跟着变,组件代码一行都不用动。这大概是我在这页里做过的最划算的决定。颜色这东西,最难的是克制。一支笔,两种墨,写出来的字反而干净;颜色一多,页面就吵了。
真正把圆角用出滋味的地方,是那排筛选的胶囊和一张张文章卡片。胶囊全圆角,选中的那一粒直接反色,底色变文字、文字变背景,干干净净;卡片 24px 的圆角,悬停时轻轻浮起三像素,阴影跟着淡开。都是些一句话能说完的小样式,可它们凑在一起,整页就有了那点“现代”的体面。你别小看体面这两个字,人对一个地方是否认真,第一眼就下判断了。
▲ 图:筛选胶囊和文章卡片——“圆角现代感”在页面里的日常落点
在这里我还得诚实地说一句:Figma 里怎么排都好看,因为你不用管内容真有 1200px 宽,不用管暗色模式,不用管真实文章标题能有多长。设计稿永远比实现好看,这是行业的真理。所以我给自己定的规矩是:Figma 负责把方向定对,具体像素在浏览器里微调,绝不在它里面死磕——那是时间黑洞。
可即便如此,第一版写出来,我还是对着屏幕愣了一会儿——Figma 里那排胶囊,到了浏览器里忽然又肥又挤;标题一长,卡片就换行,像穿错了码的衣服。设计稿是给完美内容穿的,而现实里的内容,从来不肯好好穿。从那天起我就明白:前端这件事,一半是画图,一半是给图补衣料。
暗色模式调色那天,我关了大灯,把屏幕调到最暗,盯着它看了很久。深色的页面上,圆角的卡片像浮在夜里的一扇扇小窗,一格一格透着光。那种感觉说不上来,就像深夜点开一个还算安静的地方,愿意多待一会儿。
图纸定完,真正的麻烦才开始——把图纸变成代码。
Hero 区那三块玻璃板,在 Figma 里就是三块漂亮的色块,到了浏览器里,它得让自己“浮”起来。三个绝对定位的 div,父容器给一个透视相机,三块板用同一个旋转角度,只在远近上拉开一点,再用 backdrop-filter 磨出玻璃的质感:
.glass-plate {
position: absolute;
width: 170px;
height: 215px;
border-radius: 22px;
backdrop-filter: blur(20px) saturate(190%);
-webkit-backdrop-filter: blur(20px) saturate(190%);
transform-style: preserve-3d;
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
三块板共用同一个旋转角度,只在 translateZ 和 translateX 上拉开远近,看起来才像同一摞卡片被错开摆着,而不是三张贴纸各过各的。鼠标移上去,当前那块会“翻正”、放大、提到最前面,其余的暗下去——一个编号记在状态里,:class 里一判断,就两行代码的事。
就这么一段,我调了一整个下午。Safari 不认不带前缀的 backdrop-filter,好,补上 -webkit-;补上了,又说元素没有背景时滤镜不生效,好,再垫一层半透明的渐变;等它终于在电脑上也“玻璃”起来,我一高兴,拿低端安卓一测——掉帧,糊,拖泥带水。
▲ 图:Safari 要前缀、Chrome 说没背景、安卓嫌它卡——一个滤镜,三个浏览器,把我磨得手忙脚乱
那是我第一次想删掉它。
可它是我第一眼看中的东西,实在舍不得。删之前,我把它单独拎出来,在桌面端反复确认它好看,在移动端反复确认它添乱。然后我想起那把尺子:手机优先。
于是那条媒体查询里,多了一行不起眼的 display: none。玻璃板从此只在宽屏上现身,手机上干干净净,一行多余的装饰都没有。
有些东西不是不好,只是站错了地方。明白这个,比会写 backdrop-filter 难得多。
回头算账,这三块玻璃板,我在它身上搭了一个下午加一个晚上,论投入产出,简直亏到姥姥家。可有些账不能这么算——你为一样东西费过心,那股手感、那种直觉,会偷偷长到别的东西上去。这大概也是磨刀的另一种意义。
其实到今天,我也说不清这三块玻璃板到底有什么用。它不导航,不传达信息,纯粹是个装饰。可我总觉得,一个知识库,总得有一点点“脸面”,让人第一眼觉得:这是个被认真对待的地方。装饰和噪音的界限就在这儿——它是让你觉得这里用心,而不是让你分心。
详情页的故事,是从一个很具体的时刻开始的。
那天我在手机上看自己写的一篇长文,翻到三分之二,想回头找一句开头的话。没有目录,只能一寸一寸往上滑,滑过了,还得再滑回来。屏幕亮着,我大拇指在屏幕上徒劳地来回,忽然就烦了。
▲ 图:翻到三分之二想回头找一句话,滑来滑去找不到——当场失败
长文章没有目录,是对读者的一种辜负。我决定补上。
桌面端简单,左边挂一个固定的目录栏,正文里每个标题都在里面占一行,滚动到哪一章,哪一章就高亮。高亮不是靠什么魔法,就是一个滚动监听,从后往前找“第一个已经滚过头顶的标题”:
const handleScroll = () => {
const scrollPosition = window.pageYOffset + 120
for (let i = headings.length - 1; i >= 0; i--) {
const el = headings[i]
if (el.offsetTop <= scrollPosition) {
activeTocId.value = el.id
break
}
}
}
目录不是手写的,是从正文里长出来的:解析正文的时候,每个 h2、h3 都会被记下来,按顺序排成目录,顺手给每个标题挂一个自增的 id,跳转就靠它。这样目录永远不会和正文对不上——它们本来就是一个东西的两面。
▲ 图:详情页的智能目录——左侧固定,跟着滚动高亮
真正费思量的是手机。屏就这么宽,挂不下目录。我试过把它塞进顶部,试过让它从中间蹦出来当弹层,都不对劲。弹层是打断——你正读着书,忽然跳出来一个框,让你先放下书处理它,这个动作本身就伤阅读的心情。
抽屉不一样。抽屉是从屏幕边缘拉出来的,它懂你还在读书,只是想瞥一眼目录,看完就收回去。它不打断,它只是在你手边,递了个东西过来。这样一个 0.25 秒的滑动,从 -100% 到 0,就安放了一个想翻回目录的人:
@keyframes slideInLeft {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
点遮罩关闭,点面板里面不关闭,点完目录自动收起。这段动画我没用任何库,一个 transform,一个缓动,没了。
▲ 图:抽屉从屏幕边缘滑出来的那一下——不是“好看”,是“对了”
移动端不是桌面端的缩小版。这句话我写过不知道多少遍,直到这个抽屉做出来,我才真正懂了它的意思。
▲ 图:移动端的目录抽屉——从屏幕边缘滑出,点完自动收起
做抽屉那几天,我总想起小时候去书店,架子上层够不着,就搬个凳子。凳子不显眼,也没人夸,可没有它,那本书就永远够不着。目录对读者来说,大概就是那把凳子。设计做得再好,也不过是让那把凳子好找一点、好放一点。
做完那天,我截了个屏发给一个做产品的朋友,问他感觉怎么样。他回我四个字:像那么回事。这四个字我高兴了一晚上——一个阅产品无数的挑剔的人,肯说“像那么回事”,比说“很好看”还难得。
页脚还有两样不起眼的东西:上一篇、下一篇,和一条版权声明。上一篇是给读者一条路——读完了,顺着手就往下走;版权声明是给读者一份安心——这里的东西谁写的、能不能用、怎么用,都摆在明面上。一个页面要让人觉得稳当,靠的往往就是这些藏在角落里的体面。
详情页再往下,是正文怎么渲染。这里我栽的跟头最多,也最见不得人。
一开始我偷懒,想用现成的 markdown 库。可这站的文章不只是 markdown——有公式,有表格,有要实时演示的交互块,还有一堆动图。要么把库拆了重装,要么自己写一个。我选了后者,然后一脚踩进一个没底的坑。其实我一开始试过现成库,bytemd、markdown-it,都装过。配置到一半就放弃了——公式要引一堆插件,表格样式得重写,演示块更是无从谈起。改别人家的库,就像在别人的书房里重新装修:墙是你的,但柱子不是你的,你永远得绕着它走。
这站的编辑器里还能写公式,文章里嵌着要现场演示的交互块,渲染器里都为它们留了位置——遇到公式就套上衬线字体、居中排开,遇到演示块就在正文里嵌一个能点的真东西。你读到的这一段,底下其实就跑着一个这样的演示。
▲ 图:正文里嵌着的实时演示块——能现场点的那种这些东西,现成库给不了,都得自己一点点拼。整段渲染的骨架,是先把代码块从原文里抠出来,剩下的文字按空行切成一块一块,再按特征各找各的家——以井号开头的是标题,以减号开头的是列表,带感叹号方括号的是图片。分完家,按顺序往模板里一摆,一篇文章就成了。
解析器本身不难,难的是那些边角料:表格的隔行线、列表里混段落的、标题里带表情的,一个比一个刁。最要命的是,文章内容在代码里是个模板字符串,里面要嵌代码块,反引号全得写成转义形式,插值符号也得躲着写,不然就会被当成变量替换。写错一个,整个文件编译不过。
那天凌晨两点,我盯着屏幕上一行标红的报错,心里只有一个念头:写文档比写代码难,这句话我算是信了。
▲ 图:凌晨两点,一个人和满屏的反引号死磕——谁也不肯先低头
后来我给每个代码块加了个复制按钮,点一下,“复制代码”变成“已复制”,两秒后再变回去。做完那个小功能,我忽然觉得前面那些坑都值了——你给读者一个复制键,就是告诉他:这段代码,你可以拿走用。设计得再漂亮,都不如这个小小的按钮实在。
▲ 图:复制按钮做完、点下去真的能复制的那一刻——成就感是真实的
自己写解析器的坏处是边界情况多,好处是想要什么随时能加。这几年我越来越觉得,核心路径上的工具,能自己握住的就别外包——你把命门交给别人,别人一升级,你就得跟着改,改来改去,最后耗掉的是自己的耐心。
后来我在后台瞄了一眼,复制按钮的点击次数,比文章底部的分享还多。那一刻我挺高兴的——说明读者是真的想用这里的代码,而不是看看就走了。
动效这件事,我想了很久,最后总结成一个很笨的原则:只动该动的。
hover 上浮、箭头平移、抽屉滑出、胶囊反色,每一处动,都是因为状态变了,需要一个交代。除此之外一概不动。没有粒子爆炸,没有视差滚动,没有哗啦啦的整页转场。那些东西不是不能做,是做了,它就抢了内容的戏——你正读着一篇文章,旁边忽然炸开一片烟花,你会觉得这站很炫,但不会觉得它好用。
比如抽屉,0.25 秒。再慢一点,就显出“拖”的劲;再快一点,眼睛还没跟上,就糊过去了。卡片悬停 0.25 秒,玻璃板 0.4 秒——它更大、更重,就多给它一点时间。动效这东西,差几十毫秒,体感就是两回事,你未必说得出来,但你的身体知道。
时长我也定了规矩,统一落在 200 到 500 毫秒之间,曲线几乎全站共用同一个。现在回头看,这种“同一个脾气”的动效,比任何花活都更能让人觉得这个站挺顺。你很难说出顺在哪里,但就是顺。就像一个人讲话,抑扬顿挫全是同一个腔调,你听久了就觉得安稳;要是每句话都换一种怪腔,你再喜欢他说的内容,也坐不住。
曲线我几乎全站统一用一条:入场是 cubic-bezier(0.16, 1, 0.3, 1),快起慢停;需要往返的场合用另一条对等的。有人说动效要丰富,要每种状态一种曲线,我不同意。丰富是给眼睛看的,统一是给身体的。身体要的是安稳,不是惊喜。
还有个细节我做了,但没跟任何人说:页面里配了系统“减弱动态效果”的检测,开了这个设置的人,动画会全部自动收掉,只留下瞬间的状态切换。不是每个读者都需要,但总有人需要。做无障碍这件事,很多时候不是加分项,是底线。
顺便说一句,读到这里,你其实已经撞上了这个站的一个小心思——详情页里嵌着能现场点的演示块。比如下面这个 View Transitions,网格和列表之间切换,浏览器原生做快照过渡,一个库都不用引:
:::demo view-transitions :::
动效做到最后,常常就是这样:越想炫,越要用笨办法;越想要现代感,越依赖那些浏览器早就给好的东西。
页面做完那天,我把它在手机上进进出出好几趟,又切到暗色模式里看了个遍。
▲ 图:在深色的页面里又看了一遍——踏实,像交完了一份卷子
然后我关了灯。
屏幕还亮着。Hero 那三块玻璃,在深色的背景里微微透着光;卡片一张张排下去,圆角圆圆的,安安静静的,像一排整整齐齐站好的书脊。我忽然想起开工那晚,楼下烧烤摊收了摊,我对着散落一地的旧文章发愁的样子。两个星期,一个页面,说不上大,甚至算不上复杂。
可我知道,每一处圆角都磨过,每一个毫秒都量过,每一行代码背后,都有一个后半夜。这些东西,用的人大概说不出来,但读的人,心里会有感觉的。就像朱自清写父亲翻月台,不过是几步路的事,可那几步路,走得很慢,慢到足够让人记很多年。
页面这东西,做的时候觉得它千钧重,做完回头看,不过是千行代码、几张图。可它立在线上,白天有人点进来,晚上也有人点进来,像一盏不灭的灯。做的人下了班,看的人还在路上。
后来有一天早上,我打开后台,看到有人收藏了这篇指南。收藏的人我自然不认识,可那一瞬间,我忽然觉得,那些后半夜熬得都值。做东西的人,最怕的不是辛苦,是辛苦完了没人看见——而被人收藏,就是最好的看见。
我不知道第一个点进来的是谁,也不知道他会不会喜欢。我只知道,做这个页面的两个星期里,我把自己能想到的、能做到的,都放进去了。剩下的,就交给看的人罢。