响应式设计 #

一、媒体查询基础 #

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