响应式设计 #

基本概念 #

响应式设计是指网页能够根据不同的屏幕尺寸和设备自动调整布局和样式。Emotion 提供了多种方式来实现响应式设计。

媒体查询基础 #

基本语法 #

jsx
import styled from '@emotion/styled'

const ResponsiveBox = styled.div`
  padding: 16px;
  font-size: 14px;
  
  @media (min-width: 768px) {
    padding: 24px;
    font-size: 16px;
  }
  
  @media (min-width: 1024px) {
    padding: 32px;
    font-size: 18px;
  }
`

常用断点 #

jsx
const breakpoints = {
  mobile: '320px',
  tablet: '768px',
  desktop: '1024px',
  wide: '1200px',
}

响应式工具函数 #

媒体查询函数 #

jsx
import { css } from '@emotion/react'

const breakpoints = {
  sm: '576px',
  md: '768px',
  lg: '992px',
  xl: '1200px',
}

const media = {
  sm: (...args) => css`
    @media (min-width: ${breakpoints.sm}) {
      ${css(...args)}
    }
  `,
  md: (...args) => css`
    @media (min-width: ${breakpoints.md}) {
      ${css(...args)}
    }
  `,
  lg: (...args) => css`
    @media (min-width: ${breakpoints.lg}) {
      ${css(...args)}
    }
  `,
  xl: (...args) => css`
    @media (min-width: ${breakpoints.xl}) {
      ${css(...args)}
    }
  `,
}

使用媒体查询函数 #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const Container = styled.div`
  padding: 16px;
  
  ${media.md`
    padding: 24px;
  `}
  
  ${media.lg`
    padding: 32px;
    max-width: 1200px;
    margin: 0 auto;
  `}
`

const Title = styled.h1`
  font-size: 24px;
  
  ${media.md`
    font-size: 32px;
  `}
  
  ${media.lg`
    font-size: 40px;
  `}
`

响应式组件 #

响应式按钮 #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const Button = styled.button`
  padding: 8px 16px;
  font-size: 14px;
  
  ${media.md`
    padding: 12px 24px;
    font-size: 16px;
  `}
  
  ${media.lg`
    padding: 14px 28px;
    font-size: 18px;
  `}
`

响应式卡片 #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const Card = styled.div`
  padding: 16px;
  margin-bottom: 16px;
  
  ${media.md`
    padding: 24px;
    margin-bottom: 24px;
  `}
  
  ${media.lg`
    padding: 32px;
    margin-bottom: 32px;
  `}
`

响应式布局 #

响应式网格 #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const Grid = styled.div`
  display: grid;
  gap: 16px;
  grid-template-columns: 1fr;
  
  ${media.sm`
    grid-template-columns: repeat(2, 1fr);
  `}
  
  ${media.md`
    grid-template-columns: repeat(3, 1fr);
  `}
  
  ${media.lg`
    grid-template-columns: repeat(4, 1fr);
    gap: 24px;
  `}
`

响应式 Flex 布局 #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const FlexContainer = styled.div`
  display: flex;
  flex-direction: column;
  gap: 16px;
  
  ${media.md`
    flex-direction: row;
    gap: 24px;
  `}
`

const FlexItem = styled.div`
  flex: 1;
  
  ${media.md`
    flex: ${props => props.wide ? 2 : 1};
  `}
`

响应式容器 #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const Container = styled.div`
  width: 100%;
  padding: 0 16px;
  margin: 0 auto;
  
  ${media.sm`
    max-width: 540px;
  `}
  
  ${media.md`
    max-width: 720px;
    padding: 0 24px;
  `}
  
  ${media.lg`
    max-width: 960px;
  `}
  
  ${media.xl`
    max-width: 1140px;
  `}
`

隐藏/显示元素 #

响应式隐藏 #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const HideOnMobile = styled.div`
  display: none;
  
  ${media.md`
    display: block;
  `}
`

const HideOnDesktop = styled.div`
  display: block;
  
  ${media.md`
    display: none;
  `}
`

const HideOn = styled.div`
  display: ${props => props.hideOn === 'mobile' ? 'none' : 'block'};
  
  ${media.md`
    display: ${props => props.hideOn === 'desktop' ? 'none' : 'block'};
  `}
`

响应式显示 #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const ShowOnMobile = styled.div`
  display: block;
  
  ${media.md`
    display: none;
  `}
`

const ShowOnDesktop = styled.div`
  display: none;
  
  ${media.md`
    display: block;
  `}
`

响应式图片 #

响应式图片组件 #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const ResponsiveImage = styled.img`
  width: 100%;
  height: auto;
  max-width: 100%;
  
  ${media.md`
    border-radius: 8px;
  `}
`

const HeroImage = styled.div`
  height: 200px;
  background-image: url('/images/hero-mobile.jpg');
  background-size: cover;
  background-position: center;
  
  ${media.md`
    height: 400px;
    background-image: url('/images/hero-tablet.jpg');
  `}
  
  ${media.lg`
    height: 600px;
    background-image: url('/images/hero-desktop.jpg');
  `}
`

响应式导航 #

移动端导航 #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const Nav = styled.nav`
  display: flex;
  flex-direction: column;
  padding: 16px;
  
  ${media.md`
    flex-direction: row;
    justify-content: space-between;
    padding: 0 24px;
    height: 60px;
    align-items: center;
  `}
`

const NavLinks = styled.ul`
  display: ${props => props.open ? 'flex' : 'none'};
  flex-direction: column;
  list-style: none;
  padding: 0;
  margin: 0;
  
  ${media.md`
    display: flex;
    flex-direction: row;
    gap: 24px;
  `}
`

const MobileMenuButton = styled.button`
  display: block;
  padding: 8px;
  
  ${media.md`
    display: none;
  `}
`

响应式表格 #

响应式表格布局 #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const TableContainer = styled.div`
  overflow-x: auto;
  
  ${media.md`
    overflow-x: visible;
  `}
`

const Table = styled.table`
  width: 100%;
  min-width: 600px;
  
  ${media.md`
    min-width: auto;
  `}
`

const TableCell = styled.td`
  padding: 8px;
  
  ${media.md`
    padding: 12px 16px;
  `}
`

卡片式表格(移动端) #

jsx
import styled from '@emotion/styled'
import { media } from './media'

const ResponsiveTable = styled.div`
  display: block;
  
  ${media.md`
    display: table;
    width: 100%;
    border-collapse: collapse;
  `}
`

const TableRow = styled.div`
  display: block;
  margin-bottom: 16px;
  padding: 16px;
  background: #f5f5f5;
  border-radius: 8px;
  
  ${media.md`
    display: table-row;
    margin-bottom: 0;
    padding: 0;
    background: transparent;
    border-radius: 0;
    
    &:nth-child(even) {
      background: #f9f9f9;
    }
  `}
`

const TableCell = styled.div`
  display: flex;
  justify-content: space-between;
  padding: 8px 0;
  
  ${media.md`
    display: table-cell;
    padding: 12px 16px;
    justify-content: initial;
  `}
  
  &::before {
    content: attr(data-label);
    font-weight: bold;
    
    ${media.md`
      content: none;
    `}
  }
`

主题集成 #

响应式主题 #

jsx
import { ThemeProvider } from '@emotion/react'

const theme = {
  breakpoints: {
    sm: '576px',
    md: '768px',
    lg: '992px',
    xl: '1200px',
  },
  spacing: {
    mobile: {
      sm: '8px',
      md: '16px',
      lg: '24px',
    },
    desktop: {
      sm: '12px',
      md: '24px',
      lg: '36px',
    },
  },
}

const Container = styled.div`
  padding: ${props => props.theme.spacing.mobile.md};
  
  @media (min-width: ${props => props.theme.breakpoints.md}) {
    padding: ${props => props.theme.spacing.desktop.md};
  }
`

最佳实践 #

1. 移动优先 #

从移动端样式开始,逐步添加大屏幕样式:

jsx
const Component = styled.div`
  font-size: 14px;
  
  @media (min-width: 768px) {
    font-size: 16px;
  }
`

2. 使用相对单位 #

jsx
const Container = styled.div`
  padding: 1em;
  
  @media (min-width: 768px) {
    padding: 2em;
  }
`

3. 测试断点 #

确保在所有断点处测试布局:

jsx
const breakpoints = {
  sm: '576px',
  md: '768px',
  lg: '992px',
  xl: '1200px',
}

4. 保持一致性 #

在整个项目中使用相同的断点:

jsx
import { media } from '../styles/media'

const Component = styled.div`
  ${media.md`...`}
  ${media.lg`...`}
`

下一步 #

掌握了响应式设计后,继续学习 常见问题,了解 Emotion 使用中的常见问题和解决方案。

最后更新:2026-03-28