这个项目最开始只是一时兴起。我打开浏览器,看着十几个标签页堆在一起,突然想:要是能把它们折叠成一个桌面环境,窗口互相叠加、随意拖拽,平时工作资料按目录归档,浏览器本身就是一个操作系统,那该多过瘾。于是我用 HTML 加 CSS 加 JavaScript 手搓了一个运行在浏览器里的简约系统,代号就叫"64x"。
64x 不是真正的操作系统,也不是什么虚拟机,它是一整套模拟桌面环境的单页应用。整套代码就一个 HTML 入口,配几个 JS 模块和 CSS 文件,双击打开就能跑,所有"软件"都是前端组件。它解决的问题很纯粹:在一个网页里提供类似多窗口桌面系统的交互体验——窗口可以移动、缩放、最大化,有任务栏、文件管理器、终端、画板、文本编辑器,文件数据还能在浏览器刷新后保留下来。对于正在学习前端状态管理、事件机制和组件通信的同学来说,它是一份很完整的参考实现;对于纯粹想折腾点有趣项目的人,它也能给你带来不少思路。
我先把最核心的架构思路和几个关键模块的实现过程整理出来,包括一些踩坑记录。这篇文章适合有 HTML/CSS/JS 基础、想挑战一个完整项目的人,不需要框架基础,因为整个项目刻意没有使用任何前端框架,纯原生实现。
1. 从"网页"到"系统":第一版架构设计
1.1 我为什么要抛弃前端框架
项目立项之初,我其实犹豫过要不要上框架。后来仔细盘了一下需求,发现这个项目的核心不是数据流,也不是虚拟 DOM 性能,而是"窗口状态管理"和"DOM 结构的动态编排"。考虑到这类交互逻辑重度依赖原生 DOM 操作,最终决定纯手写,不上框架。
这个决定带来了两个直接好处:第一,项目零依赖,用户打开 HTML 就能跑,不需要 node_modules,不需要构建工具;第二,我能在代码里完整控制每个窗口的 DOM 生命周期,这对实现拖拽、缩放、焦点切换这类底层交互非常有利,不用绕框架的抽象层。
副作用也很明显。没有框架的响应式数据绑定,窗口状态一变,我需要手动同步修改 DOM 里的所有相关节点。一开始我踩了不少重复渲染的坑,后来我引入了两个"土办法"缓解:一是给每个窗口维护一个 states 对象,修改窗口属性前统一走一个 updateWindowState 函数;二是把"状态变更"和"DOM 渲染"拆成两个阶段,状态先落库、渲染后执行,杜绝了多次修改状态导致的反复渲染。
1.2 目录结构:一个小而完整的模块划分
64x 的代码结构按照操作系统模块的思维方式拆分:
text复制64x/
├── index.html # 入口,包含桌面容器、任务栏容器
├── css/
│ ├── base.css # 重置样式与基础变量
│ ├── desktop.css # 桌面布局与窗口样式
│ └── apps.css # 内置应用的独立样式
└── js/
├── core/
│ ├── window.js # 窗口管理
│ ├── taskbar.js # 任务栏
│ └── vfs.js # 虚拟文件系统
├── apps/
│ ├── textpad.js # 文本编辑器
│ ├── terminal.js # 终端模拟器
│ ├── paint.js # 画板
│ └── files.js # 文件管理器
└── main.js # 入口与事件总线
每个模块都对应一个独立的"系统服务",模块之间通过简单的事件总线通信,避免互相直接引用。这个粒度对个人项目来说刚刚好:不至于一个文件几千行,也不至于拆得太碎管不过来。
1.3 "64x"这个名字到底代表什么
好多人问我 64x 里的 64 是不是指 64 位。当时取名的时候我确实想到了"64 位"这个概念,但实际实现里最有价值的一个数字约束是:我可以同时打开的窗口数量上限被设置成了 64 个。换句话说,系统最多同时保留 64 个窗口实例,超出后新的窗口需要关掉旧的或最小化旧的,屏幕上才能真正展示出来。
这个上限不是随意定的。我实测下来,在 Chrome 里同时挂 64 个普通 DOM 窗口(每个窗口内部有基本布局和少量事件绑定)对帧率影响还处于可接受范围,但如果放开到 128 个,即使只是移动它们,肉眼也能感觉到明显卡顿。所以 64 是当前代码结构下一个相对稳妥的平衡点。项目代号就这样定了下来——64x,既蹭了"64 位系统"的意向,也暗示了并发窗口上限。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 窗口管理器的实现:系统的心脏
2.1 窗口对象的最小模型
每个窗口在代码里不是一个 DOM 节点,而是一个普通的 JavaScript 对象。DOM 只是这个对象在屏幕上的投影。定义如下:
javascript复制// 窗口状态模型
const defaultState = {
id: null, // 唯一标识
app:
