Skip to content

文字缩放与适配

文字缩放能力是 Web 无障碍的重要组成部分。许多用户需要放大文字才能阅读,包括低视力用户、老年用户以及在远距离使用设备的用户。本节将介绍如何使用相对单位(rem/em)替代固定的 px 值、响应式字体设计策略,以及如何确保页面在文字缩放后仍然布局正常、功能完整。

前置知识

阅读本节前,建议先了解:色彩对比度

为什么文字缩放重要

  • WCAG 1.4.4(AA 级):文本可以放大到 200% 而不丢失内容或功能
  • WCAG 1.4.10(AA 级):内容在 320px CSS 像素宽度下可用(响应式)
  • 用户需求:低视力用户可能需要 400% 甚至更高的缩放级别

CSS 单位选择

px vs rem vs em

单位类型说明推荐程度
px绝对单位固定像素值,不随用户设置变化不推荐用于文本
rem相对单位相对于根元素的 font-size推荐
em相对单位相对于父元素的 font-size视情况使用
%相对单位相对于父元素的尺寸适用于特定场景
vw/vh视口单位相对于视口尺寸适用于大标题
clamp()函数在最小和最大值之间限制推荐用于响应式字体

rem 单位

rem(Root em)相对于根元素 <html>font-size,浏览器默认为 16px

css
/* 设置根字体大小 */
html {
  font-size: 100%;  /* 通常等于 16px */
  /* 或显式设置 */
  font-size: 16px;
}

/* 使用 rem 单位 */
h1 { font-size: 2rem; }    /* 32px */
h2 { font-size: 1.5rem; }  /* 24px */
h3 { font-size: 1.25rem; } /* 20px */
p  { font-size: 1rem; }    /* 16px */
.small { font-size: 0.875rem; } /* 14px */

当用户在浏览器中增大文字大小时,rem 单位会相应缩放:

css
/* 用户将浏览器字号设为 20px(放大 125%) */
/* rem 基准变为 20px */
/* h1 = 2rem = 40px */
/* h2 = 1.5rem = 30px */
/* p = 1rem = 20px */

em 单位

em 相对于当前元素的 font-size(或父元素的 font-size):

css
/* em 的特殊性:嵌套时叠加 */
div {
  font-size: 1.2em;  /* 19.2px */
}

div div {
  font-size: 1.2em;  /* 19.2 * 1.2 = 23.04px(叠加!) */
}

/* 使用 rem 避免叠加问题 */
div {
  font-size: 1.2rem;  /* 始终是 19.2px */
}

em 嵌套问题

em 在嵌套元素中会叠加计算,可能导致字体大小不可控。推荐使用 rem 作为基础字号,em 用于 paddingmargin 等与当前元素字号相关的间距。

响应式字体

clamp() 函数

clamp() 允许字体大小在最小值和最大值之间随视口宽度变化:

css
/* clamp(最小值, 首选值, 最大值) */
h1 {
  font-size: clamp(1.5rem, 4vw + 1rem, 3rem);
}

h2 {
  font-size: clamp(1.25rem, 3vw + 0.75rem, 2rem);
}

p {
  font-size: clamp(1rem, 1.5vw + 0.75rem, 1.25rem);
}

媒体查询断点

css
/* 基础字号 */
html {
  font-size: 16px;  /* 默认 */
}

/* 大屏幕放大基础字号 */
@media (min-width: 1200px) {
  html {
    font-size: 18px;
  }
}

/* 超大屏幕 */
@media (min-width: 1800px) {
  html {
    font-size: 20px;
  }
}

视口单位

css
/* 大标题使用 vw 单位,随视口变化 */
h1 {
  font-size: clamp(2rem, 5vw, 4rem);
  line-height: 1.1;
}

/* 英雄区域文本 */
.hero-title {
  font-size: clamp(2.5rem, 8vw, 6rem);
  font-weight: 700;
}

避免固定 px 的场景

需要避免的常见错误

html
<!-- 错误:使用固定 px 作为文字大小 -->
<style>
  h1 { font-size: 32px; }
  p { font-size: 16px; }
  .small { font-size: 12px; }
</style>

<!-- 正确:使用 rem 或相对单位 -->
<style>
  h1 { font-size: 2rem; }
  p { font-size: 1rem; }
  .small { font-size: 0.75rem; }
</style>

行高和间距

css
/* 行高也使用无单位值(相对值) */
p {
  font-size: 1rem;
  line-height: 1.6;  /* 行高 = 1rem * 1.6 = 1.6rem */
}

/* 间距使用 rem */
.section {
  padding: 2rem 1.5rem;    /* rem 间距 */
  margin-bottom: 1.5rem;
}

按钮、输入框等

css
/* 按钮、输入框的 padding 使用 rem */
.btn {
  padding: 0.75rem 1.5rem;
  font-size: 1rem;
  border-radius: 0.25rem;
}

input[type="text"] {
  padding: 0.625rem 0.875rem;
  font-size: 1rem;
  height: auto;  /* 不要固定高度 */
  min-height: 2.5rem;  /* 使用最小高度 */
}

Viewport 缩放与页面缩放

viewport meta 标签

html
<!-- 允许用户缩放(默认行为) -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<!-- 错误:禁止用户缩放 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<!-- 这会严重影响低视力用户 -->

不要禁止缩放

user-scalable=nomaximum-scale=1.0 会使低视力用户无法放大页面,违反 WCAG 1.4.4。始终允许用户缩放。

检测缩放级别

javascript
// 检测页面缩放级别
function detectZoom() {
  const ratio = window.outerWidth / window.innerWidth;
  console.log('缩放级别:', ratio);
  return ratio;
}

// 使用 matchMedia 检测是否放大
if (window.matchMedia('(prefers-contrast: more)').matches) {
  // 用户偏好高对比度
  document.documentElement.classList.add('high-contrast');
}

if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  // 用户偏好减少动画
  document.documentElement.classList.add('reduce-motion');
}

实战示例:无障碍友好的排版

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>文字缩放友好页面</title>
  <style>
    /* 基础设置 */
    :root {
      /* 使用 rem 作为基础单位 */
      --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      --font-mono: 'SF Mono', Monaco, 'Cascadia Mono', monospace;

      /* 字号 */
      --text-xs: 0.75rem;
      --text-sm: 0.875rem;
      --text-base: 1rem;
      --text-lg: 1.125rem;
      --text-xl: 1.25rem;
      --text-2xl: 1.5rem;
      --text-3xl: 2rem;
      --text-4xl: 2.5rem;

      /* 行高 */
      --leading-tight: 1.25;
      --leading-normal: 1.5;
      --leading-relaxed: 1.75;

      /* 间距 */
      --space-xs: 0.25rem;
      --space-sm: 0.5rem;
      --space-md: 1rem;
      --space-lg: 1.5rem;
      --space-xl: 2rem;
      --space-2xl: 3rem;
    }

    body {
      font-family: var(--font-sans);
      font-size: var(--text-base);
      line-height: var(--leading-normal);
      color: #1a1a2e;
      /* 最大宽度避免阅读疲劳 */
      max-width: 72ch;  /* ch 单位基于字符宽度 */
      margin: 0 auto;
      padding: var(--space-lg);
    }

    /* heading 使用 clamp() 响应式字体 */
    h1 {
      font-size: clamp(var(--text-2xl), 5vw + 1rem, var(--text-4xl));
      line-height: var(--leading-tight);
      margin: var(--space-xl) 0 var(--space-md);
    }

    h2 {
      font-size: clamp(var(--text-xl), 3vw + 0.75rem, var(--text-3xl));
      line-height: var(--leading-tight);
      margin: var(--space-lg) 0 var(--space-sm);
    }

    h3 {
      font-size: var(--text-xl);
      line-height: var(--leading-tight);
      margin: var(--space-md) 0 var(--space-sm);
    }

    p {
      margin-bottom: var(--space-md);
    }

    /* 按钮使用 rem */
    .btn {
      display: inline-block;
      padding: 0.75rem 1.5rem;
      font-size: var(--text-base);
      font-weight: 600;
      border: none;
      border-radius: 0.375rem;
      cursor: pointer;
      text-decoration: none;
    }

    /* 表单使用 rem */
    .form-group {
      margin-bottom: var(--space-md);
    }

    .form-group label {
      display: block;
      margin-bottom: var(--space-xs);
      font-weight: 600;
      font-size: var(--text-base);
    }

    .form-group input {
      width: 100%;
      padding: 0.625rem 0.875rem;
      font-size: var(--text-base);
      border: 2px solid #ccc;
      border-radius: 0.375rem;
      box-sizing: border-box;
    }

    /* 代码块 */
    code {
      font-family: var(--font-mono);
      font-size: 0.875em;
      padding: 0.125rem 0.25rem;
      background: #f0f0f0;
      border-radius: 0.25rem;
    }

    /* 响应式布局使用相对单位 */
    .grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
      gap: var(--space-lg);
    }

    /* 不要固定高度 */
    .card {
      padding: var(--space-lg);
      border: 1px solid #e0e0e0;
      border-radius: 0.5rem;
      min-height: 0;  /* 允许内容撑开 */
    }

    /* 图片自适应 */
    img {
      max-width: 100%;
      height: auto;
    }
  </style>
</head>
<body>
  <h1>文字缩放友好页面</h1>

  <p>
    本页面使用 rem 和 clamp() 等相对单位,当用户在浏览器中放大文字时,
    所有排版会等比例缩放,不会出现文字溢出或布局错乱。
  </p>

  <h2>排版示例</h2>

  <p>
    这是正文文本,使用 <code>1rem</code> 字号和 <code>1.5</code> 行高。
    内容区域使用 <code>max-width: 72ch</code> 限制每行约 72 个字符宽度,
    提升阅读体验。
  </p>

  <h3>子标题</h3>
  <p>子标题下的正文内容...</p>

  <form>
    <div class="form-group">
      <label for="name">姓名</label>
      <input type="text" id="name" placeholder="请输入姓名" />
    </div>
    <button type="submit" class="btn">提交</button>
  </form>

  <div class="grid">
    <div class="card">
      <h3>卡片一</h3>
      <p>内容自适应文字缩放...</p>
    </div>
    <div class="card">
      <h3>卡片二</h3>
      <p>内容自适应文字缩放...</p>
    </div>
  </div>
</body>
</html>

注意事项

  1. 不要使用 user-scalable=no:这会阻止用户缩放页面
  2. 避免固定高度的容器:使用 min-height 代替 height
  3. 避免固定宽度的文本容器:文字缩放后可能溢出
  4. 图标和装饰也要使用相对单位:确保等比例缩放
  5. 测试 200% 缩放:WCAG AA 要求页面在 200% 缩放下仍然可用

最佳实践

  • 所有文字大小使用 rem 而非 px
  • 使用 clamp() 实现响应式字体
  • 行高使用无单位值(如 1.5
  • 间距使用 rem 单位
  • 内容区域使用 max-width: 72ch 限制每行字符数
  • 容器使用 min-height 而非固定 height
  • 图片设置 max-width: 100%; height: auto;
  • 在多种缩放级别下测试页面布局

下一节

继续学习:lang 属性与语言声明

参考链接