Skip to main content

using media query in React

css in jsではメディアクエリ(@media)でのスタイルの場合分けができないので、 styledRadiumというライブラリを使ってましたが、 typescriptやReactの新しいバージョンへの対応が遅かったり変なエラーが多かったり不便なので、 代わりとして、自作hookのライブラリを公開しました。 use-mediaというリポジトリを参考にしています。

use-mediaについて

入力した値をメディアクエリの文字列に変換してuseRefの中に入れます。 はじめに、メディアクエリの初期値defaultMediaと、css in js をcss (例えばminWidthからmin-width)に直すqueryObjectToString関数をインポートしています。 const isMedium = useMedia({minWidth:500})のようにつかえば、 指定したメディアクエリを満たしているかを判定できるようになります。

import {defaultMedia, queryObjectToString as qO2S, } from '../utils'
export function useMedia (rawQuery={}, defaultState=false) {
const query = useRef( qO2S(rawQuery) );
const [state, set] = useState(defaultState);
useEffect (()=>{
const media = typeof window===undefined
? defaultMedia
: window.matchMedia(query.current)
const onChange =()=> set(Boolean(media.matches))
state && (onChange(), media.addListener(onChange))
return () => media.removeListener(onChange)
}, [])
return state
}

use-gridについて

const fontSize = useGrid({xs:"25px", md:"50px", xl:"75px"})のように使うと、 値の内容を画面のサイズに合わせてtレスポンシブに変化させられます。

import {mockMediaString, queryPropsToList as qP2L } from '../utils'
const useGrid = (props) => {
const queries = useRef( qP2L(props) )
const [state, set] = useState(queries.current[0][1])
useEffect ( () => {
const media = queries.current.map( ([query,value]) => {
const md = typeof window==="undefined"? mockMediaString:window.matchMedia(query)
const fn =()=> Boolean(md.matches) && set(value)
value && (fn(), md.addListener(fn))
return {md, fn}
})
return () => media.map( ({md,fn}) => md.removeListener(fn) )
}, [] )
return state
}

queryObjectToStringについて

css in jsのオブジェクトからcssに変換する関数queryObjectToStringを定義します。 例えば、{minWidth:500}min-width:500のように変換します。

export function queryObjectToString (query) {
if (typeof query === 'string') return query;
const toS = ([key, val]) => {
const feature = key.replace(/[A-Z]/g,s=>`-${s.toLowerCase()}`).toLowerCase();
const isN = typeof val==='number' && /[height|width]$/.test(feature)
return (typeof val==='boolean')
? `${val?'':'not '}${feature}`;
: `(${feature}: ${val}${isN?'px':''})`;
}
return Object.entries(query).map(toS).join(' and ');
}

queryPropsToListについて

"md"(medium)のような文字列からメディアクエリの文字列に変換する関数を定義します。 例えば、useGrid({xs:"ham",lg:"egg"})内で実行されるqP2L([["xs","ham"],["lg","egg"]])は、 [["(min-width:1px)and(max-width:969px)","ham"],["(min-width:970)","egg"]]のように変換します。

export function queryPropsToList ( props ) {
const SIZE = ["xs","sm","md","lg","xl"]
const toN =(key)=> SIZE.find(s=>s===key)? {xs:1,sm:576,md:720,lg:960,xl:1140}[key] : 0
const toS =(key,next)=>`(min-width:${ toN(key) }px)${ next?` and (max-width:${toN(next)-1}px)`:'' }`
const getMedia = (props) => {
const grid = SIZE.map(s=>props.find(p=>p[0]===s)||null).filter((m)=>m!==null)
const xsGr = (grid.length)? grid.find(g=>g[0]==="xs")?[]:[["xs",grid[0][1]]]: []
const noGr = (grid.length)? props.filter(p=>!SIZE.find(s=>s===p[0])) : props
return [...noGr, ...[...xsGr,...grid].map((g,i) => [
toS(g[0],i<grid.length-1?grid[i+1][0]:null), g[1]]) ]
}
return getMedia( props.map( ([key,val]) => [queryObjectToString(key),val] ) )
}