Tips & Tricks (更新: 2026/6/2)

Claude Code 响应式设计实战:从 CSS 到 Playwright 验证

用 Claude Code 实作响应式设计:mobile-first CSS、clamp、网格、图片、导航、表格与 Playwright 截图检查。

Claude Code 响应式设计实战:从 CSS 到 Playwright 验证

先定义规则,再让 Claude Code 改页面

响应式设计不是把桌面页面缩小到手机上。真正可发布的响应式页面,要同时考虑屏幕宽度、触控区域、图片体积、导航优先级、表格可读性、广告位置和转化 CTA。如果只对 Claude Code 说“帮我做移动端适配”,它可能会把某一个宽度调得好看,但留下横向滚动、固定宽卡片、过大的首图、拥挤导航、购买按钮被内容压到很后面等问题。

更可靠的做法是给 Claude Code 一份小而明确的契约。mobile-first CSS 指的是先把窄屏作为默认样式,再为大屏添加增强。clamp() 是一个 CSS 函数,可以在一行里写最小值、理想值和最大值。container query 是容器查询,意思是组件根据父容器宽度变化,而不是只看整个浏览器宽度。把这些前提写清楚,Claude Code 生成的 diff 会更接近真实项目。

官方资料建议以 MDN 的 Responsive design@containerclamp()responsive images 为准。视觉验证使用 Playwright 的 ScreenshotsVisual comparisons。Claude Code 的定位可以参考官方 overviewHow Claude Code works:它会阅读代码库、编辑文件、运行命令并验证结果,所以我们也应该把验收条件写得具体。

相关基础可以搭配阅读 Claude Code 设计系统Claude Code 无障碍实践Claude Code Playwright 测试

实作路线图

响应式改造最怕最后才补 CSS。更好的顺序是:让 Claude Code 先看现有页面,找出每个组件在哪些宽度会坏,再一起修改 CSS、图片和验证脚本。

flowchart LR
  A["读取现有页面"] --> B["mobile-first 基础 CSS"]
  B --> C["fluid grid 与 clamp()"]
  C --> D["container query 组件"]
  D --> E["响应式图片与表格"]
  E --> F["Playwright 截图检查"]

可以直接给 Claude Code 这样的提示词:

请把现有的 /responsive-demo 页面改成响应式。

要求:
- 使用 mobile-first CSS。
- 在 320px、390px、768px、1024px、1440px 下不要出现横向滚动。
- 导航、卡片、价格表、文章 CTA 和页脚不能互相遮挡。
- 内容图片需要设置 width/height、srcset、sizes、loading 和合适的 alt。
- 优先使用 CSS Grid、clamp()、@container,不要先写 JS 判断宽度。
- 修改后用 Playwright 做截图和 overflow 检查。

不要:
- 改坏现有 URL、转化 CTA 链接或无障碍标题层级。
- 增加会造成整页横向滚动的大型固定 min-width。

这个提示词的重点不是文字多,而是把完成条件说清楚。之后你 review Claude Code 的结果时,就能直接对照这些条件。

可复制的 HTML 示例

下面的示例包含导航、首屏、图片、卡片、侧栏和比较表。把图片路径换成项目里的真实资源,就可以和下一节 CSS 一起运行。在 React、Astro、Vue 项目中也可以拆成组件,但初学时先用纯 HTML 更容易观察布局变化。

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Responsive Demo</title>
    <link rel="stylesheet" href="./responsive-demo.css" />
  </head>
  <body>
    <header class="site-nav">
      <a class="brand" href="/">ClaudeCodeLab</a>
      <nav class="nav-links" aria-label="主导航">
        <a href="/zh/blog/">文章</a>
        <a href="/zh/products/">产品</a>
        <a href="/zh/training/">咨询</a>
      </nav>
    </header>

    <main class="page-shell">
      <section class="hero">
        <div>
          <p class="eyebrow">Responsive Design</p>
          <h1>先设计小屏,再让布局自然长大</h1>
          <p class="lead">
            把 mobile-first CSS、流动网格、响应式图片和 Playwright 验证放进同一个工作流。
          </p>
          <a class="primary-cta" href="/zh/products/">查看提示词模板</a>
        </div>
        <picture class="hero-media">
          <source
            type="image/avif"
            srcset="/images/responsive-demo-640.avif 640w, /images/responsive-demo-1280.avif 1280w"
            sizes="(width < 768px) 92vw, 40vw"
          />
          <img
            src="/images/responsive-demo-1280.jpg"
            alt="手机和笔记本电脑显示同一个响应式页面"
            width="1280"
            height="900"
            loading="eager"
            decoding="async"
          />
        </picture>
      </section>

      <div class="layout-grid">
        <aside class="side-panel" aria-label="视口检查项">
          <h2>需要验证的宽度</h2>
          <ul>
            <li>320px: 小屏手机</li>
            <li>390px: 常见手机</li>
            <li>768px: 平板</li>
            <li>1024px 以上: 桌面</li>
          </ul>
        </aside>

        <section class="cards" aria-label="改进卡片">
          <article class="card featured">
            <img src="/images/card-layout.jpg" alt="" width="720" height="480" loading="lazy" />
            <div class="card-body">
              <h2>让卡片响应容器宽度</h2>
              <p>可复用组件不应该只看视口,也要根据所在容器的宽度调整密度。</p>
            </div>
          </article>
          <article class="card">
            <div class="card-body">
              <h2>导航允许换行</h2>
              <p>不要把所有桌面链接硬塞进手机一行,保留足够的点击区域。</p>
            </div>
          </article>
          <article class="card">
            <div class="card-body">
              <h2>表格保留语义</h2>
              <p>窄屏上把行变成卡片,并用 `data-label` 显示列名。</p>
            </div>
          </article>
        </section>
      </div>

      <section class="comparison">
        <h2>方案比较</h2>
        <table class="responsive-table">
          <thead>
            <tr>
              <th scope="col">项目</th>
              <th scope="col">个人</th>
              <th scope="col">团队</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td data-label="项目">目标</td>
              <td data-label="个人">学习与小范围改进</td>
              <td data-label="团队">统一 review 标准</td>
            </tr>
            <tr>
              <td data-label="项目">验证</td>
              <td data-label="个人">本地 Playwright</td>
              <td data-label="团队">CI 截图检查</td>
            </tr>
          </tbody>
        </table>
      </section>
    </main>
  </body>
</html>

mobile-first CSS、流动网格与 clamp

CSS 从窄屏开始写,大屏再用 media query 增强。这样不会不断用移动端规则去覆盖桌面端规则。卡片列数交给 repeat(auto-fit, minmax(...)),标题和间距用 clamp(),特色卡片则用容器查询判断父容器是否足够宽。

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  color: #172033;
  background: #f7f8fb;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

.site-nav,
.page-shell {
  width: min(100% - 2rem, 72rem);
  margin-inline: auto;
}

.site-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding-block: 1rem;
}

.brand,
.nav-links a,
.primary-cta {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

.nav-links {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.nav-links a {
  padding-inline: 0.75rem;
  color: inherit;
  text-decoration: none;
}

.page-shell {
  padding-block: clamp(1rem, 4vw, 3rem);
}

.hero {
  display: grid;
  gap: clamp(1rem, 4vw, 2.5rem);
  align-items: center;
}

.eyebrow {
  color: #0f766e;
  font-weight: 700;
}

.hero h1 {
  max-width: 11ch;
  margin: 0;
  font-size: clamp(2.25rem, 10vw, 5rem);
  line-height: 1.02;
}

.lead {
  max-width: 62ch;
  font-size: clamp(1rem, 2vw, 1.25rem);
  line-height: 1.8;
}

.primary-cta {
  width: fit-content;
  border-radius: 0.5rem;
  padding-inline: 1rem;
  background: #172033;
  color: white;
  text-decoration: none;
  font-weight: 700;
}

.hero-media img,
.card {
  border-radius: 0.75rem;
  box-shadow: 0 18px 50px rgb(15 23 42 / 0.12);
}

.layout-grid {
  display: grid;
  gap: clamp(1rem, 3vw, 2rem);
  margin-block-start: 2rem;
}

.side-panel,
.card,
.comparison {
  background: white;
  border: 1px solid #dbe3ef;
  border-radius: 0.75rem;
}

.side-panel {
  padding: 1rem;
}

.cards {
  container: cards / inline-size;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
  gap: 1rem;
}

.card {
  overflow: hidden;
}

.card-body {
  display: grid;
  gap: 0.75rem;
  padding: 1rem;
}

.card h2,
.comparison h2 {
  margin: 0;
  font-size: clamp(1.25rem, 3vw, 1.75rem);
}

@container cards (width >= 42rem) {
  .card.featured {
    grid-column: span 2;
    display: grid;
    grid-template-columns: minmax(14rem, 0.8fr) 1fr;
  }

  .card.featured img {
    height: 100%;
    object-fit: cover;
  }
}

.comparison {
  margin-block-start: 2rem;
  padding: 1rem;
  overflow-x: auto;
}

.responsive-table {
  width: 100%;
  border-collapse: collapse;
}

.responsive-table th,
.responsive-table td {
  padding: 0.875rem;
  border-block-end: 1px solid #dbe3ef;
  text-align: left;
}

@media (width < 48rem) {
  .responsive-table thead {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
  }

  .responsive-table,
  .responsive-table tbody,
  .responsive-table tr,
  .responsive-table td {
    display: block;
    width: 100%;
  }

  .responsive-table tr {
    border: 1px solid #dbe3ef;
    border-radius: 0.5rem;
    margin-block: 0.75rem;
    overflow: hidden;
  }

  .responsive-table td {
    display: grid;
    grid-template-columns: minmax(7rem, 40%) 1fr;
    gap: 1rem;
  }

  .responsive-table td::before {
    content: attr(data-label);
    font-weight: 700;
    color: #526071;
  }
}

@media (width >= 64rem) {
  .hero {
    grid-template-columns: minmax(0, 1.1fr) minmax(18rem, 0.9fr);
  }

  .layout-grid {
    grid-template-columns: 16rem minmax(0, 1fr);
  }

  .side-panel {
    position: sticky;
    top: 1rem;
    align-self: start;
  }
}

这里最重要的不是颜色,而是避免大固定宽度。width: min(100% - 2rem, 72rem) 让小屏保留边距,大屏限制阅读宽度。minmax(min(100%, 18rem), 1fr) 则防止卡片最小宽度撑破容器。

图片、导航、卡片、表格的判断标准

把组件级规则交给 Claude Code,比一句“改成响应式”更有效。

组件常见问题给 Claude Code 的要求
导航桌面链接硬挤到手机一行允许换行,保留点击区域和 aria-label
卡片width: 320px 或大 min-width 造成 overflow使用 auto-fitminmax()、container query
图片手机也加载同一张大图添加 srcsetsizeswidthheightloadingalt
表格多列挤压导致无法阅读选择横向滚动或行卡片,并保留 data-label
CTA收益入口被图片或广告压住检查移动端第一屏下方和文章末尾

并不是所有小屏都需要汉堡菜单。只有三个链接时,换行往往更直接。表格也一样,价格对比适合横向比较,而工单列表、客户记录更适合变成行卡片。让 Claude Code 保留用户任务,而不是机械保留桌面形状。

至少要测试的用例

第一个用例是 SaaS 仪表盘。侧栏、筛选、KPI 卡片、图表和数据表会同时出现。移动端不能只是全部堆叠,而要优先展示关键指标,折叠次要筛选,并把详情表变成可读卡片。

第二个用例是博客或内容站。正文宽度、目录、广告、相关文章、免费 PDF 注册 CTA 会争夺空间。这里要重点检查行长、图片懒加载、代码块横向滚动,以及 CTA 是否被长代码示例淹没。

第三个用例是电商或课程销售页。商品卡、价格比较、购买按钮和 FAQ 都影响收入。如果价格和购买按钮在手机上掉得太后,转化会受影响,所以要把 CTA 顺序写进 Claude Code 的验收条件。

第四个用例是内部管理页面。日常用户更在意搜索、筛选、键盘操作和表格可读性。响应式改造应该保护原有工作顺序,而不是追求炫目的视觉效果。

用 Playwright 做截图和横向滚动检查

不要只靠肉眼看一次浏览器。Playwright 可以在代表性宽度下检查关键元素是否可见、页面是否出现横向滚动,并保存截图用于 review。

import { expect, test } from "@playwright/test";

const baseUrl = process.env.PLAYWRIGHT_BASE_URL ?? "http://127.0.0.1:3000";

const viewports = [
  { name: "mobile-320", width: 320, height: 740 },
  { name: "mobile-390", width: 390, height: 844 },
  { name: "tablet-768", width: 768, height: 1024 },
  { name: "desktop-1440", width: 1440, height: 1000 },
];

for (const viewport of viewports) {
  test(`responsive demo has no horizontal overflow at ${viewport.name}`, async ({ page }) => {
    await page.setViewportSize({ width: viewport.width, height: viewport.height });
    await page.goto(`${baseUrl}/responsive-demo`);

    await expect(page.getByRole("navigation", { name: "主导航" })).toBeVisible();
    await expect(page.getByRole("link", { name: "查看提示词模板" })).toBeVisible();

    const hasHorizontalOverflow = await page.evaluate(() => {
      return document.documentElement.scrollWidth > document.documentElement.clientWidth;
    });

    expect(hasHorizontalOverflow).toBe(false);
    await expect(page).toHaveScreenshot(`responsive-${viewport.name}.png`, {
      fullPage: true,
      maxDiffPixels: 300,
    });
  });
}

第一次执行会生成基准截图。只有设计有意变化时,才用 npx playwright test --update-snapshots 更新。截图会受到操作系统、字体渲染、GPU 和 headless 设置影响,所以团队最好在同一种 CI 环境里生成和比较。

常见坑与失败例

最大的问题是桌面优先 CSS。先写桌面,再不断用移动端规则覆盖,短期能用,长期会让 cascade 难以理解。让 Claude Code 把窄屏样式放回基础规则,再为大屏添加布局。

第二个坑是缺少 <meta name="viewport">。没有它,移动浏览器可能按虚拟桌面宽度渲染,导致你以为 CSS 正确,实机却不对。

第三个坑是卡片、表格、图片或第三方嵌入里藏着固定宽度。min-width: 960px 在桌面看不出来,却会破坏手机。表格必须横向滚动时,只给表格外层加 overflow-x: auto,不要让整页滚动。

第四个坑是只写 srcset 不写 sizes。浏览器需要知道候选图片和预期显示宽度,才能选择合适资源。让 Claude Code 一起检查 srcsetsizeswidthheightalt

第五个坑是过度相信截图测试。截图适合抓视觉差异,但广告、日期、动画和第三方组件会带来噪声。要同时保留 DOM 断言,例如 overflow、CTA 可见性和 navigation landmark。

把收益路径一起纳入响应式改造

响应式设计也应该服务业务目标。对 ClaudeCodeLab 这样的内容站来说,读者在手机上也要能找到免费 PDF、Gumroad 产品和咨询入口。想建立可复用工作流,可以看 Claude Code 产品与提示词模板;如果团队想用真实页面一起梳理 review 规则和 Playwright 验证,可以看 Claude Code 培训与咨询

给 Claude Code 写 brief 时,把转化路径写进完成条件。一个看起来干净、但隐藏购买按钮或咨询链接的页面,不算完成。

总结

Claude Code 做响应式设计的实务流程是:先定义 mobile-first 契约,再实现流动网格,用 clamp() 控制字号和间距,用 container query 处理可复用组件,认真处理图片、导航和表格,最后用 Playwright 截图和 overflow 断言验证。

我把本文的 demo 模式在本地 320px、390px、768px、1440px 下测试过。导航会换行而不是溢出,卡片在手机上变成一列,表格变成可读的行卡片,Playwright 的横向滚动断言也通过。Masa 的实际感受是:让 Claude Code 同时担任实现者和审查者,专门怀疑固定宽度、图片提示、CTA 位置、表格行为和截图差异,发布前返工会少很多。

#Claude Code #响应式 #CSS #移动端 #Playwright
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。