Skip to content

媒体查询 - 完整响应式设计核心指南

1. 媒体查询基础

1.1 什么是媒体查询

媒体查询(Media Queries)是CSS3的核心功能,允许根据设备特性(如屏幕宽度、分辨率、方向等)应用不同的样式。

typescript
const mediaQueryConcepts = {
  syntax: '@media [media-type] and (media-feature) { CSS rules }',
  
  mediaTypes: [
    'all - 所有设备(默认)',
    'screen - 屏幕设备',
    'print - 打印预览/打印',
    'speech - 屏幕阅读器'
  ],
  
  operators: {
    and: '组合多个条件',
    not: '否定条件',
    only: '防止旧浏览器应用样式',
    ',': '或运算符(逗号)'
  },
  
  features: [
    'width/height - 视口宽高',
    'min-width/max-width - 最小/最大宽度',
    'orientation - 方向',
    'resolution - 分辨率',
    'aspect-ratio - 宽高比',
    'color - 颜色位数',
    'prefers-color-scheme - 深色/浅色模式',
    'hover - 悬停能力',
    'pointer - 指针精度'
  ]
};

1.2 基本语法

css
/* 基础媒体查询 */
@media screen and (max-width: 768px) {
  body {
    font-size: 14px;
  }
}

/* 多条件 */
@media screen and (min-width: 768px) and (max-width: 1024px) {
  .container {
    width: 750px;
  }
}

/* 或运算 */
@media (max-width: 768px), (orientation: landscape) {
  .element {
    display: none;
  }
}

/* 否定 */
@media not screen and (color) {
  /* 非彩色屏幕 */
}

/* only关键字 */
@media only screen and (min-width: 1024px) {
  /* 仅现代浏览器 */
}

2. 常用媒体特性

2.1 宽度和高度

css
/* 视口宽度 */
@media (min-width: 768px) {
  /* 宽度 >= 768px */
}

@media (max-width: 767px) {
  /* 宽度 <= 767px */
}

/* 范围语法(新) */
@media (768px <= width <= 1024px) {
  /* 宽度在768px到1024px之间 */
}

/* 视口高度 */
@media (min-height: 600px) {
  .content {
    padding: 40px;
  }
}

/* 设备宽度(少用) */
@media (device-width: 375px) {
  /* 特定设备 */
}

2.2 方向

css
/* 竖屏 */
@media (orientation: portrait) {
  .header {
    height: 60px;
  }
}

/* 横屏 */
@media (orientation: landscape) {
  .header {
    height: 50px;
  }
  
  .sidebar {
    display: block;
  }
}

/* 移动端横屏特殊处理 */
@media (max-height: 500px) and (orientation: landscape) {
  .header {
    display: none;
  }
}

2.3 分辨率

css
/* 标准屏幕 */
@media (resolution: 1dppx) {
  .logo {
    background-image: url('logo.png');
  }
}

/* Retina屏幕(2倍) */
@media (min-resolution: 2dppx) {
  .logo {
    background-image: url('logo@2x.png');
  }
}

/* 3倍屏 */
@media (min-resolution: 3dppx) {
  .logo {
    background-image: url('logo@3x.png');
  }
}

/* 旧语法兼容 */
@media (-webkit-min-device-pixel-ratio: 2),
       (min-resolution: 192dpi) {
  /* Retina屏幕 */
}

2.4 宽高比

css
/* 16:9宽屏 */
@media (aspect-ratio: 16/9) {
  .video-container {
    padding-bottom: 56.25%; /* 9/16 */
  }
}

/* 最小宽高比 */
@media (min-aspect-ratio: 4/3) {
  .content {
    max-width: 1200px;
  }
}

/* 设备宽高比 */
@media (device-aspect-ratio: 16/9) {
  /* 特定比例设备 */
}

3. 用户偏好媒体特性

3.1 颜色主题

css
/* 浅色模式 */
@media (prefers-color-scheme: light) {
  :root {
    --bg-color: #ffffff;
    --text-color: #000000;
  }
}

/* 深色模式 */
@media (prefers-color-scheme: dark) {
  :root {
    --bg-color: #1a1a1a;
    --text-color: #ffffff;
  }
}

/* 无偏好(默认) */
@media (prefers-color-scheme: no-preference) {
  /* 使用默认主题 */
}

3.2 减少动画

css
/* 正常动画 */
.animated {
  transition: all 0.3s ease;
  animation: slide-in 0.5s;
}

/* 用户偏好减少动画 */
@media (prefers-reduced-motion: reduce) {
  .animated {
    transition: none;
    animation: none;
  }
  
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

3.3 对比度偏好

css
/* 高对比度 */
@media (prefers-contrast: high) {
  body {
    background: #000;
    color: #fff;
  }
  
  .button {
    border: 3px solid #fff;
  }
}

/* 低对比度 */
@media (prefers-contrast: low) {
  body {
    color: #666;
  }
}

3.4 透明度偏好

css
/* 减少透明度 */
@media (prefers-reduced-transparency: reduce) {
  .glass-effect {
    backdrop-filter: none;
    background: solid;
  }
}

4. 交互媒体特性

4.1 悬停能力

css
/* 支持悬停(鼠标) */
@media (hover: hover) {
  .button:hover {
    background: #0066cc;
  }
  
  .tooltip {
    display: none;
  }
  
  .item:hover .tooltip {
    display: block;
  }
}

/* 不支持悬停(触摸) */
@media (hover: none) {
  .button:active {
    background: #0066cc;
  }
  
  .tooltip {
    display: block; /* 始终显示 */
  }
}

/* 任意悬停(混合设备) */
@media (any-hover: hover) {
  /* 至少一个输入设备支持悬停 */
}

4.2 指针精度

css
/* 精确指针(鼠标) */
@media (pointer: fine) {
  .button {
    min-width: 30px;
    min-height: 30px;
  }
}

/* 粗糙指针(手指) */
@media (pointer: coarse) {
  .button {
    min-width: 44px;
    min-height: 44px;
    padding: 12px;
  }
}

/* 无指针设备 */
@media (pointer: none) {
  /* 仅键盘导航 */
}

/* 任意指针 */
@media (any-pointer: coarse) {
  /* 至少一个输入设备是粗糙指针 */
}

5. 断点策略

5.1 移动优先

css
/* 移动优先 - 从小到大 */

/* 基础样式(移动端) */
.container {
  width: 100%;
  padding: 15px;
}

/* 平板 */
@media (min-width: 768px) {
  .container {
    width: 750px;
    padding: 20px;
  }
}

/* 桌面 */
@media (min-width: 1024px) {
  .container {
    width: 970px;
    padding: 30px;
  }
}

/* 大屏 */
@media (min-width: 1280px) {
  .container {
    width: 1200px;
  }
}

5.2 桌面优先

css
/* 桌面优先 - 从大到小 */

/* 基础样式(桌面) */
.container {
  width: 1200px;
  padding: 30px;
}

/* 小屏桌面 */
@media (max-width: 1279px) {
  .container {
    width: 970px;
  }
}

/* 平板 */
@media (max-width: 1023px) {
  .container {
    width: 750px;
    padding: 20px;
  }
}

/* 移动端 */
@media (max-width: 767px) {
  .container {
    width: 100%;
    padding: 15px;
  }
}

5.3 标准断点定义

css
/* 推荐断点 */
:root {
  --breakpoint-xs: 0;
  --breakpoint-sm: 640px;
  --breakpoint-md: 768px;
  --breakpoint-lg: 1024px;
  --breakpoint-xl: 1280px;
  --breakpoint-2xl: 1536px;
}

/* 使用 */
@media (min-width: 640px) { /* sm */ }
@media (min-width: 768px) { /* md */ }
@media (min-width: 1024px) { /* lg */ }
@media (min-width: 1280px) { /* xl */ }
@media (min-width: 1536px) { /* 2xl */ }

6. React中的媒体查询

6.1 useMediaQuery Hook

tsx
// useMediaQuery.ts
import { useState, useEffect } from 'react';

export function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false);
  
  useEffect(() => {
    const media = window.matchMedia(query);
    
    // 初始值
    if (media.matches !== matches) {
      setMatches(media.matches);
    }
    
    // 监听变化
    const listener = () => setMatches(media.matches);
    media.addEventListener('change', listener);
    
    return () => media.removeEventListener('change', listener);
  }, [matches, query]);
  
  return matches;
}

// 使用
function MyComponent() {
  const isMobile = useMediaQuery('(max-width: 768px)');
  const isDark = useMediaQuery('(prefers-color-scheme: dark)');
  const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
  
  return (
    <div>
      {isMobile ? <MobileView /> : <DesktopView />}
      {isDark && <DarkModeStyles />}
      {!prefersReducedMotion && <Animations />}
    </div>
  );
}

6.2 预定义断点Hook

tsx
// useBreakpoint.ts
export function useBreakpoint() {
  const isSm = useMediaQuery('(min-width: 640px)');
  const isMd = useMediaQuery('(min-width: 768px)');
  const isLg = useMediaQuery('(min-width: 1024px)');
  const isXl = useMediaQuery('(min-width: 1280px)');
  const is2Xl = useMediaQuery('(min-width: 1536px)');
  
  if (is2Xl) return '2xl';
  if (isXl) return 'xl';
  if (isLg) return 'lg';
  if (isMd) return 'md';
  if (isSm) return 'sm';
  return 'xs';
}

// 使用
function ResponsiveComponent() {
  const breakpoint = useBreakpoint();
  
  const columns = {
    xs: 1,
    sm: 2,
    md: 3,
    lg: 4,
    xl: 5,
    '2xl': 6
  }[breakpoint];
  
  return (
    <Grid columns={columns}>
      {items.map(item => <Card key={item.id} {...item} />)}
    </Grid>
  );
}

6.3 服务端渲染注意事项

tsx
// SSR安全的媒体查询
export function useMediaQuery(query: string, defaultValue = false): boolean {
  const [matches, setMatches] = useState(defaultValue);
  
  useEffect(() => {
    // 仅在客户端执行
    if (typeof window === 'undefined') return;
    
    const media = window.matchMedia(query);
    setMatches(media.matches);
    
    const listener = () => setMatches(media.matches);
    media.addEventListener('change', listener);
    
    return () => media.removeEventListener('change', listener);
  }, [query]);
  
  return matches;
}

// 服务端提示
export function ResponsiveContent() {
  const isMobile = useMediaQuery('(max-width: 768px)', true); // 默认移动端
  
  // 避免闪烁
  const [mounted, setMounted] = useState(false);
  
  useEffect(() => {
    setMounted(true);
  }, []);
  
  if (!mounted) {
    return <MobileView />; // SSR默认渲染
  }
  
  return isMobile ? <MobileView /> : <DesktopView />;
}

7. 实战案例

7.1 响应式导航

css
/* 移动端导航 */
.nav {
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0;
  left: -100%;
  width: 80%;
  height: 100vh;
  background: #fff;
  transition: left 0.3s;
}

.nav.open {
  left: 0;
}

.nav-toggle {
  display: block;
}

/* 桌面导航 */
@media (min-width: 768px) {
  .nav {
    position: static;
    flex-direction: row;
    width: auto;
    height: auto;
  }
  
  .nav-toggle {
    display: none;
  }
}

7.2 响应式布局

css
/* 移动端单列 */
.grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
}

/* 平板双列 */
@media (min-width: 768px) {
  .grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* 桌面三列 */
@media (min-width: 1024px) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* 大屏四列 */
@media (min-width: 1280px) {
  .grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

7.3 响应式字体

css
/* 基础字体 */
html {
  font-size: 14px;
}

h1 { font-size: 1.75rem; } /* 24.5px */
h2 { font-size: 1.5rem; }  /* 21px */

/* 平板字体 */
@media (min-width: 768px) {
  html {
    font-size: 16px;
  }
  
  h1 { font-size: 2rem; }   /* 32px */
  h2 { font-size: 1.75rem; }
}

/* 桌面字体 */
@media (min-width: 1024px) {
  html {
    font-size: 18px;
  }
  
  h1 { font-size: 2.25rem; } /* 40.5px */
  h2 { font-size: 2rem; }
}

7.4 打印样式

css
/* 屏幕样式 */
.no-print {
  display: block;
}

.page-break {
  display: none;
}

/* 打印样式 */
@media print {
  /* 隐藏不需要的元素 */
  .no-print,
  nav,
  footer,
  .sidebar {
    display: none !important;
  }
  
  /* 显示链接URL */
  a[href]:after {
    content: " (" attr(href) ")";
  }
  
  /* 分页 */
  .page-break {
    page-break-before: always;
  }
  
  /* 黑白打印优化 */
  body {
    color: #000;
    background: #fff;
  }
  
  /* 避免分页断裂 */
  h1, h2, h3 {
    page-break-after: avoid;
  }
  
  img {
    max-width: 100% !important;
  }
}

8. 容器查询(Container Queries)

8.1 容器查询基础

css
/* 定义容器 */
.container {
  container-type: inline-size; /* 或 size, normal */
  container-name: sidebar;
}

/* 容器查询 */
@container (min-width: 400px) {
  .card {
    display: flex;
  }
}

/* 命名容器查询 */
@container sidebar (min-width: 300px) {
  .widget {
    padding: 20px;
  }
}

/* 多条件 */
@container (min-width: 400px) and (max-width: 800px) {
  .item {
    font-size: 1.2rem;
  }
}

8.2 React容器查询

tsx
// Container.tsx
export function Container({ children }: { children: React.ReactNode }) {
  return (
    <div style={{ containerType: 'inline-size' }}>
      {children}
    </div>
  );
}

// CSS
@container (min-width: 400px) {
  .responsive-card {
    flex-direction: row;
  }
}

@container (max-width: 399px) {
  .responsive-card {
    flex-direction: column;
  }
}

9. 媒体查询优化

9.1 合并查询

css
/* ❌ 不好 - 重复的媒体查询 */
@media (min-width: 768px) {
  .header { font-size: 24px; }
}

.content { padding: 20px; }

@media (min-width: 768px) {
  .footer { margin-top: 40px; }
}

/* ✅ 好 - 合并相同的媒体查询 */
@media (min-width: 768px) {
  .header { font-size: 24px; }
  .footer { margin-top: 40px; }
}

9.2 避免过度使用

css
/* ❌ 不好 - 过多的断点 */
@media (min-width: 320px) { }
@media (min-width: 480px) { }
@media (min-width: 600px) { }
@media (min-width: 768px) { }
@media (min-width: 900px) { }
@media (min-width: 1024px) { }

/* ✅ 好 - 合理的断点 */
@media (min-width: 640px) { } /* 移动 -> 平板 */
@media (min-width: 1024px) { } /* 平板 -> 桌面 */

10. 调试技巧

typescript
const debugMediaQueries = {
  devTools: [
    'Chrome DevTools - Device Mode',
    'Firefox - Responsive Design Mode',
    'Safari - Responsive Design Mode'
  ],
  
  testingTools: [
    'BrowserStack - 真实设备测试',
    'Responsively App - 多设备预览',
    'Polypane - 专业响应式工具'
  ],
  
  tips: [
    '使用matchMedia API测试',
    '添加视觉断点指示器',
    '记录所有断点',
    '测试边界值',
    '检查打印样式'
  ]
};

// JavaScript调试
function debugMediaQuery(query: string) {
  const mq = window.matchMedia(query);
  console.log(`Query: ${query}`);
  console.log(`Matches: ${mq.matches}`);
  
  mq.addEventListener('change', (e) => {
    console.log(`${query} changed to: ${e.matches}`);
  });
}

// 视觉断点指示器
const showBreakpoint = () => {
  const indicator = document.createElement('div');
  indicator.style.cssText = `
    position: fixed;
    bottom: 10px;
    right: 10px;
    padding: 5px 10px;
    background: rgba(0,0,0,0.8);
    color: white;
    z-index: 9999;
  `;
  
  const update = () => {
    const width = window.innerWidth;
    indicator.textContent = `${width}px`;
  };
  
  update();
  window.addEventListener('resize', update);
  document.body.appendChild(indicator);
};

11. 最佳实践

typescript
const mediaQueryBestPractices = {
  strategy: [
    '移动优先方法',
    '使用min-width而非max-width',
    '合理的断点数量(3-5个)',
    '基于内容而非设备',
    '测试边界情况'
  ],
  
  performance: [
    '合并相同的媒体查询',
    '避免过度嵌套',
    '使用CSS变量',
    '考虑关键CSS',
    '优化图片资源'
  ],
  
  accessibility: [
    '尊重用户偏好',
    '支持prefers-reduced-motion',
    '确保打印样式',
    '保持焦点可见',
    '足够的触摸目标'
  ],
  
  maintenance: [
    '文档化断点',
    '使用CSS变量或预处理器',
    '组织化样式结构',
    '版本控制',
    '定期审查和优化'
  ]
};

12. 总结

媒体查询的核心要点:

  1. 基础语法: @media type and (feature)
  2. 常用特性: width, height, orientation
  3. 用户偏好: prefers-color-scheme, prefers-reduced-motion
  4. 交互特性: hover, pointer
  5. 断点策略: 移动优先或桌面优先
  6. React集成: useMediaQuery Hook
  7. 容器查询: 组件级响应式
  8. 性能优化: 合并查询,避免过度使用

通过掌握媒体查询,可以创建真正响应式的Web应用。