useMediaQuery
미디어 쿼리에 대한 일치 여부를 추적하는 React 훅입니다. 다양한 화면 크기, 디바이스 특성, 사용자 환경 설정에 따라 반응형 UI를 구현할 수 있습니다.
기본 사용법
tsx
import { useMediaQuery, media } from '@teamsparta/react';
function Example() {
const isTabletOrLarger = useMediaQuery(media.min(768));
const isDarkMode = useMediaQuery(media.prefersColorScheme.dark);
return (
<div className={isDarkMode ? 'dark-theme' : 'light-theme'}>
{isTabletOrLarger ? <TabletContent /> : <MobileContent />}
</div>
);
}Props
| 이름 | 타입 | 설명 | 기본값 |
|---|---|---|---|
| query | MediaQuery | 확인할 미디어 쿼리 | - |
| defaultValue | boolean | SSR 환경에서 사용할 기본값 | false |
Return
boolean - 미디어 쿼리 일치 여부 (true 또는 false)
미디어 쿼리 유틸리티 (media)
media 객체는 타입 안전한 방식으로 미디어 쿼리 문자열을 생성하는 유틸리티입니다. 직접 미디어 쿼리 문자열을 작성하는 대신 이 객체를 사용하면 타입 체크와 자동 완성을 통해 안전하게 미디어 쿼리를 작성할 수 있습니다.
카테고리별 미디어 쿼리
화면 크기 관련
| 함수/속성 | 설명 | 생성되는 쿼리 | 예시 |
|---|---|---|---|
min(width) | 최소 화면 너비 | (min-width: {width}px) | media.min(768) |
max(width) | 최대 화면 너비 | (max-width: {width}px) | media.max(767) |
between(minWidth, maxWidth) | 화면 너비 범위 | (min-width: {minWidth}px) and (max-width: {maxWidth}px) | media.between(768, 1023) |
width(width) | 정확한 화면 너비 | (width: {width}px) | media.width(1024) |
디스플레이 방향 및 비율
| 함수/속성 | 설명 | 생성되는 쿼리 | 예시 |
|---|---|---|---|
orientation.portrait | 세로 방향 | (orientation: portrait) | media.orientation.portrait |
orientation.landscape | 가로 방향 | (orientation: landscape) | media.orientation.landscape |
aspectRatio.exact(w, h) | 정확한 화면 비율 | (aspect-ratio: {w}/{h}) | media.aspectRatio.exact(16, 9) |
aspectRatio.min(w, h) | 최소 화면 비율 | (min-aspect-ratio: {w}/{h}) | media.aspectRatio.min(16, 9) |
aspectRatio.max(w, h) | 최대 화면 비율 | (max-aspect-ratio: {w}/{h}) | media.aspectRatio.max(16, 9) |
디바이스 입력 관련
| 함수/속성 | 설명 | 생성되는 쿼리 | 예시 |
|---|---|---|---|
pointer.fine | 마우스 등 정밀한 포인팅 장치 | (pointer: fine) | media.pointer.fine |
pointer.coarse | 터치스크린 등 덜 정밀한 포인팅 장치 | (pointer: coarse) | media.pointer.coarse |
pointer.none | 포인팅 장치 없음 | (pointer: none) | media.pointer.none |
pointer.anyFine | 최소 하나의 정밀한 포인팅 장치 | (any-pointer: fine) | media.pointer.anyFine |
pointer.anyCoarse | 최소 하나의 터치스크린 | (any-pointer: coarse) | media.pointer.anyCoarse |
hover.hover | 호버 기능이 있는 디바이스 | (hover: hover) | media.hover.hover |
hover.none | 호버 기능이 없는 디바이스 | (hover: none) | media.hover.none |
hover.anyHover | 최소 하나의 입력 장치가 호버 지원 | (any-hover: hover) | media.hover.anyHover |
디스플레이 품질
| 함수/속성 | 설명 | 생성되는 쿼리 | 예시 |
|---|---|---|---|
resolution.min(dppx) | 최소 해상도 | (min-resolution: {dppx}dppx) | media.resolution.min(2) |
resolution.max(dppx) | 최대 해상도 | (max-resolution: {dppx}dppx) | media.resolution.max(2) |
resolution.exact(dppx) | 정확한 해상도 | (resolution: {dppx}dppx) | media.resolution.exact(2) |
resolution.retina | 레티나 디스플레이 | (min-resolution: 2dppx) | media.resolution.retina |
resolution.dpi(dpi) | DPI 기반 해상도 | (resolution: {dpi}dpi) | media.resolution.dpi(192) |
colorGamut.srgb | sRGB 색 영역 | (color-gamut: srgb) | media.colorGamut.srgb |
colorGamut.p3 | P3 색 영역 | (color-gamut: p3) | media.colorGamut.p3 |
colorGamut.rec2020 | Rec2020 색 영역 | (color-gamut: rec2020) | media.colorGamut.rec2020 |
사용자 환경 설정
| 함수/속성 | 설명 | 생성되는 쿼리 | 예시 |
|---|---|---|---|
prefersColorScheme.dark | 다크 모드 | (prefers-color-scheme: dark) | media.prefersColorScheme.dark |
prefersColorScheme.light | 라이트 모드 | (prefers-color-scheme: light) | media.prefersColorScheme.light |
prefersReducedMotion.reduce | 모션 감소 선호 | (prefers-reduced-motion: reduce) | media.prefersReducedMotion.reduce |
prefersReducedMotion.noPreference | 모션 감소 선호하지 않음 | (prefers-reduced-motion: no-preference) | media.prefersReducedMotion.noPreference |
prefersReducedTransparency.reduce | 투명도 감소 선호 | (prefers-reduced-transparency: reduce) | media.prefersReducedTransparency.reduce |
prefersReducedTransparency.noPreference | 투명도 감소 선호하지 않음 | (prefers-reduced-transparency: no-preference) | media.prefersReducedTransparency.noPreference |
prefersContrast.more | 높은 대비 선호 | (prefers-contrast: more) | media.prefersContrast.more |
prefersContrast.less | 낮은 대비 선호 | (prefers-contrast: less) | media.prefersContrast.less |
prefersContrast.custom | 사용자 정의 대비 선호 | (prefers-contrast: custom) | media.prefersContrast.custom |
prefersContrast.noPreference | 대비 선호 없음 | (prefers-contrast: no-preference) | media.prefersContrast.noPreference |
prefersReducedData.reduce | 데이터 사용량 감소 선호 | (prefers-reduced-data: reduce) | media.prefersReducedData.reduce |
prefersReducedData.noPreference | 데이터 사용량 감소 선호하지 않음 | (prefers-reduced-data: no-preference) | media.prefersReducedData.noPreference |
환경 관련
| 함수/속성 | 설명 | 생성되는 쿼리 | 예시 |
|---|---|---|---|
invertedColors.inverted | 색상 반전 적용 | (inverted-colors: inverted) | media.invertedColors.inverted |
invertedColors.none | 색상 반전 없음 | (inverted-colors: none) | media.invertedColors.none |
forcedColors.active | 강제 색상 활성화 (고대비 모드 등) | (forced-colors: active) | media.forcedColors.active |
forcedColors.none | 강제 색상 비활성화 | (forced-colors: none) | media.forcedColors.none |
monochrome.any | 흑백 화면 (비트 수 상관없음) | (monochrome) | media.monochrome.any |
monochrome.bits(bits) | 특정 비트 수의 흑백 화면 | (monochrome: {bits}) | media.monochrome.bits(8) |
복합 쿼리
| 함수/속성 | 설명 | 생성되는 쿼리 | 예시 |
|---|---|---|---|
and(...queries) | 모든 조건 충족 (AND) | {query1} and {query2} and ... | media.and(media.prefersColorScheme.dark, media.max(767)) |
or(...queries) | 하나 이상의 조건 충족 (OR) | {query1}, {query2}, ... | media.or(media.orientation.portrait, media.max(767)) |
not(query) | 조건 부정 (NOT) | not {query} | media.not(media.hover.hover) |
유용한 쿼리 조합 예제
| 용도 | 쿼리 | 설명 |
|---|---|---|
| 반응형 디자인 | media.min(768) | 768px 이상 화면 (태블릿/데스크탑) |
| 모바일 전용 | media.max(767) | 767px 이하 화면 (모바일) |
| 태블릿 전용 | media.between(768, 1023) | 768px-1023px 사이 화면 (태블릿) |
| 터치 디바이스 | media.pointer.coarse | 터치스크린 기기 |
| 마우스 기기 | media.pointer.fine | 마우스 사용 기기 |
| 다크 모드 | media.prefersColorScheme.dark | 다크 모드 사용자 |
| 접근성 모션 감소 | media.prefersReducedMotion.reduce | 모션 감소 선호 사용자 |
| 모바일 다크모드 | media.and(media.max(767), media.prefersColorScheme.dark) | 모바일에서 다크 모드 |
| 특수 접근성 | media.or(media.prefersReducedMotion.reduce, media.prefersHighContrast) | 모션 감소 또는 고대비 선호 |
헬퍼 훅
useMediaQuery 기반으로 만들어진 편의 훅들입니다.
화면 크기 관련 훅
| 훅 | 기능 | 미디어 쿼리 |
|---|---|---|
useMinWidth(width) | 최소 너비 이상인지 확인 | (min-width: {width}px) |
useMaxWidth(width) | 최대 너비 이하인지 확인 | (max-width: {width}px) |
useBetweenWidth(minWidth, maxWidth) | 너비 범위 내인지 확인 | (min-width: {minWidth}px) and (max-width: {maxWidth}px) |
useExactWidth(width) | 정확한 너비인지 확인 | (width: {width}px) |
디바이스 입력 관련 훅
| 훅 | 기능 | 미디어 쿼리 |
|---|---|---|
useIsTouchDevice() | 터치 디바이스인지 확인 | (pointer: coarse) |
useIsMouseDevice() | 마우스 디바이스인지 확인 | (pointer: fine) |
useHasHover() | 호버 기능이 있는지 확인 | (hover: hover) |
사용자 환경 설정 관련 훅
| 훅 | 기능 | 미디어 쿼리 |
|---|---|---|
usePrefersDarkMode() | 다크 모드 선호 여부 | (prefers-color-scheme: dark) |
usePrefersLightMode() | 라이트 모드 선호 여부 | (prefers-color-scheme: light) |
usePrefersReducedMotion() | 모션 감소 선호 여부 | (prefers-reduced-motion: reduce) |
usePrefersHighContrast() | 고대비 선호 여부 | (prefers-contrast: more) |
usePrefersReducedData() | 데이터 사용량 감소 선호 여부 | (prefers-reduced-data: reduce) |
디스플레이 특성 관련 훅
| 훅 | 기능 | 미디어 쿼리 |
|---|---|---|
useIsPortrait() | 세로 방향 화면인지 확인 | (orientation: portrait) |
useIsLandscape() | 가로 방향 화면인지 확인 | (orientation: landscape) |
useIsRetina() | 레티나 디스플레이인지 확인 | (min-resolution: 2dppx) |
useSupportsP3ColorGamut() | P3 색 영역 지원 여부 | (color-gamut: p3) |
환경 관련 훅
| 훅 | 기능 | 미디어 쿼리 |
|---|---|---|
useIsForcedColors() | 강제 색상 모드 활성화 여부 | (forced-colors: active) |
useIsInvertedColors() | 색상 반전 활성화 여부 | (inverted-colors: inverted) |
useIsMonochrome() | 흑백 화면 여부 | (monochrome) |
사용 예제
반응형 레이아웃
tsx
function ResponsiveLayout() {
const isMobile = useMaxWidth(767);
const isTablet = useBetweenWidth(768, 1023);
const isDesktop = useMinWidth(1024);
return (
<div className="layout">
{isMobile && <MobileLayout />}
{isTablet && <TabletLayout />}
{isDesktop && <DesktopLayout />}
</div>
);
}다크 모드 지원
tsx
function DarkModeSupport() {
const prefersDarkMode = usePrefersDarkMode();
return (
<div className={prefersDarkMode ? 'dark-theme' : 'light-theme'}>
<h1>안녕하세요!</h1>
<p>현재 {prefersDarkMode ? '다크' : '라이트'} 모드입니다.</p>
</div>
);
}접근성 향상
tsx
function AccessibleButton() {
const prefersReducedMotion = usePrefersReducedMotion();
return (
<button
className={prefersReducedMotion ? 'no-animation' : 'animate'}
aria-label="확인"
>
확인
</button>
);
}디바이스별 인터페이스
tsx
function DeviceAwareInterface() {
const isTouchDevice = useIsTouchDevice();
const hasHover = useHasHover();
return (
<div>
{isTouchDevice ? <TouchFriendlyControls /> : <PreciseControls />}
{hasHover && <HoverEffects />}
</div>
);
}복합 조건 활용
tsx
function SmartUI() {
// 모바일이거나 모션 감소 선호 시 간단한 애니메이션 사용
const useSimpleAnimation = useMediaQuery(
media.or(media.max(767), media.prefersReducedMotion.reduce),
);
// 터치 기능이 있는 큰 화면(태블릿 등)에서는 특별한 UI 제공
const isLargeTouch = useMediaQuery(
media.and(media.min(768), media.pointer.coarse),
);
return (
<div>
<Animations complexity={useSimpleAnimation ? 'simple' : 'rich'} />
{isLargeTouch && <TouchOptimizedUI />}
</div>
);
}유의사항
SSR 지원
서버 사이드 렌더링 환경에서는 defaultValue 값을 사용합니다. 이는 브라우저 환경이 아니기 때문에 실제 미디어 쿼리를 확인할 수 없기 때문입니다.
tsx
// 기본적으로 서버에서는 모바일로 처리 (max-width: 767px)
const isMobile = useMediaQuery(media.max(767), true);호환성
React 18 이상에서는 useSyncExternalStore를 사용하였습니다.
18버전 미만에서는 동작하지 않습니다.
