Appearance
媒体查询 - 完整响应式设计核心指南
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. 总结
媒体查询的核心要点:
- 基础语法: @media type and (feature)
- 常用特性: width, height, orientation
- 用户偏好: prefers-color-scheme, prefers-reduced-motion
- 交互特性: hover, pointer
- 断点策略: 移动优先或桌面优先
- React集成: useMediaQuery Hook
- 容器查询: 组件级响应式
- 性能优化: 合并查询,避免过度使用
通过掌握媒体查询,可以创建真正响应式的Web应用。