1. 为什么我需要从别人的网站里“扒”出设计风格
先说个场景:我去年接手一个老项目,客户指着隔壁竞品的官网说“就照这个风格改,但我也说不清具体是啥风格”。
和客户反复确认的过程非常痛苦——他说“高级感”,你理解为深色,他想要的其实是白底细线加大量留白。美术字说不清,开发也不可能靠感觉一遍遍试。这时候我就意识到,比起“模仿”,不如把那个网站的设计语言当成一套数据来解构、量化、转录。而这一切的起点,就是浏览器里最不起眼的两个工具:DOM和计算样式,最终落成一份叫DESIGN.md的文档。
所谓的“提取设计风格”,不是截图,也不是调色板工具抓五个颜色,而是通过分析目标网站的 DOM 结构和getComputedStyle返回的最终样式,反向推导出字号体系、间距规律、圆角半径、阴影层级、色彩变量,甚至组件在不同状态下的交互反馈。这套东西,本身就是一套可以复用的设计规范。
我也见过不少前端做这件事的方式:F12 打开,看到哪个颜色就复制哪个,截两张图丢给设计师说“照着来”。但零散的色值拼不出规则,只有把信息结构化,才能形成可执行的产出。本文要讲的,是用工程化手段,把“我觉得很好看”变成“字号是 14/16/20/28 四档、主色是#2F6BFF、卡片阴影分三个层级”这样可量化的描述,然后沉淀成DESIGN.md。
这套方法适合谁?前端开发、独立开发者、UI 设计师、以及一切需要“参考竞品但不想只靠眼睛看”的人。
2. 从静态 DOM 里读出的视觉线索:class、内联样式与语义结构
2.1 为什么先看 DOM 而不是直接看样式表
我见过很多人一上来就去翻 CSS 文件,结果被几千行代码绕晕。真正高效的路径是反过来的:先看 DOM 结构,再去对样式。因为 CSS 文件是“配方”,而 DOM 是和实际渲染结果一一对应的“成品”。从成品反推配方,比从配方想象成品容易得多。
打开 DevTools 的 Elements 面板,第一件要做的事不是看样式,而是“通读结构”。注意三个层面的东西:
- 命名体系:class 的命名方式能直接反映设计系统。BEM(Block Element Modifier)风格(如
card__title--large)说明设计系统成熟度高;原子化 CSS(如flex items-center p-4)说明是实用优先的方案;还有大量无意义哈希类名(如_3x9f2a)说明经过了构建工具处理,这时就得靠计算样式来兜底。 - 语义标签:用的是
h1/h2还是清一色div?按钮用的是button还是a标签模拟?前者反映了一个团队对可访问性(a11y)的重视程度,后者往往是历史包袱或偷懒的痕迹。 - 内联样式:如果内联样式占比高(比如
style="margin-top: 32px"),说明这个页面可能是活动页或营销页,设计规范执行得比较松散;反之,如果类名清晰且内联样式极少,说明有严格的组件库约束。
比如,在一家 SaaS 公司的官网首页,我观察到所有卡片都遵守类似card card--featured card--compact的命名规则,配合少量内联间距,基本就能判断它的组件体系运行在一套成熟的变量系统之上。这个判断直接决定了后续提取工作要跑到哪个深度。
2.2 快速筛选关键节点的实操套路
面对一个节点很多的页面,不可能每个都看。我的习惯是三个步骤锁目标:
- 看
<head>里引用的字体与图标库,这是最外层的设计资产清单; - 扫一遍正文区的标题层级(h1→h3)和按钮,这是风格的核心表现区域;
- 看表单、弹窗、提示条这类状态组件,因为它们的边框、阴影、过渡动画最能体现细节用心程度。
如果页面是 React/Vue 写的,通常会发现根节点下面挂着一个巨大的#root或#app,然后是一层一层组件嵌套。这种结构下,“设计意图”往往封装在组件里,静态快照看不出状态变化。这就要配合后面的计算样式来挖掘。
2.3 DOM 采集时的版权与合规边界
这点必须单独拎出来说。提取设计风格是分析和学习,不是搬运。你可以参考它的栅格比例、色板逻辑、字号阶梯,但不要直接照抄对方的 SVG 图标、品牌插画、文案内容,更不能把提取出来的 DESIGN.md 连带素材一起用于商用产品,尤其当对方的设计资产受版权保护时。
另外,高频抓取或自动化脚本访问对方站点,要控制频率,遵守robots.txt的约定,别把别人的站点拖垮。我们自己写脚本只做一次性分析,不上规模。
3. getComputedStyle 的深层使用:为什么元素面板里的颜色有时候是假的
3.1 元素面板的“Styles”页签和真实计算值之间的差异
把元素面板当作权威,是提取过程中的一个大坑。你明明看到color: #333,把它记录进文档,结果一上线发现颜色不对。原因在于,DevTools 的 Styles 面板展示的是“匹配到的 CSS 规则”,也就是样式表里关于这个元素的所有声明,而真实渲染出来的效果,是被getComputedStyle计算后的最终值,它可能已经被同一优先级下的另一个规则覆盖,也可能继承了父级,或者被动画/过渡临时改写。
举一个实际例子:某个卡片文字在页面上显示为深蓝色rgb(31, 78, 199),但它的样式表里从头到尾没有这个值。打开 Styles 面板,只看到.card-text { color: #222; }。原来颜色来自更上层的继承链——父容器设置了color: #1F4EC7,子元素没有自己的颜色声明,于是继承了。如果你只扒了.card-text规则,抓到的#222就是彻头彻尾的错误数据。
所以我的准则是:一切以getComputedStyle的返回值为准。它反映的是浏览器经过级联、继承、层叠之后真正绘制到屏幕上的结果,也是唯一和用户最终所见一致的数据源。
3.2 在 Console 里快速提取目标的计算样式
最简单的做法是在 Elements 面板选中目标元素,然后在 Console 里执行:
getComputedStyle(document.querySelector('.card__title'))它会返回一个CSSStyleDeclaration对象,包含上百个属性。但直接用比较冗长,我会封装成一个小函数,只输出关心的属性:
const pickStyle = (sel, props) => { const el = document.querySelector(sel); if (!el) return null; const style = getComputedStyle(el); const result = {}; props.forEach(p => result[p] = style[p]); return result; }; pickStyle('.card__title', ['fontSize', 'fontWeight', 'lineHeight', 'letterSpacing', 'color', 'fontFamily'] );也有直接在 Elements 面板右键 → Copy → Copy Computed Style 的方式,能拿到全部计算值,但信息量过大反而不适合快速决策。我一般只在研究某个具体组件时用完整复制,做全站规范提取时,还是以函数筛选为主。
3.3 伪类状态与伪元素的计算样式提取
普通元素的计算样式好拿,但按钮的 hover、focus 状态,以及::before、::after伪元素的样式,是纯静态分析容易漏掉的部分。这些恰恰是判断一个设计系统精致程度的关键——一个连按钮 hover 色和过渡动画都精心设计的网站,与一个交互反馈粗糙的网站,风格质感差距巨大。
提取伪类状态,可以这样操作:在 Elements 面板中右键点击元素,选择 Force State 里的:hover、:focus、:active,再在 Console 里执行getComputedStyle(...)。也可以直接用 JavaScript 触发:
const btn = document.querySelector('.btn'); btn.matches(':hover'); // 配合 DevTools 的强制状态使用伪元素的计算样式和普通元素几乎是同一个 API 路径,只是选择器要稍微麻烦一点。实际写的时候,没法直接把伪元素传给querySelector,需要用一个变通的方式:
const card = document.querySelector('.card'); const beforeStyle = getComputedStyle(card, '::before'); console.log(beforeStyle.content, beforeStyle.width, beforeStyle.height);getComputedStyle的第二个参数就是伪元素名字,这个用法很多前端不知道,但它对提取图标字体、装饰底纹、角标这些设计细节非常有用。
4. 设计风格提取的实操路径:从视觉采样到数据整理
4.1 确立一个完整的采集维度清单
每次提取设计风格前,我会先定一套维度,然后按维度去采集,避免东一榔头西一棒子。通常分四层:
| 层级 | 关注内容 | 数据来源 |
|---|---|---|
| 色彩层 | 主色、辅助色、背景色、文字色、边框色、渐变方向 | 计算样式 + 背景图分析 |
| 字体层 | 字体族、字号阶梯、行高、字重、字间距 | 计算样式 |
| 布局层 | 栅格列数、容器最大宽度、内边距节奏、外边距节奏、区块间距 | 盒模型数据 |
| 组件层 | 圆角半径、阴影深度、边框宽度、过渡时长、动画缓动曲线 | 计算样式 + 动画面板 |
每一层都需要尽量多的采样点。比如色彩层,不能只看首页,还应该看列表页、详情页、表单页、错误页。同一个按钮在明暗两种背景下呈现的颜色可能完全不同,这种细节只有多页面采样才能发现。
4.2 自动采集脚本:一个可复制的 Console 工具集
手动点击一个个元素太慢了,我通常先在 Console 里跑一段小脚本,批量收集信息。下面这段是一个用于提取全页面标题字号体系的示例:
const headingTags = ['h1', 'h2', 'h3', 'h4']; const result = {}; headingTags.forEach(tag => { document.querySelectorAll(tag).forEach(el => { const style = getComputedStyle(el); const key = `${tag}|${style.fontSize}|${style.fontWeight}|${style.lineHeight}`; if (!result[key]) { result[key] = { tag, fontSize: style.fontSize, fontWeight: style.fontWeight, lineHeight: style.lineHeight, color: style.color, margin: [style.marginTop, style.marginBottom].join(' '), count: 0 }; } result[key].count++; }); }); console.table(result);跑完后,一张清晰的“这个网站到底用了多少种标题样式”的表格就出来了。你会惊讶地发现,很多网站的标题字号并不规律——同一层级在不同页面有不同大小,这本身就是一个重要的设计系统诊断结论。
同样的思路还可以扩展为“按钮提取器”“卡片提取器”“表单输入框提取器”,核心套路是一样的:定位一组同类元素,批量读取计算样式,汇总去重,看分布的规律性。
4.3 视觉采集过程中的干扰项处理
- 字体加载前后不一致:如果页面使用了 web font,在字体尚未加载完成时拿到的
fontFamily可能是 fallback 字体,字号行高也会有轻微偏移。等字体加载完成后再采集,或对比两次采集结果。 - 视口宽度变化导致的流动性布局差异:同一个元素在桌面端和移动端的 padding、字号可能根本不同。采集时固定视口宽度(比如 1440px),或者分别记录断点,避免混合记录导致数据混乱。
- CSS 变量在计算样式中的表现:
getComputedStyle返回的是计算后的值,而不是变量名。这意味着,如果你想知道它原本用了--color-primary这个东西,需要在 Styles 面板或源文件里另外查证。计算样式适合拿“结果”,CSS 变量适合拿“抽象层”。
5. DESIGN.md 的成文规范:一份可用、可评审、可迭代的设计风格文档
5.1 文档结构怎么搭才不变成色板粘贴簿
很多人在“把收集到的数据整理成文档”这一步翻车,因为做成了流水账:红橙黄绿青蓝紫色值一列,圆角列表一堆,没有规则感。真正的 DESIGN.md 应该具备三个特征:分层(全局/组件/状态)、定量(用数字说话)、可追溯(引用来源节点)。
一份标准的 DESIGN.md 我习惯这样组织:
# DESIGN.md — [站点名称] 设计风格提取 ## 1. 设计原则概况 (基于页面观感总结的 3-5 条风格特征,如“整体走圆润亲和路线,圆角偏大,阴影柔和”) ## 2. 色彩系统 ### 2.1 主色板 (列出主色、辅助色、功能色,附色值和用途说明) ### 2.2 文本色阶 (主文本、次级文本、占位文本的颜色与使用场景) ## 3. 字体系统 ### 3.1 字体族 ### 3.2 字号与行高阶梯 (建议用表格,把 h1-h6、正文、辅助文字的字号/行高/字重对齐) ## 4. 间距与布局 ### 4.1 间距节奏 ### 4.2 容器与栅格 ## 5. 组件规范 ### 5.1 按钮体系 (默认/hover/focus/disabled 四态的颜色、阴影、圆角、过渡) ### 5.2 卡片 ### 5.3 表单控件 ## 6. 动效与交互反馈 (过渡时长、缓动函数、hover 位移幅度等) ## 7. 附录:提取信息源页面清单及节点路径这个结构的核心是,它把“风格”拆成了可执行的模块:设计师看了知道怎么延续,开发看了知道怎么实现。
5.2 用表格量化关键参数,别给读者一堆模棱两可的描述
DESIGN.md 里的描述必须精确。比如阴影这一项,不要写“阴影比较柔和,类似材质设计的感觉”,而要写成:
| 层级 | 参数值 |
|---|---|
| 卡片默认阴影 | 0 2px 8px rgba(15, 23, 42, 0.06) |
| 卡片悬浮阴影 | 0 8px 24px rgba(15, 23, 42, 0.12) |
| 弹窗阴影 | 0 16px 48px rgba(15, 23, 42, 0.18) |
同理,过渡动画写成:
| 场景 | 属性 | 时长 | 缓动 |
|---|---|---|---|
| 按钮 hover | background-color, box-shadow | 0.2s | ease-out |
| 卡片 hover 上浮 | transform, box-shadow | 0.3s | cubic-bezier(0.22, 1, 0.36, 1) |
| 弹窗进入 | opacity, transform | 0.25s | ease-in-out |
这些参数从哪来?计算样式拿静态值,动画参数要去 DevTools 的 Performance 面板录一段交互,或者直接在 Console 里读getComputedStyle(el).transition拿默认值。
5.3 记录“反模式”比记录“正模式”更重要
提取风格时,除了记录“人家是怎么做的”,还要记录“哪里有分歧”。比如:
- 同是按钮,首页的大按钮圆角是 8px,弹窗里的小按钮圆角却是 4px,这种不统一要写下来;
- 同样是卡片标题,列表页用的是 16px/600,详情页却是 18px/700,这种差异可能导致后续实现时拿不准;
- 有的页面滚动条样式被改了,有的页面还是默认样式。
我习惯在 DESIGN.md 中加一个“不一致记录”的区块。这不是为了吐槽对方,而是警示自己:当你要复刻这套风格时,这些不一致是“设计的一部分”,还是“历史遗留的 bug”?识别不出这一点,照搬风格时很容易把这些矛盾也一起搬进自己的项目。
6. 把 DESIGN.md 用在项目里的两种方式:手工对照与自动校验
6.1 手工对照审查:组件开发时逐项核对
最常见的用法是把 DESIGN.md 当作组件开发的设计验收文档。让设计师按文档里的规范评审开发完的界面:字号是否一致,圆角是否对得上,阴影层次是否匹配,交互反馈是否规范。这时候文档里越量化,验收效率越高。
我以前在一个团队里推行过“设计评审前先对 DESIGN.md”的流程:开发实现完组件,不要直接喊设计师看,先对着文档自查一遍。能解决掉七成问题,剩下的才是真正需要设计师做判断的事。这既是对设计规范的尊重,也是把设计评审时间用在刀刃上。
6.2 自动校验:用 Puppeteer 做运行时检查
更进阶一种玩法是把它做成自动化的视觉回归。拿 Puppeteer 写一段脚本,加载页面,读取一组关键元素的计算样式,和 DESIGN.md 里的期望值做对比,超出阈值就报 diff。这在大型团队里非常有用,相当于把你的“风格规范”转换成可执行的测试用例:
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://example.com', { waitUntil: 'networkidle2' }); const styles = await page.evaluate(() => { const btn = document.querySelector('.btn-primary'); const cs = getComputedStyle(btn); return { fontSize: cs.fontSize, borderRadius: cs.borderRadius, backgroundColor: cs.backgroundColor, boxShadow: cs.boxShadow }; }); console.table(styles); await browser.close(); })();把这份脚本跑在多个页面上,收集结果再与 DESIGN.md 对比,就能快速判断目标网站的设计风格是否发生了大改动,或者你的复刻版页面是否始终贴合原版风格。
不过,CSS 属性在计算样式里输出的是标准化格式,比如颜色是rgb(47, 107, 255),阴影是多段空格分隔字符串,和文档里写的 hex 格式不一样。写比对逻辑时,要么两边统一格式,要么用容差比较,不要做字符串全等。
7. 提取过程中遇到的反爬与“假风格”干扰
7.1 动态加载和骨架屏造成的“样式缺失期”
现在很多大型网站是 SSR + 客户端水合,或者是纯 CSR 页面。当你打开页面的一瞬间,可能渲染的还是骨架屏,真正的组件样式还没挂载。这时候读取计算样式,拿到的是骨架屏的数据,不是最终页面的数据。
处理方式很简单:等。在脚本里使用waitForSelector或轮询某个重要元素的文字内容,确认页面渲染完成后再采集。比如:
await page.waitForFunction(() => { const el = document.querySelector('.card'); return el && getComputedStyle(el).display !== 'none'; });也可以直接等document.fonts.ready确保字体加载完成。这是最容易被忽略的细节,字体加载前后,版式和颜色虽然不会变,但行高和间距的计算结果会因为 fallback 字体的替换而完全不同。
7.2 广告位、第三方组件对风格数据的污染
有些页面上会混入第三方组件:聊天插件、广告位、Cookie 同意弹窗、调查问卷浮层。这些组件通常是白标产品,样式风格与站点主体完全不同。采集时如果不小心选中了它们,得到的数据会把整个文档搞乱——圆角突然来个 20px,背景色突然变成橙黄,字体族变成系统默认字体。
我的处理原则:采集前先人工分辨区域归属,把目标元素限定在站点自有组件的范围内。自动化采集脚本里,尽量通过选择器和 DOM 位置过滤,比如限定在main或#__next容器内,排除iframe和固定的第三方浮层。
7.3 遇到“假风格”页面:如何判断要不要参考它
还有一种情况:有些网站的主题是卖主题的模板站,或者用了现成的 UI 库但没有任何自定义。这类页面的设计风格提取出来,本质上只是 Bootstrap、Tailwind 默认主题的参数,不代表任何团队的设计智慧。参考价值很低。
判断方法很简单:看它的类名和计算样式是否高度趋同于某个公开 UI 框架的默认值。比如你会发现所有按钮的borderRadius都是0.375rem,fontFamily全是默认系统字体栈,间距全是0.5rem的整数倍——这就是“框架原装风格”,而不是“自定义设计风格”。这种页面提取出来做练习可以,但别当成竞品设计分析报告来写。
8. 进阶玩法:CSS 变量反向追踪与设计 Tokens 的自动生成
8.1 从计算值反查 CSS 变量定义
getComputedStyle拿到的是最终计算值,但如果只是想分析设计系统,更高效的角度是摘出 CSS 变量本身。比如一个按钮背景是rgb(47, 107, 255),但在样式表里它引用的是var(--brand-primary)。直接从计算值入手,你会丢失“原来整个设计系统里有一个叫品牌主色的变量”这个关键信息。
反查的方法是:在 Styles 面板里点击属性值旁的链条图标,就能跳转到变量定义处。或者在 Console 里把目标元素涉及到的所有自定义属性都取出来:
const style = getComputedStyle(document.querySelector('.btn-primary')); const brandColor = style.getPropertyValue('--brand-primary'); console.log(brandColor); // 拿到变量名对应的值更彻底的玩法是遍历整个页面的:root选择器,把站点声明的所有 CSS 自定义属性及其值拉出来,构成一个 token 清单。这几乎就是对方设计系统的“源代码备份”。提取出这些 token,再对照它们在一个页面里的实际使用位置,就可以反推出设计系统的组织逻辑。
8.2 从 DESIGN.md 到设计 Token:一份文档如何驱动跨平台落地
DESIGN.md 的最终价值在于把零散数据结构化,那再往前走一步,它就应该是设计 Token 的原料。设计 Token 是平台无关的名字-值对,比如color.brand.primary = #2F6BFF。它可以直接映射到:
- CSS 变量:
--color-brand-primary: #2F6BFF; - Tailwind 配置:
colors.brand.primary = '#2F6BFF' - iOS 的
Color扩展 - Android 的
colors.xml
所以,我在写 DESIGN.md 时,不只写值,还会顺带标注每个值的 token 命名建议:
| 设计语义 | Token 名称 | 值 | 来源节点 |
|---|---|---|---|
| 品牌主色 | color.brand.primary | #2F6BFF | 首页顶部导航 logo 旁按钮 |
| 品牌悬浮色 | color.brand.primaryHover | #1F56DB | 任意按钮 hover 态 |
| 主文本色 | color.text.primary | #1F2937 | 正文 p 标签 |
| 商标辅助灰 | color.text.secondary | #6B7280 | 卡片描述文本 |
这样一来,DESIGN.md 不仅仅是“笔记”,它变成了设计系统落地的中间层:左手连着别人的网站,右手连着你的跨端代码。
8.3 实测案例:从零到 DESIGN.md 的完整时间线
最后分享一个实际执行过的流程,供参考时间预期。
目标站点是一个中型 SaaS 官网,大约 6 个主要页面。我在固定视口 1440px、网络限速不快的环境下,完成全部提取和文档工作大约花了两小时,其中前半小时在梳理页面结构和确定目标元素,中间一小时写脚本批量跑数据,最后半小时整理成文档结构。如果目标站点只有一个页面且组件类型单一,半小时就能搞定;如果是大型电商站,涉及多模板、多品牌分区,那大概率要花上一个工作日甚至更久。
执行顺序上,我不建议边采集边写文档,效率太低。正确流程是:先定维度、建脚本、批量采集,数据跑完再统一整理。这样思路不会被打断,脚本也能重复利用。积累几套模板后,面对新站点基本可以做到“换选择器就能复用”的状态。
这个能力最大的价值在于:它让我从一个“凭感觉抄作业”的开发者,变成了一个能清晰解释“为什么这个设计看起来高级”的实践者。毕竟把视觉翻译成数据,才是设计与工程协作的通用语言。