全局样式 #

基本概念 #

虽然 Emotion 鼓励组件化的样式管理,但在某些场景下仍需要全局样式,如 CSS 重置、字体导入、基础样式等。Emotion 提供了 Global 组件来处理全局样式。

基本用法 #

使用 Global 组件 #

jsx
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react'

const globalStyles = css`
  * {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
  
  html {
    font-size: 16px;
  }
  
  body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
      Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    line-height: 1.5;
    color: #333;
  }
`

function App() {
  return (
    <>
      <Global styles={globalStyles} />
      <div>App Content</div>
    </>
  )
}

多个全局样式 #

可以添加多个 Global 组件:

jsx
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react'

const resetStyles = css`
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
`

const baseStyles = css`
  html {
    font-size: 16px;
    scroll-behavior: smooth;
  }
  
  body {
    font-family: system-ui, -apple-system, sans-serif;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
  }
`

const typographyStyles = css`
  h1, h2, h3, h4, h5, h6 {
    line-height: 1.2;
    font-weight: 600;
  }
  
  p {
    margin-bottom: 1rem;
  }
  
  a {
    color: #007bff;
    text-decoration: none;
    
    &:hover {
      text-decoration: underline;
    }
  }
`

function App() {
  return (
    <>
      <Global styles={resetStyles} />
      <Global styles={baseStyles} />
      <Global styles={typographyStyles} />
      <AppContent />
    </>
  )
}

CSS 重置 #

简单重置 #

jsx
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react'

const resetStyles = css`
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
  
  html {
    font-size: 100%;
    -webkit-text-size-adjust: 100%;
  }
  
  body {
    min-height: 100vh;
    line-height: 1.5;
    text-rendering: optimizeSpeed;
  }
  
  img, picture, video, canvas, svg {
    display: block;
    max-width: 100%;
  }
  
  input, button, textarea, select {
    font: inherit;
  }
  
  p, h1, h2, h3, h4, h5, h6 {
    overflow-wrap: break-word;
  }
  
  ul, ol {
    list-style: none;
  }
  
  a {
    text-decoration: none;
    color: inherit;
  }
  
  button {
    background: none;
    border: none;
    cursor: pointer;
  }
`

现代 CSS 重置 #

jsx
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react'

const modernReset = css`
  *, *::before, *::after {
    box-sizing: border-box;
  }
  
  * {
    margin: 0;
  }
  
  html {
    scroll-behavior: smooth;
  }
  
  body {
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
  }
  
  img, picture, video, canvas, svg {
    display: block;
    max-width: 100%;
  }
  
  input, button, textarea, select {
    font: inherit;
  }
  
  p, h1, h2, h3, h4, h5, h6 {
    overflow-wrap: break-word;
  }
  
  #root, #__next {
    isolation: isolate;
  }
`

字体导入 #

Google Fonts #

jsx
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react'

const fontStyles = css`
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fira+Code&display=swap');
  
  body {
    font-family: 'Inter', system-ui, -apple-system, sans-serif;
  }
  
  code, pre {
    font-family: 'Fira Code', monospace;
  }
`

本地字体 #

jsx
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react'

const localFontStyles = css`
  @font-face {
    font-family: 'CustomFont';
    src: url('/fonts/CustomFont-Regular.woff2') format('woff2'),
         url('/fonts/CustomFont-Regular.woff') format('woff');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
  }
  
  @font-face {
    font-family: 'CustomFont';
    src: url('/fonts/CustomFont-Bold.woff2') format('woff2'),
         url('/fonts/CustomFont-Bold.woff') format('woff');
    font-weight: 700;
    font-style: normal;
    font-display: swap;
  }
  
  body {
    font-family: 'CustomFont', sans-serif;
  }
`

动态全局样式 #

基于主题的全局样式 #

jsx
/** @jsxImportSource @emotion/react */
import { Global, css, useTheme } from '@emotion/react'

function GlobalStyles() {
  const theme = useTheme()
  
  return (
    <Global
      styles={css`
        body {
          background-color: ${theme.colors.background};
          color: ${theme.colors.text};
          transition: background-color 0.3s ease, color 0.3s ease;
        }
        
        ::selection {
          background-color: ${theme.colors.primary};
          color: white;
        }
        
        ::-webkit-scrollbar {
          width: 8px;
          height: 8px;
        }
        
        ::-webkit-scrollbar-track {
          background: ${theme.colors.surface};
        }
        
        ::-webkit-scrollbar-thumb {
          background: ${theme.colors.textMuted};
          border-radius: 4px;
          
          &:hover {
            background: ${theme.colors.primary};
          }
        }
      `}
    />
  )
}

function App() {
  return (
    <ThemeProvider theme={theme}>
      <GlobalStyles />
      <AppContent />
    </ThemeProvider>
  )
}

条件全局样式 #

jsx
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react'

function App() {
  const [isModalOpen, setIsModalOpen] = useState(false)
  
  return (
    <>
      {isModalOpen && (
        <Global
          styles={css`
            body {
              overflow: hidden;
            }
          `}
        />
      )}
      <AppContent />
    </>
  )
}

工具类 #

布局工具类 #

jsx
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react'

const utilityClasses = css`
  .flex {
    display: flex;
  }
  
  .flex-col {
    flex-direction: column;
  }
  
  .items-center {
    align-items: center;
  }
  
  .justify-center {
    justify-content: center;
  }
  
  .justify-between {
    justify-content: space-between;
  }
  
  .gap-1 { gap: 4px; }
  .gap-2 { gap: 8px; }
  .gap-3 { gap: 16px; }
  .gap-4 { gap: 24px; }
  
  .p-1 { padding: 4px; }
  .p-2 { padding: 8px; }
  .p-3 { padding: 16px; }
  .p-4 { padding: 24px; }
  
  .m-0 { margin: 0; }
  .m-auto { margin: auto; }
  
  .text-center { text-align: center; }
  .text-left { text-align: left; }
  .text-right { text-align: right; }
  
  .w-full { width: 100%; }
  .h-full { height: 100%; }
`

响应式工具类 #

jsx
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react'

const responsiveUtilities = css`
  .hidden { display: none; }
  
  @media (min-width: 576px) {
    .sm\\:block { display: block; }
    .sm\\:flex { display: flex; }
    .sm\\:hidden { display: none; }
  }
  
  @media (min-width: 768px) {
    .md\\:block { display: block; }
    .md\\:flex { display: flex; }
    .md\\:hidden { display: none; }
  }
  
  @media (min-width: 992px) {
    .lg\\:block { display: block; }
    .lg\\:flex { display: flex; }
    .lg\\:hidden { display: none; }
  }
`

打印样式 #

jsx
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react'

const printStyles = css`
  @media print {
    *, *::before, *::after {
      background: transparent !important;
      color: black !important;
      box-shadow: none !important;
      text-shadow: none !important;
    }
    
    a, a:visited {
      text-decoration: underline;
    }
    
    a[href]::after {
      content: " (" attr(href) ")";
    }
    
    abbr[title]::after {
      content: " (" attr(title) ")";
    }
    
    img {
      page-break-inside: avoid;
    }
    
    p, h2, h3 {
      orphans: 3;
      widows: 3;
    }
    
    h2, h3 {
      page-break-after: avoid;
    }
    
    .no-print {
      display: none !important;
    }
  }
`

组织全局样式 #

分离文件 #

jsx
import { Global, css } from '@emotion/react'
import resetStyles from './styles/reset'
import typographyStyles from './styles/typography'
import utilityStyles from './styles/utilities'

const globalStyles = css`
  ${resetStyles}
  ${typographyStyles}
  ${utilityStyles}
`

function GlobalStyles() {
  return <Global styles={globalStyles} />
}

样式文件示例 #

styles/reset.js:

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

export default css`
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
  
  html {
    scroll-behavior: smooth;
  }
  
  body {
    min-height: 100vh;
    line-height: 1.5;
  }
`

styles/typography.js:

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

export default css`
  body {
    font-family: system-ui, -apple-system, sans-serif;
    -webkit-font-smoothing: antialiased;
  }
  
  h1, h2, h3, h4, h5, h6 {
    line-height: 1.2;
    font-weight: 600;
  }
  
  h1 { font-size: 2.5rem; }
  h2 { font-size: 2rem; }
  h3 { font-size: 1.75rem; }
  h4 { font-size: 1.5rem; }
  h5 { font-size: 1.25rem; }
  h6 { font-size: 1rem; }
`

最佳实践 #

1. 最小化全局样式 #

尽量减少全局样式,优先使用组件级样式:

jsx
const Container = styled.div`
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 16px;
`

2. 使用 CSS 变量 #

结合 CSS 变量实现动态主题:

jsx
/** @jsxImportSource @emotion/react */
import { Global, css } from '@emotion/react'

const cssVariables = css`
  :root {
    --color-primary: #007bff;
    --color-secondary: #6c757d;
    --spacing-unit: 8px;
    --border-radius: 4px;
  }
  
  [data-theme="dark"] {
    --color-primary: #4da3ff;
    --color-secondary: #adb5bd;
  }
`

3. 避免过度使用 #

全局样式应该只用于:

  • CSS 重置
  • 字体导入
  • 基础排版
  • 滚动条样式
  • 打印样式

下一步 #

掌握了全局样式后,继续学习 动画与Keyframes,了解如何在 Emotion 中创建 CSS 动画。

最后更新:2026-03-28