【React】Emotionで使えるメディアクエリのutility関数を作ってみた

僕は普段reactのcssは「CSS Modules」で書いているのですが、EmotionやlinariaのようなCSS in JSを扱うこともあります。

CSS Modulesでメディアクエリを書くときはscssのmixinを使っているのですが、CSS in JSのときはベタ書きしていました。

せっかくCSS in JSではTypeScriptの恩恵を受けるれるのに、もったいないと思い今回はEmotionで使えるメディアクエリのutility関数を作ってみたので紹介します。

目次

コードの全体像

まずはコードの全体像は以下になります。

ファイル名はmediaQueries.tsとしています。

/**
 *
 * メディアクエリを返す関数集です
 *
 */

/* ブレイクポイントの変数や型などを設定
 * ========================================================================== */

/**
 *
 * ブレイクポイントはtailwind cssを参考にしている
 * @see {@link https://tailwindcss.com/docs/screens | tailwindcss}
 *
 */
const breakpoints = {
  sm: '640px',
  md: '768px',
  lg: '1024px',
  xl: '1280px',
} as const;

type Breakpoints = typeof breakpoints; // breakpoints変数から型を作成
type Breakpointkeys = keyof Breakpoints; // オブジェクト型のkeyからユニオン型を作成("sm" | "md" | "lg" | "xl")

type ArgumentBreakpoint = Breakpointkeys | number; //引数の型

/**
 *
 * ユーザ定義型ガード
 * "sm" | "md" | "lg" | "xl"のようなユニオン型かを判定する
 *
 */
const isBreakpointkeys = (value: any): value is Breakpointkeys => {
  return value in breakpoints; //breakpointsに対象のプロパティがあるかチェック
};

/* これ以降は実際にimportして使用する関数
 * ========================================================================== */

/**
 *
 * min-widthのメディアクエリ
 * ブラウザのサイズが横幅>=breakpointの場合
 *
 */
export const minScreen = (breakpoint: ArgumentBreakpoint) => {
  const mediaQuery = isBreakpointkeys(breakpoint)
    ? `@media (min-width: ${breakpoints[breakpoint]})`
    : `@media (min-width: ${breakpoint}px)`;
  return mediaQuery;
};

/**
 *
 * min-widthにnot演算子を使用したメディアクエリ
 * ブラウザのサイズが横幅<breakpointの場合
 * max-widthと同じような効果になります
 *
 * モバイルファーストで指定している場合はmax-widthではなmin-widthにnot演算子を組み合わせた方が良い
 *
 */
export const notMinScreen = (breakpoint: ArgumentBreakpoint) => {
  const mediaQuery = isBreakpointkeys(breakpoint)
    ? `@media not all and (min-width: ${breakpoints[breakpoint]})`
    : `@media not all and (min-width: ${breakpoint}px)`;
  return mediaQuery;
};

/**
 *
 * min-heightのメディアクエリ
 * ブラウザのサイズが縦幅>=breakpointの場合
 *
 */
export const minScreenHeight = (breakpoint: ArgumentBreakpoint) => {
  const mediaQuery = isBreakpointkeys(breakpoint)
    ? `@media (min-height: ${breakpoints[breakpoint]})`
    : `@media (min-height: ${breakpoint}px)`;
  return mediaQuery;
};

/**
 *
 * max-widthのメディアクエリ
 * ブラウザのサイズが横幅<=breakpointの場合
 *
 */
export const maxScreen = (breakpoint: ArgumentBreakpoint) => {
  const mediaQuery = isBreakpointkeys(breakpoint)
    ? `@media (max-width: ${breakpoints[breakpoint]})`
    : `@media (max-width: ${breakpoint}px)`;
  return mediaQuery;
};

/**
 *
 * max-widthにnot演算子を使用したメディアクエリ
 * ブラウザのサイズが横幅>breakpointの場合
 *
 */
export const notMaxScreen = (breakpoint: ArgumentBreakpoint) => {
  const mediaQuery = isBreakpointkeys(breakpoint)
    ? `@media not all and (max-width: ${breakpoints[breakpoint]})`
    : `@media not all and (max-width: ${breakpoint}px)`;
  return mediaQuery;
};

/**
 *
 * max-heightのメディアクエリ
 * ブラウザのサイズが縦幅<=breakpointの場合
 *
 */
export const maxScreenHeight = (breakpoint: ArgumentBreakpoint) => {
  const mediaQuery = isBreakpointkeys(breakpoint)
    ? `@media (max-height: ${breakpoints[breakpoint]})`
    : `@media (max-height: ${breakpoint}px)`;
  return mediaQuery;
};

/**
 *
 * orientation landscape
 * ブラウザのサイズが横幅>=縦幅の場合
 * つまりスマホなどの横向きの状態
 *
 */
export const landscape = () => {
  const mediaQuery = `@media screen and (orientation: landscape)`;
  return mediaQuery;
};

/**
 *
 * orientation portrait
 * ブラウザのサイズが縦幅>横幅の場合
 *
 */
export const portrait = () => {
  const mediaQuery = `@media screen and (orientation: portrait)`;
  return mediaQuery;
};

サンプル

CodeSandboxにサンプルを用意しました。

使い方

それでは使い方になります。 当たり前ですが前提として「Emotion」の導入をしていないと使えません。

インポート

mediaQueries.tsを自分の環境の好きな場所に置いてインポートしてください。

import {
  minScreen,
  notMinScreen,
  minScreenHeight,
  maxScreen,
  notMaxScreen,
  maxScreenHeight,
  landscape,
  portrait,
} from 'path/to/mediaQueries';

上記はminScreenやmaxScreenHeightなど色々インポートしていますが、モバイルファーストの観点からほとんどの場合はminScreenのみのインポートでも大丈夫かと思います。

次点でnotMinScreenをインポートしてもいいかも。

//これだけでも多分大丈夫
import { minScreen } from 'path/to/mediaQueries';
関数詳細引数の型
minScreen(breakpoint)min-widthのメディアクエリ
ブラウザのサイズが[横幅>=breakpoint]の場合
number | "sm" | "md" | "lg" | "xl”
notMinScreen(breakpoint)min-widthにnot演算子を使用したメディアクエリ
ブラウザのサイズが[横幅<breakpoint]の場合
number | "sm" | "md" | "lg" | "xl”
minScreenHeight(breakpoint)min-heightのメディアクエリ
ブラウザのサイズが[縦幅>=breakpoint]の場合
number | "sm" | "md" | "lg" | "xl”
maxScreen(breakpoint)max-widthのメディアクエリ
ブラウザのサイズが[横幅<=breakpoint]の場合
number | "sm" | "md" | "lg" | "xl”
notMaxScreen(breakpoint)max-widthにnot演算子を使用したメディアクエリ
ブラウザのサイズが[横幅>breakpoint]の場合
number | "sm" | "md" | "lg" | "xl”
maxScreenHeight(breakpoint)max-heightのメディアクエリ
ブラウザのサイズが[縦幅<=breakpoint]の場合
number | "sm" | "md" | "lg" | "xl”
landscape()orientation landscapeのメディアクエリ
ブラウザのサイズが[横幅>=縦幅]の場合(つまりスマホなどの横向きの状態)
なし
portrait()orientation portraitのメディアクエリ
ブラウザのサイズが[横幅<縦幅]の場合
なし

使用例

インポートした関数(landscapeとportraitを除く)の引数にsmmdlgxlの文字列もしくは400のような数字を渡す。

minScreen('md')

// 以下の文字列が返る
// @media (min-width: 768px)

型定義しているので入力時に補完が効きます。

以下のコードはmin-width: 768pxのときに文字の色が赤になります。

import { css } from '@emotion/react';
import { minScreen } from 'path/to/mediaQueries';

...

const text = css`
 color: black;
 ${minScreen('md')} {
   color: red;
 }
`

基本的にsmmdlgxlの文字列を渡しますが、例外的な対応をする場合は数字を渡して任意のブレイクポイントも指定出来ます。

const text = css`
 color: black;
 //画面幅が1500px以上の場合
 ${minScreen(1500)} {
   color: red;
 }
`

ブレイクポイントについて

mdlgのようなブレイクポイントの中身は以下のようになっています。

const breakpoints = {
  sm: '640px',
  md: '768px',
  lg: '1024px',
  xl: '1280px',
} as const;

ブレイクポイントはtailwindcssのそれを参考にしています。

tailwindcssのブレイクポイント: https://tailwindcss.com/docs/screens

僕はブレイクポイントの数値は自分で好き勝手に決めるよりBootstrapやtailwindcssのようなよく使われているcssフレームワークを参考にすると良いと思っています。

orientationについて

orientationは画面の向きでcssを切り替えるメディアクエリです。

  • portrait(横幅<縦幅、縦向き)
  • landscape(横幅>=縦幅、横向き)

これらの関数も用意しています。 こちらは他のminScreenなどと違い引数は必要ありません。

import { css } from '@emotion/react';
import { portrait, landscape } from 'path/to/mediaQueries';

...

const textA = css`
 color: black;
 ${portrait()} {
   color: red;
 }
`

const textB = css`
 color: black;
 ${landscape()} {
   color: red;
 }
`

最後に

今回はEmotionで使えるメディアクエリのutility関数を紹介してみましたが、linariaでも使用することができました。