最近给 LMN516 的桌面端前台加了一个侧边栏。 最开始想这件事的时候,我并没有觉得它有多复杂。后台已经有侧边栏,桌面端前台现在的内容和功能也越来越多,继续依靠顶栏、页面内部入口和各种返回按钮去组织,已经有点乱了。那就给前台也加一个侧边栏,把文章、相片、音乐、收藏、通信、个人空间这些入口重新组织起来。 从功能描述上看,这甚至只是一个很普通的 UI 改动。 但真正把侧边栏放上去以后,奇怪的事情马上开始出现。 原来很多看起来正常的页面,突然显得不正常了。 有的页面顶部本来有自己的标题,现在侧边栏已经明确告诉我进入了“文章”“音乐”或者“相片”,正文里又出现一次同样的标题,显得非常重复;有的页面还留着“返回我的空间”“返回收藏目录”这样的按钮,可左边已经有一套一直存在的导航,再保留这种返回逻辑就越来越奇怪。

还有一些问题更明显。 部分页面仍然按照“我是一张独立网页”的思路设计,试图占满整个浏览器窗口。花园冒险、飞行棋、AI 语言陪练这些原本考虑过全屏体验的页面,在手机上这么做可能没有问题,但到了桌面端,顶部已经存在全局顶栏,左边又有侧边栏,如果它们依然理解成传统意义上的 100vw × 100vh,就会直接和网站自己的结构打架。 聊天页面也一样。以前整个网页一起滚动没有什么奇怪的,现在有了固定的顶栏和侧边栏以后,聊天工作区如果还带着整个页面上下跑,体验就明显不对。更合理的结构已经变成:网站外壳固定,聊天框自己占满剩下的工作区,只滚动聊天内容。 这些问题最近一个接一个冒出来。单独看,每一个都像一个小 Bug:删个标题、去掉一个返回按钮、调整一个高度、补一个 overflow、改一个 z-index

但这些问题出现得太集中以后,我开始觉得,真正发生的事情可能不是“侧边栏上线以后还有很多细节需要修”。 而是这个网站的形态已经变了

LMN516 从内容网站、身份系统到 App Shell 的形态变化


一次真正的形态变化,其实早就发生过

回头看,LMN516 第一次发生类似的变化,并不是这次侧边栏,而是加入登录系统的时候。 在没有登录系统以前,它更接近一个很传统的个人内容网站。 有文章,有照片,有音乐,有碎碎念,有各种页面。访问者打开一个地址,看完内容,再去另一个地址。系统当然也有数据库、有后台、有各种技术实现,但从用户视角看,核心关系非常简单:

访问者
  ↓
网页
  ↓
公开内容

那个时候,网站最重要的问题主要还是“有什么内容”和“怎么找到这些内容”。 登录系统加入以后,很多事情开始变得完全不同。 网站第一次需要知道:现在进来的人是谁? 这个问题一旦存在,后面马上会跟着一整串问题:这是普通用户还是管理员?这个人有没有自己的内容?哪些数据只有他本人能看?哪些操作只有站长能做?“我的”到底是什么?一个人在不同设备上的登录状态怎么保持?前台和后台是什么关系?同一个页面,不同身份的人看到的东西是不是应该不同? 所以登录系统真正加入 LMN516 的,并不是一个 /login 页面。 它改变的是整个系统中人和数据之间的关系。 原来的模型大概是:

内容
↓
任何人浏览

后来变成:

结构图
                ┌─ 公开内容
用户身份 ─ 权限 ├─ 我的内容
                ├─ 私人数据
                └─ 管理能力

从那以后,LMN516 就已经不再只是一个公开内容的集合了。它开始有身份、有状态、有权限,也开始真正出现“我的 LMN516”和“别人看到的 LMN516”之间的区别。 这其实是第一次形态变化。


侧边栏带来的,是第二次

如果说登录改变的是“人和数据的关系”,那么这次侧边栏改变的就是“人和整个界面的关系”。 这是两个完全不同的维度。 当桌面端只有普通网页的时候,每个页面可以相对自由。页面自己放标题,自己放返回按钮,自己决定顶部留多少空间,自己决定什么时候全屏,甚至自己做一套小导航,都不会显得特别奇怪。 因为那个时候,浏览器窗口基本就等于页面自己的领地。 现在不一样了。 桌面端已经逐渐形成了一套长期存在的结构:

LMN516 App Shell 的顶栏、侧边栏与工作区关系

这张图看起来只是多了左边那一条东西,但它背后的变化其实很大。 因为从这一刻开始,页面已经不再真正拥有整个屏幕。 屏幕先属于 LMN516,然后才有一部分空间分配给当前页面。 这就是我现在越来越明确的一件事:LMN516 的桌面端正在形成自己的 App Shell,也就是应用外壳。 两种结构最大的区别,不是有没有一条侧边栏,而是页面的地位发生了变化。 以前页面是容器。 现在页面正在变成容器里的内容。


为什么以前正常的页面,现在会突然变得别扭

这也解释了最近很多让我反复觉得“不对劲”的细节。 最明显的是标题和返回按钮。 以前页面需要自己告诉用户“你现在在哪里”,所以顶部放一个“文章”“音乐”“我的评论”,再放一个“返回我的空间”,是合理的。现在左侧导航已经长期存在,用户的位置本来就在整个 Shell 中持续可见,页面再把同样的信息重复一遍,就像进入电脑软件以后,每一个面板都先弹一句“你现在正在这个面板”,反而变成了噪音。 所以最近把桌面端一级入口中的大量返回、目录标题、档案标题和重复说明清掉,并不是单纯为了让页面“更干净”。它实际上是在重新分配界面的职责:

全局位置由 Shell 表达,页面只负责自己的内容。

全屏的问题也是一样。 过去我说“这个功能应该全屏”,意思通常非常直观,就是占满浏览器窗口。 现在“全屏”已经至少出现了两种含义。 移动端的游戏、沉浸式体验,可能确实需要真正的设备级全屏;但是桌面端很多所谓的“全屏”,真正应该表达的是“占满 LMN516 的工作区”。

设备级全屏与工作区全屏的区别

也就是:

浏览器窗口
- 顶栏
- 侧边栏
= 当前功能可以使用的完整区域

这个区别看起来很小,但如果不先把定义统一,后面所有页面都会各自理解一次“全屏”,最后一定越来越乱。 滚动也是最近很明显的一类问题。 传统网页默认 body 滚动非常自然,因为整个页面就是浏览对象。但是聊天、歌词管理器、Agent 工作区、长列表、后台组件管理这些界面,慢慢已经不太像一篇网页了,而更像软件中的一个工作面板。 这种情况下,更合理的关系应该是:

Shell:固定
      ↓
工作区:固定尺寸
      ↓
模块内部:自己滚动

而不是:

整个网页
   ↓
顶栏、侧栏、聊天框、输入框
全部一起上下移动

一旦 Shell 出现,“谁负责滚动”就从一个 CSS 小问题变成了界面架构问题。


页面正在失去一部分“自治权”

这一点可能才是这次变化里面最关键的部分。 过去设计一个新页面,可以先问: “这个页面应该长什么样?” 然后开始画标题、返回按钮、内容区、菜单、弹窗。 现在这样做已经越来越危险了。 因为一个页面如果继续独立设计,很容易重新发明一遍整个系统已经存在的东西。 它再做一个返回。 再做一个目录。 再做一个标题栏。 再定义一种弹窗。 再决定一次滚动规则。 再决定一次全屏规则。 最后 LMN516 里面会出现几十个看起来都合理的小系统,但它们组合起来却不是一个系统。 所以页面需要主动交出一部分原来的权力。 导航权逐渐交给侧边栏。 全局状态交给顶栏。 页面位置由路由和 Shell 表达。

全局弹层应该由统一的层级系统管理。 页面只保留和自己业务真正相关的局部动作。 这并不是说页面会变得越来越简单,而是它们的边界会越来越清楚。


前台和后台,也开始不像两个完全独立的世界了

侧边栏上线以后,另外一件越来越明显的事情,是前台和后台的关系也需要重新理解。 过去很容易把它们想成两套系统。 前台给普通人使用,后台给站长管理,所以两边分别做自己的组件、自己的界面、自己的逻辑,好像很自然。 但 LMN516 现在出现了越来越多反例。 比如通信。 个人端有通信,站长端也有通信。如果这两边各自维护一个聊天区域,就很容易出现现在看到的情况:明明做的是同一件事,消息区域的样式、交互、滚动甚至后续功能却慢慢分叉。 更合理的结构不是“努力把两边做得看起来一样”,而应该从代码和职责上让它们本来就是同一个东西。

LMN516 普通用户与站长共用业务核心的权限视图

这和“复制一份然后保持一致”有根本区别。 前者是:

组件 A ≈ 组件 B

需要永远人为保证它们相似。 后者是:

同一个组件
+ 不同身份
+ 不同权限
= 不同视图

这样以后聊天主体修改一次,两边自然一起变化。站长真正特殊的部分,只需要存在于管理入口、权限和操作能力上。 我觉得 LMN516 的前后台最后很可能会越来越接近这种关系:并不是两个网站,而是同一个系统里的两种权限视图。


最近为什么一直在做“UI 统一”

如果只看最近的工作记录,会发现似乎一直在改一些特别琐碎的东西。 这个标题去掉。 那个返回按钮没清干净。 顶栏挡住正文了。 侧边栏展开的时候跳了一下。 聊天区域不要带着整个页面一起滚。 桌面端游戏要保留顶栏。 AI 语言陪练不能继续把整个桌面端当作手机全屏。 前台和后台的通信区域为什么不一样。 甚至歌词管理打开一首歌以后,为什么一个区域一下子撑得特别大。 这些事情分开看,当然都可以分别修。 但它们集中出现,本身就是一种信号。 不是最近突然多了几十个互不相关的 UI Bug,而是旧的页面设计规则正在大面积失效。 以前默认:

页面 = 屏幕

现在应该逐渐变成:

屏幕
  ↓
LMN516 Shell
  ↓
工作区域
  ↓
页面模块

如果这个基本模型没有统一,再勤快地一个页面一个页面修,后面还是会继续长出新的不一致。 这也是为什么最近开始需要一个真正的 UI 中心。


UI 中心不应该只是一个“样式展览馆”

刚开始提 UI 中心的时候,很容易先想到视觉上的东西:字体是什么,圆角多少,卡片怎么做,阴影怎么用,间距是多少,按钮有几种状态。 这些当然需要。 但做到现在,我反而觉得 UI 中心真正重要的一部分,应该是界面架构规范。 它需要规定的并不只是“东西长什么样”,还要规定“东西归谁管”。

LMN516 UI 中心中的界面责任边界

这样以后再做一个页面,需要先回答的其实是一组架构问题: 它属于 Shell 里的哪一层?它需要不需要自己的标题?侧边栏是不是已经表达了这个标题?它能不能真正全屏?还是只能占满工作区?滚动应该发生在页面、列表还是整个 Shell?返回到底属于浏览器历史、侧边栏导航还是页面内部层级?弹窗是这个页面私有的,还是一个系统级弹层?移动端还要不要继续使用同一种结构?它和后台有没有同源组件? 这些问题一旦先回答清楚,后面的 CSS 反而容易很多。 如果这些问题没回答,再精致的圆角、阴影和颜色也只能解决表面。


LMN516 正在从“网站”往“应用”移动

现在回头看 LMN516 这段时间的变化,我觉得大概可以画成这样:

结构图
第一阶段
┌──────────────────┐
│    内容网站       │
│                  │
│ 文章 / 照片 / 音乐 │
│ 页面 / 浏览 / 链接 │
└────────┬─────────┘
         │
         │ 加入登录与身份
         ▼
第二阶段
┌──────────────────┐
│  有身份的个人网站  │
│                  │
│ 用户 / 权限 / 我的 │
│ 私人数据 / 管理员  │
└────────┬─────────┘
         │
         │ 加入统一 Shell
         ▼
第三阶段
┌──────────────────┐
│     应用型网站     │
│                  │
│ 顶栏 / 侧边栏      │
│ 工作区 / 全局导航  │
│ 同源组件 / 权限视图 │
└──────────────────┘

这里所谓的“从网站变成应用”,当然不是说 LMN516 从此就不再是一个网站。 它还是通过浏览器访问,还是有 URL,文章也还是网页。 变化的是组织方式。 内容网站的核心单位是“页面”。 应用型网站的核心单位慢慢变成了“系统中的模块”。 这种变化会继续影响很多以前没有联系在一起的东西:信息架构、导航方式、权限体系、页面层级、屏幕空间怎么分配、组件之间的责任边界、弹层怎么出现、滚动发生在哪里、移动端和桌面端到底应该共享什么,以及前台后台到底是两套东西,还是同一套系统的不同入口。 所以侧边栏看起来只是占了屏幕左边一小块地方,它真正改变的却是整个网站对“页面”的定义。


以后做页面,问题的顺序也应该反过来

以前设计 LMN516 的一个页面,我很容易先想它的样式。 这个页面做成卡片还是列表?标题摆哪?返回键怎么放?这里要不要全屏?右边能不能再放个面板? 以后可能应该先停一下。 第一件事先问:

这个东西在 LMN516 整个系统外壳里面,到底处在什么位置?

先决定它属于系统的哪一层,再决定它长什么样。 先决定谁负责导航,再决定页面要不要返回。 先决定谁负责滚动,再写 overflow。 先决定它到底拥有整个屏幕,还是只拥有工作区,再谈“全屏”。 先判断前台和后台是不是同一个业务主体,再决定要不要重新写第二份组件。 这一套顺序变化以后,很多最近反复出现的问题其实可以提前消失。 因为真正稳定的 UI 并不是“所有按钮都长得一样”。 而是每个东西都知道自己应该负责什么,也知道什么事情不应该由自己负责。


这次前台侧边栏上线以后,我才比较明确地感觉到,网站开发里有两种完全不同的功能。 一种是在原来的结构里增加能力。 比如多一个筛选条件、多一种文章展示方式、多一个按钮。 还有一种功能,看起来也只是增加了一样东西,却会让原来很多默认规则一起失效。 登录系统是一次。 侧边栏可能又是一次。 它们共同的特点是:功能上线以后,不能只检查“这个功能自己有没有做好”,还得回头重新检查整个系统。 因为网站的形态已经变了。 原来那些理所当然的页面规则,也就不再理所当然了。