🦁

ブレイクポイントを二重管理しないために

に公開1

こんにちは
株式会社 TAIAN でソフトウェアエンジニアをしています、竹内です

普段 React で Web アプリケーションを書くことが多く、そのほとんどが複数の画面幅を考慮する必要があります。

そういった時 CSS の media query や container query を利用して幅を変えたり非表示にしたりといった対応をしますが、叶えたい仕様によっては DOM の構造が全く違うものを出し分けるシーンがあるかと思います。

そうした場合、React では以下のような実装になると思います。

export function App() {
  const width = useWindowWidth()

  if (width < 640) {
    return (
      <div>...</div>
    )
  }

  return (
     <div>...</div>
  )
}

この640という値をマジックナンバーとしないため定数に定義することが多いと思いますが、同じ値を CSS でも管理してしまわないでしょうか。

// breakpoint.ts
export const breakpoints = {
  sm: 640,
  md: 768,
  lg: 1024
} as const
// global.css
:root {
  --sm: 640px;
  --md: 768px;
  --lg: 1024px;
}

今回はこの値の 2 重管理を避けるべく考えられるいくつかの実装パターンを紹介します。

CSS-in-JS を用いる

CSS の記述を JavaScript(TypeScript)で書けるようにしてしまえば当然単一の値を管理すれば済むようになります。

例えば以下は styled-components を用いた例です。

export const breakpoints = {
  sm: 640,
  md: 768,
  lg: 1024,
} as const

export const media = {
  sm: `@media (min-width: ${breakpoints.sm}px)`,
  md: `@media (min-width: ${breakpoints.md}px)`,
  lg: `@media (min-width: ${breakpoints.lg}px)`,
} as const

const Container = styled.div`
  padding: 16px;

  ${media.sm} {
    padding: 24px;
  }

  ${media.md} {
    padding: 32px;
    max-width: 768px;
    margin: 0 auto;
  }

  ${media.lg} {
    padding: 48px;
    max-width: 1024px;
  }
`

のように単一の値を管理しながら JavaScript の表現力を用いた開発生産性の向上が見込めます。

一方、

  • ランタイム実行によるパフォーマンスの低下
    • vanilla-extractのような Zero Runtime を唄う技術によって解消はされます
  • 文字列テンプレートリテラルや JavaScript のオブジェクトによる css の記述
  • ライブラリ自体の流行り廃りに追従する必要が一定出てくる

などこれらを考慮する必要は出てきます。

JavaScript で管理した値を動的に CSS Custom Property に定義する

CSS-in-JS と同じようにブレイクポイントを表現する値は JavaScript で管理しつつ、アプリケーションの実行時にその値を CSS Custom Property へセットするようなアプローチも取れます。

export const breakpoints = {
  sm: 640,
  md: 768,
  lg: 1024,
} as const

export const setCSSBreakpoints = () => {
  if (typeof document !== 'undefined') {
    Object.entries(breakpoints).forEach(([key, value]) => {
      document.documentElement.style.setProperty(`--bp-${key}`, `${value}px`)
    })
  }
}

export function App() {
  useEffect(() => {
    setCSSBreakpoints()
  }, [])

  return (
    <div className="hoge">...</div>
  )
}
.hoge {
  @media (min-width: var(--bp-sm)) {
    font-size: 16px;
  }

  @media (min-width: var(--bp-md)) {
    font-size: 18px;
  }

  @media (min-width: var(--bp-lg)) {
    font-size: 20px;
  }
}

本アプローチでは値の単一管理を実現できながらシンプルに CSS を記述することができますね。

一方で

  • ハイドレーションを行うようなシーンではさまざまな考慮が必要になるケースがある
  • IE 対応をしなければならないケースでは CSS Custom Property を使えない

といった考慮ポイントがあります。

Tailwind CSS

Tailwind CSSを使うことでも値を単一に管理できます。

// design-tokens/tokens.ts

export const designTokens = {
  breakpoints: {
    sm: 640,
    md: 768,
    lg: 1024,
  }
} as const;
// tailwind.config.ts
import { designTokens } from "./design-tokens/tokens.ts"

module.exports = {
  theme: {
    screens: breakpoints,
  }
}
// App.ts

export function App() {
  return (
    <div className="container mx-auto px-md lg:px-lg">
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-md">
        ...
      </div>
    </div>
  )
}

このdesignTokens.breakpointsもただのオブジェクトのためJavascriptの条件分岐に利用できます。

まとめ

ブレイクポイントの二重管理を避ける方法として、CSS-in-JS、動的CSS Custom Property、tailwindcssの3つのアプローチを紹介しました。

まだまだこれ以外にも方法があると思いますので、ぜひコメントでご紹介いただけると嬉しいです。

We are hiring!

TAIANでは、このような開発・技術・思想に向き合い、未来をつくる仲間を一人でも多く探しています。少しでも興味を持っていただいた方は弊社の紹介ページをご覧ください。

https://taian-inc.notion.site/engineer

TAIANテックブログ

Discussion

天海 はら天海 はら

私も一つ「ブレイクポイントを二重管理しない」の方法を知っている。

CSS に数値を定義、JS で受け取る

:root {
  --sm: 640px;
  --md: 768px;
  --lg: 1024px;
}

.hoge {
  font-size: 14px; 
}

@media (min-width: var(--bp-sm)) {
  .hoge {
    font-size: 16px;
  }
}
// client.js
function getBreakPoints() {
    const getValue = (name) =>
      parseInt(getComputedStyle(document.documentElement).getPropertyValue(name))

    const sm = getValue('--sm')
    const md = getValue('--md')
    const lg = getValue('--lg')

    return {sm, md, lg}
}