响应式设计 #
一、媒体查询基础 #
1.1 基本媒体查询 #
jsx
import styled from 'styled-components';
const Container = styled.div`
padding: 16px;
width: 100%;
@media (min-width: 768px) {
padding: 24px;
max-width: 720px;
margin: 0 auto;
}
@media (min-width: 1024px) {
padding: 32px;
max-width: 960px;
}
@media (min-width: 1280px) {
max-width: 1140px;
}
`;
1.2 断点变量 #
jsx
const breakpoints = {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
};
const Container = styled.div`
padding: 16px;
@media (min-width: ${breakpoints.md}) {
padding: 24px;
}
@media (min-width: ${breakpoints.lg}) {
padding: 32px;
}
`;
二、媒体查询工具 #
2.1 媒体查询函数 #
jsx
import styled, { css } from 'styled-components';
const breakpoints = {
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
};
const media = {
sm: (...args) => css`
@media (min-width: ${breakpoints.sm}px) {
${css(...args)}
}
`,
md: (...args) => css`
@media (min-width: ${breakpoints.md}px) {
${css(...args)}
}
`,
lg: (...args) => css`
@media (min-width: ${breakpoints.lg}px) {
${css(...args)}
}
`,
xl: (...args) => css`
@media (min-width: ${breakpoints.xl}px) {
${css(...args)}
}
`,
};
const Container = styled.div`
padding: 16px;
${media.md`
padding: 24px;
max-width: 720px;
margin: 0 auto;
`}
${media.lg`
padding: 32px;
max-width: 960px;
`}
`;
2.2 响应式混入 #
jsx
const responsive = (breakpoint) => (...args) => css`
@media (min-width: ${breakpoint}px) {
${css(...args)}
}
`;
const sm = responsive(640);
const md = responsive(768);
const lg = responsive(1024);
const xl = responsive(1280);
const Text = styled.p`
font-size: 14px;
${sm`
font-size: 16px;
`}
${md`
font-size: 18px;
`}
${lg`
font-size: 20px;
`}
`;
2.3 主题集成断点 #
jsx
import styled, { css } from 'styled-components';
const theme = {
breakpoints: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
},
};
const media = (breakpoint) => (...args) => css`
@media (min-width: ${props => props.theme.breakpoints[breakpoint]}) {
${css(...args)}
}
`;
const Container = styled.div`
padding: 16px;
${media('md')`
padding: 24px;
`}
`;
三、响应式网格 #
3.1 基础网格 #
jsx
import styled from 'styled-components';
const Grid = styled.div`
display: grid;
gap: 16px;
grid-template-columns: 1fr;
@media (min-width: 640px) {
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 1024px) {
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
@media (min-width: 1280px) {
grid-template-columns: repeat(4, 1fr);
}
`;
const GridItem = styled.div`
background: white;
padding: 24px;
border-radius: 12px;
`;
3.2 参数化网格 #
jsx
import styled, { css } from 'styled-components';
const Grid = styled.div`
display: grid;
gap: ${props => props.$gap || '16px'};
grid-template-columns: ${props => props.$cols || '1fr'};
${props => props.$colsSm && css`
@media (min-width: 640px) {
grid-template-columns: repeat(${props.$colsSm}, 1fr);
}
`}
${props => props.$colsMd && css`
@media (min-width: 768px) {
grid-template-columns: repeat(${props.$colsMd}, 1fr);
}
`}
${props => props.$colsLg && css`
@media (min-width: 1024px) {
grid-template-columns: repeat(${props.$colsLg}, 1fr);
}
`}
`;
function App() {
return (
<Grid $colsSm={2} $colsMd={3} $colsLg={4} $gap="24px">
<GridItem>1</GridItem>
<GridItem>2</GridItem>
<GridItem>3</GridItem>
<GridItem>4</GridItem>
</Grid>
);
}
3.3 响应式间距 #
jsx
const Stack = styled.div`
display: flex;
flex-direction: column;
gap: ${props => props.$gap?.[0] || '16px'};
@media (min-width: 768px) {
gap: ${props => props.$gap?.[1] || props.$gap?.[0] || '16px'};
}
@media (min-width: 1024px) {
gap: ${props => props.$gap?.[2] || props.$gap?.[1] || props.$gap?.[0] || '16px'};
}
`;
function App() {
return (
<Stack $gap={['8px', '16px', '24px']}>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</Stack>
);
}
四、响应式组件 #
4.1 响应式隐藏 #
jsx
import styled, { css } from 'styled-components';
const Hide = styled.div`
${props => props.$below && css`
@media (max-width: ${props.$below}px) {
display: none;
}
`}
${props => props.$above && css`
@media (min-width: ${props.$above}px) {
display: none;
}
`}
`;
function App() {
return (
<>
<Hide $below={768}>Hidden on mobile</Hide>
<Hide $above={1024}>Hidden on desktop</Hide>
</>
);
}
4.2 响应式显示 #
jsx
const Show = styled.div`
display: none;
${props => props.$below && css`
@media (max-width: ${props.$below}px) {
display: block;
}
`}
${props => props.$above && css`
@media (min-width: ${props.$above}px) {
display: block;
}
`}
`;
function App() {
return (
<>
<Show $above={768}>Only visible on tablet and desktop</Show>
<Show $below={640}>Only visible on mobile</Show>
</>
);
}
4.3 响应式文本 #
jsx
const Heading = styled.h1`
font-size: 24px;
line-height: 1.2;
@media (min-width: 640px) {
font-size: 32px;
}
@media (min-width: 1024px) {
font-size: 48px;
}
`;
const Text = styled.p`
font-size: 14px;
line-height: 1.6;
@media (min-width: 768px) {
font-size: 16px;
}
@media (min-width: 1024px) {
font-size: 18px;
line-height: 1.8;
}
`;
五、移动优先设计 #
5.1 移动优先原则 #
jsx
import styled from 'styled-components';
const Button = styled.button`
width: 100%;
padding: 16px;
font-size: 16px;
@media (min-width: 640px) {
width: auto;
padding: 12px 24px;
}
@media (min-width: 1024px) {
padding: 12px 32px;
font-size: 18px;
}
`;
const Navigation = styled.nav`
display: flex;
flex-direction: column;
gap: 8px;
@media (min-width: 768px) {
flex-direction: row;
gap: 16px;
}
`;
5.2 移动导航模式 #
jsx
import styled from 'styled-components';
import { useState } from 'react';
const Nav = styled.nav`
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
background: white;
border-bottom: 1px solid #eee;
`;
const MenuButton = styled.button`
display: flex;
padding: 8px;
background: none;
border: none;
cursor: pointer;
@media (min-width: 768px) {
display: none;
}
`;
const Menu = styled.div`
display: ${props => props.$open ? 'flex' : 'none'};
flex-direction: column;
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
padding: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
@media (min-width: 768px) {
display: flex;
flex-direction: row;
position: static;
padding: 0;
box-shadow: none;
}
`;
const MenuItem = styled.a`
padding: 12px 16px;
color: #333;
text-decoration: none;
&:hover {
background: #f5f5f5;
}
@media (min-width: 768px) {
padding: 8px 16px;
&:hover {
background: transparent;
color: #667eea;
}
}
`;
function Navigation() {
const [open, setOpen] = useState(false);
return (
<Nav>
<Logo>Brand</Logo>
<MenuButton onClick={() => setOpen(!open)}>
☰
</MenuButton>
<Menu $open={open}>
<MenuItem href="/">Home</MenuItem>
<MenuItem href="/about">About</MenuItem>
<MenuItem href="/contact">Contact</MenuItem>
</Menu>
</Nav>
);
}
六、响应式布局模式 #
6.1 圣杯布局 #
jsx
import styled from 'styled-components';
const Layout = styled.div`
display: flex;
flex-direction: column;
min-height: 100vh;
`;
const Header = styled.header`
padding: 16px;
background: #667eea;
color: white;
`;
const Main = styled.main`
flex: 1;
display: flex;
flex-direction: column;
@media (min-width: 768px) {
flex-direction: row;
}
`;
const Sidebar = styled.aside`
padding: 16px;
background: #f5f5f5;
@media (min-width: 768px) {
width: 250px;
flex-shrink: 0;
}
`;
const Content = styled.div`
flex: 1;
padding: 16px;
@media (min-width: 768px) {
padding: 24px;
}
`;
const Footer = styled.footer`
padding: 16px;
background: #333;
color: white;
`;
function App() {
return (
<Layout>
<Header>Header</Header>
<Main>
<Sidebar>Sidebar</Sidebar>
<Content>Content</Content>
</Main>
<Footer>Footer</Footer>
</Layout>
);
}
6.2 卡片网格布局 #
jsx
import styled from 'styled-components';
const CardGrid = styled.div`
display: grid;
gap: 16px;
padding: 16px;
grid-template-columns: 1fr;
@media (min-width: 640px) {
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 1024px) {
grid-template-columns: repeat(3, 1fr);
gap: 24px;
padding: 24px;
}
@media (min-width: 1280px) {
grid-template-columns: repeat(4, 1fr);
}
`;
const Card = styled.div`
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
`;
const CardImage = styled.img`
width: 100%;
height: 200px;
object-fit: cover;
`;
const CardContent = styled.div`
padding: 16px;
`;
七、响应式工具 #
7.1 响应式值 Hook #
jsx
import { useState, useEffect } from 'react';
function useMediaQuery(query) {
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 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)');
return { isSm, isMd, isLg, isXl };
}
function ResponsiveComponent() {
const { isMd, isLg } = useBreakpoint();
return (
<div>
{isMd ? 'Tablet or larger' : 'Mobile'}
{isLg && <DesktopOnlyContent />}
</div>
);
}
7.2 响应式样式函数 #
jsx
import styled, { css } from 'styled-components';
const responsiveValue = (property, values) => {
const breakpoints = [0, 640, 768, 1024, 1280];
const breakpointNames = ['xs', 'sm', 'md', 'lg', 'xl'];
return css`
${property}: ${values.xs || values[0]};
${Object.entries(values).map(([key, value]) => {
const index = breakpointNames.indexOf(key);
if (index > 0) {
return css`
@media (min-width: ${breakpoints[index]}px) {
${property}: ${value};
}
`;
}
return null;
})}
`;
};
const Box = styled.div`
${props => responsiveValue('padding', props.$padding || { xs: '16px', md: '24px', lg: '32px' })}
${props => responsiveValue('font-size', props.$fontSize || { xs: '14px', lg: '16px' })}
`;
八、容器查询 #
8.1 基本容器查询 #
jsx
import styled from 'styled-components';
const CardContainer = styled.div`
container-type: inline-size;
container-name: card;
`;
const Card = styled.div`
display: flex;
flex-direction: column;
padding: 16px;
background: white;
border-radius: 12px;
@container card (min-width: 400px) {
flex-direction: row;
padding: 24px;
}
`;
const CardImage = styled.img`
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 8px;
margin-bottom: 16px;
@container card (min-width: 400px) {
width: 200px;
height: 200px;
margin-bottom: 0;
margin-right: 24px;
}
`;
九、打印样式 #
9.1 打印优化 #
jsx
import styled from 'styled-components';
const PrintableContent = styled.div`
padding: 24px;
@media print {
padding: 0;
* {
background: white !important;
color: black !important;
box-shadow: none !important;
}
nav, footer, .no-print {
display: none !important;
}
a[href]::after {
content: " (" attr(href) ")";
}
}
`;
十、总结 #
响应式设计要点速查表:
| 技术 | 用途 | 示例 |
|---|---|---|
| 媒体查询 | 断点样式 | @media (min-width: 768px) |
| 媒体函数 | 复用断点 | ${media.md\…`}` |
| 移动优先 | 渐进增强 | 从小屏到大屏 |
| 响应式网格 | 自适应布局 | grid-template-columns |
| 响应式组件 | 条件渲染 | useMediaQuery |
| 容器查询 | 基于容器 | @container |
下一步:学习 样式复用 掌握样式片段和混入模式。
最后更新:2026-03-28