Web 作品集实践思考

On this page

最近在用 Vibe Coding 搭建个人作品集网站时,我逐渐意识到:网站并不是把 PDF 作品集重新排版到浏览器里。 当作品集从固定画布变成一个真实运行的 Web 产品后,设计对象也发生了变化——除了内容本身,还需要处理视口、滚动、响应式、交互状态以及页面生命周期。

这也让我重新思考了几个原本在制作 PDF 作品集时很少需要面对的问题。

01 尽量避免「单个内容单元」过长,而不是避免长页面

过去制作 PDF 作品集时,我习惯使用比较长的画板。因为最终阅读方式通常还是纵向滚动,一张长图中连续放置分析、过程和方案,并不会产生太大的问题;页面与页面之间甚至可以通过长图自然完成视觉过渡。

但到了网页里,这种方式开始暴露问题。

浏览器真正提供给网页的可视区域远小于显示器本身。即使显示器是 1920 × 1080,顶部浏览器工具栏、标签栏,以及网站自身的导航都会继续压缩有效高度。原本按照 16:9 或固定画板设计的内容,很容易出现“一屏只能看到上半部分”的情况。用户需要滚动一点,才能看到完整结论;再滚动一点,又进入下一组内容,原本完整的信息关系被视口切断了。

所以我现在更倾向于一个原则:

页面整体可以很长,但每一个核心叙事单元尽量在一屏内完成。

这里的“一屏”并不是要求所有东西强行压缩进 100vh,而是让一个问题、一组分析、一个设计结论尽可能形成视觉闭环。比如一张方案对比、一段用户链路或者一个关键界面,最好进入视口后就能基本看完整,而不是让信息长期处于被裁切的状态。

对于文字量较大的研究和分析内容,则没有必要机械地限制在一屏。Web 本来就是流式媒介,强行缩小字号、压缩间距,反而会牺牲可读性。

因此,相比“不要做长页”,我现在更认可的说法是:

以视口作为设计单位,以内容完整性决定页面高度。

02 滚动叙事很好看,但并不适合承载所有 UX 分析

我一开始也很喜欢 Scroll Storytelling。

它最有吸引力的地方,是可以把用户的滚动动作直接转换为叙事进度。例如一个产品始终固定在屏幕中央,随着用户向下滚动,它不断旋转、拆解、切换状态,周围的信息依次出现。

这种方式非常适合一个特点:

整个故事始终存在一个稳定的“主角”。

例如一台手机、一辆汽车、一个硬件设备,甚至同一个核心界面。用户滚动时看到的不是一页页独立内容,而是同一个主体不断发生变化,因此很容易建立连续的视觉记忆。

但 UX 作品集的结构往往恰好相反。

一个完整的设计项目里可能同时存在用户研究、竞品分析、流程图、问题归纳、信息架构、多个设计方案、界面细节与最终验证。主体不断发生变化,而且大量内容之间不是线性关系,而是分析 → 分支 → 对比 → 收敛。

如果为了视觉效果,强行把这些内容全部变成 Scroll Storytelling,反而容易出现一个问题:叙事形式开始压过信息本身。

用户必须不停滚动才能触发下一句话、下一张图;本来几秒钟就可以扫完的分析,被拆成几十秒的动画过程。对于招聘方这种快速浏览作品集的用户,这未必是一件好事。

因此我现在更倾向于一种混合结构。

研究、分析、问题拆解仍然使用正常的网页文档流,让用户可以快速扫读;真正具有连续变化关系的部分,再使用滚动叙事。例如“原版 → 问题 → 改版”的同一个界面演变、同一条用户链路的逐步优化,或者最终产品 Demo,都非常适合 Sticky + Scroll 的方式。

也就是说:

Scroll Storytelling 应该承担“关系变化”,而不是承担“所有内容展示”。

它更像一种局部叙事工具,而不是整个作品集必须遵循的布局模式。

03 16:9 是设计画布,但不是浏览器视口

这是我做网站之后感受非常明显的一点。

设计作品集经常使用 16:9,因为它接近 PPT、显示器以及常见设计展示的比例。但用户真正浏览网站时,网页几乎不会获得完整的 16:9 屏幕。

例如前面提到的 1920 × 1080 显示器,在正常浏览器状态下可能只剩接近 1920 × 900~1000 的有效视口。此时原本严格按照 16:9 制作的一张“页面”,已经无法完整出现。

CSS 本身也区分了传统 vh 与 dvh、svh、lvh 等不同视口高度单位,就是为了应对浏览器 UI 导致可视区域动态变化的问题。

我目前想到两种方案。

方案一:进入项目后切换全屏

全屏最大的优势非常直接:它可以重新获得完整屏幕,让 16:9 的内容真正按照设计时的比例展示,沉浸感也最强。

尤其对于高度视觉化、接近 Presentation 的作品集,这种体验会很好。

但把它作为默认体验会有几个明显问题。

首先,网页不能在用户进入详情页时直接“强制全屏”。标准 Fullscreen API 要求用户产生明确的交互行为,例如点击按钮之后才能调用,而且目前不同浏览器的完整支持情况仍存在差异。

其次,全屏会拿掉浏览器工具栏和部分系统上下文。对于作品集这种需要快速浏览、返回、打开新页面、查看链接的场景,这种沉浸反而可能增加操作成本。

所以我更愿意把它定义成:

Presentation Mode,而不是默认浏览模式。

项目页正常打开,需要沉浸查看时,用户再主动点击「全屏浏览」。

方案二:像 PPT 一样,优先保证高度完整

另一个方案是在普通浏览器里始终保持内容比例,但不要求铺满宽度,而是:

先确保整个页面高度完整出现,再根据剩余空间确定宽度。

例如设计稿仍然保持 16:9,但实际网页中的展示区域最大高度始终不超过:

当前视口高度 − 导航 − 上下安全间距。

当浏览器比较矮时,整个画面会按比例缩小,两侧留下更多空白。

这种方案最大的优势是信息完整性非常稳定。无论用户使用 1920 × 1000、1440 × 800 还是其他比例,每次进入一个核心 section,都能看到一个完整的信息单位。

这实际上更符合我前面提到的“一屏一结论”。

它的问题也很明显:如果内容本身文字很多,整体缩放后字体会变小;极端宽屏下两侧也可能产生大量空白。如果把整个网站都做成一个不断缩放的 PPT Canvas,网页本身的响应式、文字可读性、复制搜索甚至无障碍能力都会被削弱。

因此,我目前更倾向的不是二选一,而是:

默认使用高度优先的 Web 布局,保留原有构图关系;对于关键项目,再提供可选的全屏 Presentation Mode。

视觉化程度很高的方案页可以像 PPT,一屏完整呈现;研究、分析和长文本则继续使用原生网页排版。

这样既保留了设计稿的构图,又没有为了保持 16:9 而让整个网站变成“一张张会滚动的 PPT”。

04 性能优化:与其“及时销毁”,不如建立内容生命周期

这是目前我还没有真正进入,但已经提前意识到的问题。

当作品集开始加入视频、iframe、GSAP 动画、Canvas、WebGL、交互 Demo,甚至真实嵌入的小型 Web App 后,性能问题就不再只是“图片压缩得够不够小”。

更重要的问题开始变成:

一个已经离开视口的内容,还应该继续运行吗?

这里我最初的想法是“及时销毁”,但查了一些资料之后,我觉得更准确的理解应该是资源生命周期管理。

并不是所有离开视口的元素都应该立即销毁。

一个普通 HTML section,即使暂时不在屏幕里,也没有必要反复创建和销毁。浏览器现在甚至可以通过 content-visibility: auto,直接跳过屏幕外内容的布局与绘制工作,而内容仍然保留在 DOM 和可访问性树中。MDN 甚至专门提供了 contentvisibilityautostatechange,用于在内容停止渲染时暂停 Canvas 等计算任务。

真正应该管理的是那些持续消耗 CPU、GPU、内存或网络资源的元素。

比如一个 Canvas 动画离开视口后,可以停止 requestAnimationFrame;一个视频离开视口后,可以暂停播放;一个 iframe 在距离用户很远时,可以延迟加载;一个复杂的 WebGL 场景在项目切换后,则应该真正释放资源。

Intersection Observer 很适合承担这一层控制。它能够异步判断元素何时进入或离开视口,而不需要自己持续监听滚动位置,因此很适合控制懒加载、动画启停以及交互模块的激活状态。

加载本身也应该被延迟。浏览器原生已经支持 iframe 的 loading="lazy";视频则可以通过 preload="none" 或 metadata 避免用户还没看到它时就提前下载完整媒体资源。

而真正离开页面时,就进入另一层:Cleanup。

如果使用 React,useEffect 本身就提供 cleanup 机制,组件卸载或依赖变化时可以移除事件监听、observer、timer 以及第三方实例。

如果使用 GSAP,gsap.context() 可以集中管理动画和 ScrollTrigger,并在组件销毁时通过 revert() 一次性清理。

WebGL 则更需要主动管理。Three.js 官方明确指出,纹理、Geometry 和 Material 对应的 GPU 资源不会因为普通 JavaScript 垃圾回收就自动完全释放,需要在不再使用时主动执行 dispose()。

所以我现在会把作品集里的内容分成三个状态:

未进入视口时不加载;暂时离开视口时暂停;确认不再使用时销毁。

这比简单地“元素离开屏幕就删除”更合理。因为销毁和重新初始化本身也有成本。如果用户上下滚动时,WebGL 场景、复杂动画不断 Destroy → Create,反而可能产生新的卡顿。

真正需要优化的是不必要的持续工作。

部署完成以后,我也不会直接凭感觉判断“网站流不流畅”,而是先测量。当前 Core Web Vitals 主要关注加载速度、交互响应和视觉稳定性,对应 LCP、INP 与 CLS;Google 当前建议的良好体验阈值分别是 LCP ≤ 2.5 秒、INP ≤ 200 ms、CLS ≤ 0.1,并以真实用户访问的第 75 百分位进行判断。

与此同时,可以通过 Chrome DevTools 的 Performance 面板检查主线程、FPS 和长任务,再用 Memory 面板观察 Heap、持续内存分配以及 Detached DOM,判断页面来回浏览之后是否真的存在资源没有被释放的问题。

从“做一个网站”,到“设计一个运行中的作品集”

做 Vibe Coding 之后,我最大的感受其实并不是“现在可以更快写前端了”。

而是当设计师真正开始参与实现时,会开始注意很多以前不会进入 Figma 的问题:

一张图应该多高、什么时候进入视口、用户滚动到哪里触发动画、一个 Demo 离开页面后还应不应该继续运行、浏览器只有 900px 高时内容应该如何重排……

这些都不是传统意义上的“界面设计问题”,但它们最终决定了用户真正看到的体验。

所以我现在越来越倾向于把个人作品集理解成一个真实产品,而不是 PDF 的网页版。

页面可以长,但信息单元应该完整;滚动可以参与叙事,但不应该阻碍阅读;16:9 可以作为视觉基准,但不能成为浏览器的约束;丰富的交互可以增加体验,但也必须拥有完整的加载、运行、暂停与销毁生命周期。

当设计真正进入代码以后,设计对象也就从一张静态画布,变成了一个会随着设备、视口、用户行为和运行状态不断变化的系统。