公司动态

Vue3 还原一个企业级后台-13-像素级验收

📅 2026/8/17 2:52:46
Vue3 还原一个企业级后台-13-像素级验收
像素级验收如何对比 Figma 截图与开发成品像素级还原这四个字被说了整整 12 篇到底是口号还是真的做到了本文不空谈用 Playwright pixelmatch 把 Figma 截图和浏览器截图放一起做像素对比量化出每一帧的差异百分比。17 个 frame逐个验收。一、验收的三个维度像素级还原不能只看像素。一个按钮颜色完全一致但点了没反应——这不算还原。我把验收拆成三个维度维度检查内容工具视觉颜色、字体、间距、圆角、阴影Playwright pixelmatch交互点击、hover、状态切换、表单提交手动操作 Playwright 脚本数据Mock 数据真实性、空状态、loading 态肉眼 Mock 日志本文聚焦第一个维度——视觉对比。交互和数据在之前的业务模块篇已经逐页验证过不赘述。二、准备工作先用 Playwright 截图Playwright 是微软出的浏览器自动化工具能启动 Chrome / Firefox / WebKit 并对指定页面截图。和 Puppeteer 比它的 API 更简洁且原生支持多浏览器。2.1 初始化 Playwrightpnpmadd-Dplaywright/test npx playwrightinstallchromium新建截图脚本// scripts/screenshot-all.jsconst{chromium}require(playwright)constpathrequire(path)constBASE_URLhttp://localhost:5173constOUTPUT_DIRpath.resolve(__dirname,../screenshots/dev)constPAGES[{name:login,url:/login},{name:api-registry,url:/api-manage/registry},{name:api-detail-info,url:/api-manage/detail/1},{name:api-detail-debug,url:/api-manage/detail/1?tabdebug},{name:model-hub,url:/model-hub},{name:model-detail-interface,url:/model-hub/detail/1?tabinterface},{name:model-detail-files,url:/model-hub/detail/1?tabfiles},{name:model-detail-script,url:/model-hub/detail/1?tabscript},{name:model-publish-list,url:/model-publish},{name:model-publish-detail,url:/model-publish/detail/1},]asyncfunctionmain(){constbrowserawaitchromium.launch({headless:true})constcontextawaitbrowser.newContext({viewport:{width:1920,height:1080},deviceScaleFactor:1})for(constpageConfigofPAGES){constpageawaitcontext.newPage()awaitpage.goto(${BASE_URL}${pageConfig.url},{waitUntil:networkidle})// 等 Element Plus 动画结束awaitpage.waitForTimeout(500)awaitpage.screenshot({path:path.join(OUTPUT_DIR,${pageConfig.name}.png),fullPage:false// 只截可视区域和 Figma 导出一致})console.log(✅${pageConfig.name}.png)awaitpage.close()}awaitbrowser.close()}main()几个要点waitUntil: networkidle确保 Mock 数据加载完再截图。waitForTimeout(500)等 Element Plus 的过渡动画完成el-table 渲染、fade-in 等。fullPage: false只截可视区域。Figma 设计稿也是按 1920×1080 可视区设计的保持一致。2.2 Figma 截图的准备从 Figma 导出设计稿截图有两种方式在 Figma 中手动选中每个 frame → Export → PNG 2x → 放到screenshots/figma/。用 Figma API 批量导出需要 Personal Access Token。为了这篇博客的完整性我手动导出了 17 个 frame 的截图。Figma 导出时注意选2x导出因为 Playwright 截图是 1xdeviceScaleFactor: 1尺寸要对齐。如果 Figma 导的是 2x 而 Playwright 截的是 1x像素对比前需要把 Figma 缩放到 50%。2.3 目录结构screenshots/ ├── figma/ # Figma 导出的设计稿截图 │ ├── login.png │ ├── api-registry.png │ ├── api-detail-info.png │ ├── api-detail-debug.png │ ├── model-hub.png │ ├── model-detail-interface.png │ ├── model-detail-files.png │ ├── model-detail-script.png │ ├── model-publish-list.png │ └── model-publish-detail.png ├── dev/ # Playwright 截的开发截图 │ ├── login.png │ ├── api-registry.png │ └── ... └── diff/ # pixelmatch 生成的差异图 ├── login-diff.png └── ...三、像素对比pixelmatch 上场pixelmatch 是最轻量的像素级图片对比库64 行核心代码零依赖。对比两张 PNG输出一张差异图红色标记不同像素点同时返回差异像素的数量。3.1 安装依赖pnpmadd-Dpixelmatch pngjs3.2 对比脚本// scripts/compare-all.jsconstfsrequire(fs)constpathrequire(path)const{PNG}require(pngjs)constpixelmatchrequire(pixelmatch)constFIGMA_DIRpath.resolve(__dirname,../screenshots/figma)constDEV_DIRpath.resolve(__dirname,../screenshots/dev)constDIFF_DIRpath.resolve(__dirname,../screenshots/diff)// 要对比的页面列表constPAGES[login,api-registry,api-detail-info,api-detail-debug,model-hub,model-detail-interface,model-detail-files,model-detail-script,model-publish-list,model-publish-detail,]fs.mkdirSync(DIFF_DIR,{recursive:true})constresults[]functioncompareImages(figmaPath,devPath,diffPath){constfigmaPngPNG.sync.read(fs.readFileSync(figmaPath))constdevPngPNG.sync.read(fs.readFileSync(devPath))// 尺寸不一致时报错if(figmaPng.width!devPng.width||figmaPng.height!devPng.height){return{error:尺寸不一致: figma${figmaPng.width}x${figmaPng.height}, dev${devPng.width}x${devPng.height}}}const{width,height}figmaPngconstdiffnewPNG({width,height})constdiffPixelspixelmatch(figmaPng.data,devPng.data,diff.data,width,height,{threshold:0.1}// 敏感度0完全一致才不计1完全不同的才算)fs.writeFileSync(diffPath,PNG.sync.write(diff))consttotalPixelswidth*heightconstdiffPercent((diffPixels/totalPixels)*100).toFixed(2)return{diffPixels,totalPixels,diffPercent}}// 逐个对比for(constnameofPAGES){constfigmaPathpath.join(FIGMA_DIR,${name}.png)constdevPathpath.join(DEV_DIR,${name}.png)constdiffPathpath.join(DIFF_DIR,${name}-diff.png)if(!fs.existsSync(figmaPath)){results.push({page:name,diffPercent:N/A,note:Figma 截图缺失})continue}if(!fs.existsSync(devPath)){results.push({page:name,diffPercent:N/A,note:开发截图缺失})continue}constrescompareImages(figmaPath,devPath,diffPath)if(res.error){results.push({page:name,diffPercent:N/A,note:res.error})}else{conststatusparseFloat(res.diffPercent)5?❌:✅results.push({page:name,diffPercent:res.diffPercent,status})}}// 输出验收报告console.table(results)3.3 pixelmatch 的threshold参数threshold是 pixelmatch 最关键的一个参数它控制什么算差异、什么不算threshold含义适用场景0两个像素完全一模一样才算一致严格验收但可能会把抗锯齿算成差异0.1允许轻微色差抗锯齿、字体渲染差异推荐值平衡严格度和实用性0.5只标记明显差异太宽松漏检风险大本项目用threshold: 0.1既不会把字体 subpixel 渲染的轻微色差标记为差异也不会放过真正的颜色偏差。四、验收报告运行node scripts/compare-all.js得到 10 个页面的对比结果页面差异率状态说明登录1.8%✅背景渐变有轻微差异Figma 渐变引擎渲染与 CSS 不同API 注册管理2.3%✅表格行高 48px vs 50pxElement Plus 默认行高API 详情-基本信息1.5%✅无显著差异API 详情-运行调试2.1%✅textarea 边框颜色 #DCDFE6 vs #D0D5DD模型汇聚列表3.8%✅卡片间距 16px vs 14px模型详情-接口 Tab2.6%✅表格列宽有 ±2px 浮动模型详情-文件 Tab2.4%✅el-upload 组件默认样式影响模型详情-脚本 Tab1.9%✅深色背景模拟 CodeMirror色值略有偏差模型发布列表2.0%✅状态标签圆角 8px vs 6px模型发布详情1.7%✅无显著差异10 个页面全部通过验收平均差异率 2.21%全部在 5% 容忍线以下。五、常见像素差异及处理策略差异图红色区域能看到一些看似有问题、实际不是问题的差异。下面列 5 种最常见的5.1 字体 subpixel 渲染不可消除Figma 用的是操作系统级字体渲染浏览器用的是浏览器引擎字体渲染Windows 上 ClearType。同一个字在 Figma 截图中比在浏览器截图中略微偏蓝或偏红。判定这是渲染引擎差异不属于前端 bug。threshold: 0.1已经足够屏蔽这类差异。5.2 Element Plus 默认样式覆盖可修复如果 Figma 设计稿里按钮圆角是 10px而 Element Plus 默认是var(--el-border-radius-base)即 4px差异图会在按钮四个角出现红色标记。修复在第 04 篇设计系统建设中我们已经把 Element Plus 的 CSS 变量统一覆写// styles/element-override.scss :root { --el-border-radius-base: 10px; // 对齐设计稿 --el-border-radius-small: 8px; --el-border-radius-round: 14px; }但某些深层组件如el-table的行高、el-select的下拉间距没有被 CSS 变量覆盖需要额外写.el-table__row { height: 50px; // 对齐 Figma 设计稿 } .el-select-dropdown__item { padding: 0 16px; // 对齐设计稿内边距 }5.3 字体未安装fallback 导致Figma 里用了 MiSans 字体开发机上没装。浏览器 fallback 到 Microsoft YaHei两个字体字宽不同导致文字换行位置不一致差异率飙升。修复引入 CDN 字体或本地打包第 04 篇已讲过。验收前确保字体已加载。5.4 Figma 导出 vs 浏览器截图的尺寸差异Figma 导出时选 2x浏览器截的是 1x。如果忘记缩放 Figma 截图尺寸不匹配pixelmatch 直接报错。修复在对比脚本中加入尺寸检测不一致时自动缩放functionresizeToMatch(sourcePng,targetWidth,targetHeight){// 用 sharp 或 canvas 缩放 sourcePng 到 targetWidth × targetHeight// 这里不展开实际项目中 add sharp 依赖即可}5.5 1px 偏差阴影偏移、边框宽度Figma 设计稿的box-shadow是0 2px 8px rgba(0,0,0,0.1)CSS 写了同样的值但渲染效果有 1px 偏移差异。这是浏览器的box-shadow渲染算法和 Figma 的差异不属于前端可控范围。判定差异率 0.3% 的 1px 级别偏差直接标记为通过。六、把验收脚本做成 CI 检查手动跑脚本太麻烦写进package.json{scripts:{dev:vite,screenshot:node scripts/screenshot-all.js,compare:node scripts/compare-all.js,verify:npm run screenshot npm run compare}}以后每次改完样式跑pnpm verify# 1. 启动 dev serverpnpmdev# 2. 截图 对比pnpmverify# 3. 查看 diff/ 目录下的差异图红色区域就是有偏差的地方更进一步可以把验收脚本集成到 Git pre-commit hook 中——每次 commit 前自动截对比差异率超过 5% 时阻止提交。不过对于一个原型项目来说手动跑一跑就够了。七、像素级验收的工程价值做完这轮验收有三个收获远超把图截下来对比一下1. 量化了还原度。“我觉得挺像的” → “10 个页面平均差异率 2.21%”。数字让像素级还原从口号变成可验证的指标。和设计师沟通时不再说你看看像不像而是差异率 2.3%主要是表格行高差了 2px我马上改。2. 暴露了 CSS 变量覆盖不全的问题。验收报告直接指出了行高、边框颜色、卡片间距的偏差反向推动了设计 token 体系的完善——之前只覆写了主色、圆角、字号漏掉了组件级别的细节。修完这些偏差后设计 token 才算真正覆盖了所有可能出差异的地方。3. 形成了可复用的工程能力。这套 Playwright pixelmatch 的脚本换一个 Figma 文件、换一组页面 URL 就能直接复用。下次再做 Figma 还原项目不管是用 Vue 还是 React验收工具链已经有了。上一篇12 - 性能优化路由懒加载、组件按需引入下一篇预告14 篇博文的收官之作。项目做完了、优化完了、验收也过了是时候坐下来复盘——踩过哪些坑、沉淀了什么模式、给读者什么建议。