网站导航

设计基础

响应式设计

选择合适的响应式边界,调整页面列数与可复用摘要,并验证布局发生变化的宽度。

本页内容

#概览

页面布局与可复用卡片可能需要不同的响应式规则。页面跟随视口,卡片则跟随父元素提供的空间。在 Master CSS 中,将条件附加到需要改变样式的 class 后方。

请先完成安装。这些示例使用默认预设,以及加载 @import '@master/css'; 的应用样式表。每个预览都提供观察边界所需的控件;生成的 CSS 显示随附 class 的输出。

#选择变化的依据

工作区列数这类页面整体决策,使用视口条件。同一组件可能出现在主列、侧栏或其他受限区域时,则使用容器查询。

由什么决定?写法测量对象
页面视口grid-cols(3)@md视口宽度
组件可用空间grid-cols(2)@container(--md)最近且符合条件的祖先容器宽度

同一视口,两种组件宽度

根字号为 16px 时,28rem 等于 448px。这些是实际容器宽度:同一视口内,宽摘要分为双轨道,窄摘要保持堆叠。

文字说明
  1. 相同视口. 两张卡片位于同一页面。视口断点看到的宽度相同。
  2. 不同容器. 主容器宽 448px,侧栏容器宽 160px。
  3. 局部响应. 每个祖先使用 container-inline。后代卡片使用 grid-cols(1) 与 grid-cols(2)@container(--md),所以只有主列卡片达到预设的 28rem 查询阈值。

图中两张卡片共用同一个视口,但父元素宽度不同。视口条件会同时应用到两者;容器查询则让窄版保持堆叠,宽版分为多列。

#让页面随视口调整

从单列开始,在内容有足够空间时再增加列数。此工作流程使用预设的 2xs 阈值 37.5rem 与 md 阈值 64rem。以标准初始字号 16px 计算,两者分别为 600px 与 1024px。

页面列数随视口调整

<section aria-label="Publishing workflow" class="grid-cols(1) gap-md grid-cols(2)@2xs grid-cols(3)@md">  <article class="p-md r-lg b(1) b-solid b-line-divider bg-surface-raised">    <h3 class="m(0) text-sm font-regular fg-text-strong">Draft</h3>    <p class="mb(0) mt-xs text-sm fg-text-muted">Collect the changes.</p>  </article>  <article class="p-md r-lg b(1) b-solid b-line-divider bg-surface-raised">    <h3 class="m(0) text-sm font-regular fg-text-strong">Review</h3>    <p class="mb(0) mt-xs text-sm fg-text-muted">Check the examples.</p>  </article>  <article class="p-md r-lg b(1) b-solid b-line-divider bg-surface-raised">    <h3 class="m(0) text-sm font-regular fg-text-strong">Publish</h3>    <p class="mb(0) mt-xs text-sm fg-text-muted">Share the release notes.</p>  </article></section>
生成的 CSS
@layer theme {  :root,  :host {    --color-zinc-40: oklch(68.6% .0028 265);    --color-zinc-60: oklch(48.6% .003 265);    --color-zinc-90: oklch(24.6% .0022 265);    --color-zinc-100: oklch(15% .0016 265)  }  :root,  :host {    --color-surface-raised: light-dark(var(--color-white), var(--color-zinc-90));    --color-line-divider: light-dark(oklch(0% 0 none / .12), oklch(100% 0 none / .12));    --color-text-strong: light-dark(var(--color-zinc-100), var(--color-white));    --color-text-muted: light-dark(var(--color-zinc-60), var(--color-zinc-40))  }  :root,  :host {    --color-white: oklch(100% 0 none)  }  :root,  :host {    --font-weight-regular: 400;    --font-size-sm: .875rem;    --text-sm: var(--font-size-sm);    --text-sm--line-height: 1.5;    --text-sm--letter-spacing: 0;    --radius-lg: .5rem;    --spacing-xs: .5rem;    --spacing-md: 1rem  }}@layer utilities {  .text-sm {    font-size: var(--text-sm);    line-height: var(--text-sm--line-height, normal);    letter-spacing: var(--text-sm--letter-spacing, normal)  }  .grid-cols\(1\) {    display: grid;    grid-template-columns: repeat(1, minmax(0, 1fr))  }  .bg-surface-raised {    background-color: var(--color-surface-raised)  }  .b-line-divider {    border-color: var(--color-line-divider)  }  .r-lg {    border-radius: var(--radius-lg)  }  .b-solid {    border-style: solid  }  .b\(1\) {    border-width: calc(1 * 1px)  }  .fg-text-muted {    color: var(--color-text-muted)  }  .fg-text-strong {    color: var(--color-text-strong)  }  .font-regular {    font-weight: var(--font-weight-regular)  }  .gap-md {    gap: var(--spacing-md)  }  .m\(0\) {    margin: calc(0 * 1px)  }  .mt-xs {    margin-top: var(--spacing-xs)  }  .mb\(0\) {    margin-bottom: calc(0 * 1px)  }  .p-md {    padding: var(--spacing-md)  }  @media (width >= 37.5rem) {    .grid-cols\(2\)\@2xs {      display: grid;      grid-template-columns: repeat(2, minmax(0, 1fr))    }  }  @media (width >= 64rem) {    .grid-cols\(3\)\@md {      display: grid;      grid-template-columns: repeat(3, minmax(0, 1fr))    }  }}
选择宽度或使用滑块。实际预览视口会改变:低于 2xs 为一列,达到 2xs 为两列,达到 md 为三列。外层页面保有自己的视口。

grid-cols(1) 是基础布局。条件只替换列数;内容顺序、间距与表面样式保持不变。三列只有在内容放得下时才有帮助——采用阈值前,先在应用中试用较长标签。

@md 读取预设的 @custom-media --md 定义,不会读取 --breakpoint-md 主题变量。若要调整所有使用 @md 的 class 阈值,在应用 CSS 入口重新声明自定义媒体查询:

app.css
@import '@master/css';@custom-media --md (width >= 48rem);

若新边界只属于某个布局,而非整个预设,请使用项目专属名称:

app.css
@custom-media --workspace-wide (width >= 72rem);
<section class="grid-cols(1) grid-cols(3)@workspace-wide">…</section>

断点参考列出预设名称与值。按内容需求选择阈值;md 这类名称不代表特定设备。

#让组件随容器调整

在祖先元素加上 container-inline,再为后代元素设置容器条件。此处摘要与状态在窄区域中上下堆叠;祖先达到预设 --md 容器阈值 28rem 时,改为两列。

摘要随容器调整

正在测量容器……

Release notes

Review the changes before publishing.

Status
Draft ready
Next step
Editorial review
调整祖先元素的宽度。达到 28rem 后,摘要与状态会排在同一行;页面视口保持不变。
<section class="container-inline">  <article class="grid-cols(1) gap-lg p-md b-solid b(1) b-line-divider r-lg bg-surface-raised grid-cols(2)@container(--md)">    <div class="min-w(0)">      <h3 class="m(0) text-md font-regular fg-text-strong">Release notes</h3>      <p class="mb(0) mt-xs text-sm fg-text-muted">Review the changes before publishing.</p>    </div>    <dl class="grid gap-xs m(0) text-sm">      <div>        <dt class="fg-text-muted">Status</dt>        <dd class="m(0) fg-text-strong">Draft ready</dd>      </div>      <div>        <dt class="fg-text-muted">Next step</dt>        <dd class="m(0) fg-text-strong">Editorial review</dd>      </div>    </dl>  </article></section>
生成的 CSS
@layer utilities {  .grid-cols\(1\) {    display: grid;    grid-template-columns: repeat(1, minmax(0, 1fr))  }  .container-inline {    container-type: inline-size  }  @container (width >= 28rem) {    .grid-cols\(2\)\@container\(--md\) {      display: grid;      grid-template-columns: repeat(2, minmax(0, 1fr))    }  }}

保持浏览器窗口不变,移动 容器 滑块。它调整的是祖先元素,而不是视口。以 16px 字号比较 447px 与 448px:第二列应在 448px 出现。控件受限于文章可用宽度;若文章本身比阈值窄,请加宽浏览器。

祖先建立查询边界,后代改变布局。元素不能把自己的尺寸当作容器查询的祖先。让卡片高度由内容决定,文本换行时才能自然增长。

#区分两套尺度

@md 与 @container(--md) 使用不同定义:

Class 或值在预设中的含义
@md自定义媒体查询的 64rem 视口阈值
@container(--md)自定义容器条件的 28rem 容器阈值
max-w-md使用 --container-md 尺寸 token 的最大宽度

调整 --container-md 会改变宽度上限,不会改变任一查询阈值。使用 @custom-container 定义可复用容器条件,或直接编写 grid-cols(2)@container(width>=30rem) 这类局部查询。定义语法与完整尺度请见具名容器条件。

#为外层容器命名

未具名的尺寸查询会测量最近且符合条件的祖先。若嵌套布局再建立另一个容器,就会改以内层边界为准。当你要测量的是外层宽度,请用 (container-name:outer) 为外层命名,再使用 @container(outer|--md)。

选择要测量的祖先

<section class="(container-name:outer) container-inline p-md r-lg b(1) b-solid b-line-divider">  <p class="mt(0) text-sm fg-text-muted">Outer container</p>  <div class="container-inline (max-width:14rem)">    <p class="text-sm fg-text-muted">Inner container · at most 14rem</p>    <div class="p-md r-md bg-line-subtle b-line-divider b(1) b-solid b-text-blue@container(--md)">      <h3 class="m(0) text-sm font-regular">Nearest container</h3>      <p class="mb(0) mt-xs text-sm">        <span class="hidden@container(--md)">Below 28rem</span>        <span class="hidden inline@container(--md)">At least 28rem</span>      </p>    </div>    <div class="mt-md p-md r-md bg-line-subtle b-line-divider b(1) b-solid b-text-blue@container(outer|--md)">      <h3 class="m(0) text-sm font-regular">Named outer container</h3>      <p class="mb(0) mt-xs text-sm">        <span class="hidden@container(outer|--md)">Below 28rem</span>        <span class="hidden inline@container(outer|--md)">At least 28rem</span>      </p>    </div>  </div></section>
生成的 CSS
@layer theme {  :root,  :host {    --color-zinc-40: oklch(68.6% .0028 265);    --color-zinc-60: oklch(48.6% .003 265);    --color-blue-30: oklch(72.87% .1468 249.3);    --color-blue-60: oklch(51.83% .2687 266.1)  }  :root,  :host {    --color-line-divider: light-dark(oklch(0% 0 none / .12), oklch(100% 0 none / .12));    --color-line-subtle: light-dark(oklch(0% 0 none / .06), oklch(100% 0 none / .06));    --color-text-muted: light-dark(var(--color-zinc-60), var(--color-zinc-40));    --color-text-blue: light-dark(var(--color-blue-60), var(--color-blue-30))  }  :root,  :host {    --font-weight-regular: 400;    --font-size-sm: .875rem;    --text-sm: var(--font-size-sm);    --text-sm--line-height: 1.5;    --text-sm--letter-spacing: 0;    --radius-md: .375rem;    --radius-lg: .5rem;    --spacing-xs: .5rem;    --spacing-md: 1rem  }}@layer utilities {  .text-sm {    font-size: var(--text-sm);    line-height: var(--text-sm--line-height, normal);    letter-spacing: var(--text-sm--letter-spacing, normal)  }  .bg-line-subtle {    background-color: var(--color-line-subtle)  }  .b-line-divider {    border-color: var(--color-line-divider)  }  .r-lg {    border-radius: var(--radius-lg)  }  .r-md {    border-radius: var(--radius-md)  }  .b-solid {    border-style: solid  }  .b\(1\) {    border-width: calc(1 * 1px)  }  .fg-text-muted {    color: var(--color-text-muted)  }  .container-inline {    container-type: inline-size  }  .hidden {    display: none  }  .font-regular {    font-weight: var(--font-weight-regular)  }  .m\(0\) {    margin: calc(0 * 1px)  }  .mt-md {    margin-top: var(--spacing-md)  }  .mt-xs {    margin-top: var(--spacing-xs)  }  .mb\(0\) {    margin-bottom: calc(0 * 1px)  }  .mt\(0\) {    margin-top: calc(0 * 1px)  }  .p-md {    padding: var(--spacing-md)  }  .\(container-name\:outer\) {    container-name: outer  }  .\(max-width\:14rem\) {    max-width: 14rem  }  @container (width >= 28rem) {    .b-text-blue\@container\(--md\) {      border-color: var(--color-text-blue)    }  }  @container (width >= 28rem) {    .inline\@container\(--md\) {      display: inline    }  }  @container (width >= 28rem) {    .hidden\@container\(--md\) {      display: none    }  }  @container outer (width >= 28rem) {    .b-text-blue\@container\(outer\|--md\) {      border-color: var(--color-text-blue)    }  }  @container outer (width >= 28rem) {    .inline\@container\(outer\|--md\) {      display: inline    }  }  @container outer (width >= 28rem) {    .hidden\@container\(outer\|--md\) {      display: none    }  }}
切换 Narrow outer 与 Wide outer。内层容器保持低于 28rem,因此外层容器加宽时,只有具名查询会改变。

两个状态读数位于同一个窄内层容器中。加宽外层边界只会改变具名查询的结果。容器命名是选择祖先,不是让元素查询自身。

具名查询可越过较近容器

名称选择符合条件的祖先。被查询元素须置于该祖先内;容器不会查询自己。

文字说明

嵌套 article 与 card 容器边界包住同一个子元素。蓝色标示将子元素的 article 查询配对到外层具名 article 边界;较近的 card 边界仍为紫色。

#只对改变的样式加条件

共用样式保持无条件。只有需要不同值的属性与范围才加上条件。例如,侧栏在低于 md 时使用凸起表面,而较大布局不保留这个背景:

<aside class="p-md bg-surface-raised@max-md">…</aside>
生成的 CSS
@layer theme {  :root,  :host {    --color-zinc-90: oklch(24.6% .0022 265)  }  :root,  :host {    --color-surface-raised: light-dark(var(--color-white), var(--color-zinc-90))  }  :root,  :host {    --color-white: oklch(100% 0 none)  }}@layer utilities {  @media not (width >= 64rem) {    .bg-surface-raised\@max-md {      background-color: var(--color-surface-raised)    }  }}

@max-md 是预设 @md 的补集,两者不重叠。重新声明 --md 会一起移动两个边界。hidden@media(52.125rem<=width<=64rem) 这类局部范围条件,可锁定特定区间而不改变项目的具名阈值。条件参考说明此查询语法。

最小值与补集衔接且不重叠

圆点表示排除或包含边界。坐标轴仅呈现预设 md 阈值附近,并非设备尺寸图表。

文字说明
  1. 预设 @md 从 width >= 64rem 开始。@max-md 为其补集,不包含阈值本身。
  2. 63.999rem 时仅 max-md 匹配;64rem 时仅 md 匹配。边界没有间隙,也不重叠。
  3. 替换已展开的 manifest 定义时,也要重新声明补集;改变一个已编译定义不会使另一个重新展开。

改变布局,不重复表面样式

带条件的布局 class 保留其他无关声明。此示意图省略非必要的卡片内容。

文字说明

两张卡片共用表面、圆角与文本。父元素跨越阈值时,只有布局从堆叠改为横排。

#验证边界,不只看截图

测试每个阈值前一刻与刚好达到阈值的情况。页面示例请检查实际 iframe 视口的 599/600px 与 1023/1024px。组件示例则固定页面视口,以 16px 字号检查祖先元素的 447/448px。

结果不如预期时,检查后代元素计算后的 grid-template-columns。确认条件匹配、容器查询使用预期且符合条件的祖先,并且没有其他规则在层叠中胜出。

最后测试长文本、放大文本与窄布局。每种宽度都应保持合理的阅读顺序、允许内容换行,并避免固定卡片高度。布局会另外说明网格与间距的决策,不与查询边界混为一谈。