Интеграция с Vue
Vue composables включены в основной бандл и активируются, когда Vue установлен как peer-зависимость.
Регистрация плагина
import { createApp } from 'vue'
import { ResponsivePlugin } from 'responsive-media/vue'
const app = createApp(App)
app.use(ResponsivePlugin, {
sm: [{ type: 'max-width', value: 767 }],
lg: [{ type: 'min-width', value: 1024 }],
})
app.mount('#app')Типизированные хуки из вашего конфига
defineResponsive(config, options?)
Применяет config к общему состоянию и возвращает useResponsive, useBreakpoints, useResponsiveValue и plugin, причём состояние и ключи брейкпоинтов выводятся из config — без обобщённого параметра, который нужно писать вручную. Вызовите её в одном модуле и импортируйте хуки оттуда.
// responsive.ts
import { defineResponsive } from 'responsive-media/vue'
export const { useResponsive, useBreakpoints, plugin } = defineResponsive(
{
mobile: [{ type: 'max-width', value: 600 }],
smallTablet: [{ type: 'max-width', value: 850 }],
desktop: [{ type: 'min-width', value: 961 }],
},
{ order: ['mobile', 'smallTablet', 'desktop'] },
)useResponsive()возвращает{ mobile: boolean; smallTablet: boolean; desktop: boolean }.- Хелперы
useBreakpoints()принимают только эти ключи:isAbove('nope')— ошибка компиляции. Обобщённый параметрuseBreakpoints<K>()необязателен и у обычного composable. plugin—app.use(plugin)предоставляет состояние черезRESPONSIVE_KEY, так же какResponsivePlugin.
Плагин принимает опции установки: app.use(plugin, { ssrState, hydration, hydrating }). ssrState — снимок, который отрендерил сервер и с которым гидратируется клиент; hydration — 'immediate' или 'deferred'; hydrating явно сообщает, гидратирует ли приложение серверную разметку; commit ('auto' или 'manual') и commitTimeout решают, когда приходит настоящее состояние, а commitHydration(), возвращаемая рядом с plugin, применяет его в режиме 'manual'. См. SSR и гидратация. На сервере плагин даёт каждому приложению собственное состояние, поэтому параллельные запросы никогда не делят одно.
Вызов заменяет конфигурацию, заданную ранее через ResponsivePlugin или setResponsiveConfig(), потому что у них одно общее состояние. В Nuxt этот вызов делает модуль.
Responsive-состояние
useResponsive<T>()
Возвращает Vue-реактивное responsive-состояние. Реактивно в шаблонах и computed-свойствах. Принимает обобщённый параметр типа для сужения возвращаемого состояния — см. ConfigToState<T>.
<script setup lang="ts">
import { useResponsive } from 'responsive-media/vue'
type MyState = { sm: boolean; lg: boolean }
const state = useResponsive<MyState>()
</script>
<template>
<MobileNav v-if="state.sm" />
<DesktopNav v-else />
</template>Хелперы брейкпоинтов
useBreakpoints()
Возвращает реактивные упорядоченные хелперы брейкпоинтов. Все методы реагируют на изменения вьюпорта в шаблонах. Как и useResponsive() выше, сначала проверяет inject(RESPONSIVE_KEY, null) и только затем откатывается на зеркало глобального синглтона — поэтому кастомное состояние, переданное через provide(RESPONSIVE_KEY, customState), одинаково учитывается обоими composables.
<script setup>
import { useBreakpoints } from 'responsive-media/vue'
const { current, isAbove, isBelow, between } = useBreakpoints()
</script>
<template>
<span>Current: {{ current }}</span>
<DesktopNav v-if="isAbove('sm')" />
<MobileNav v-else />
<TabletOnly v-if="between('sm', 'lg')" />
</template>
current— этоComputedRef<string | null>.isAbove,isBelow,between— обычные функции, реактивные благодаря тому, что читают из Vue-реактивного состояния.
Передача кастомного состояния
RESPONSIVE_KEY
Ключ инъекции, из которого читают и useResponsive(), и useBreakpoints(). Передайте его вручную через provide() (в обход ResponsivePlugin), чтобы направить их на состояние, созданное самостоятельно через createResponsiveState() — полезно для изолированного responsive-контекста, например внутри одного поддерева большого приложения.
<script setup>
import { reactive } from 'vue'
import { provide } from 'vue'
import { RESPONSIVE_KEY } from 'responsive-media/vue'
import { createResponsiveState, TailwindPreset, TailwindOrder } from 'responsive-media'
const customState = reactive(
createResponsiveState(TailwindPreset, { order: [...TailwindOrder] }).getState(),
)
provide(RESPONSIVE_KEY, customState)
</script>Значения по брейкпоинтам
useResponsiveValue(map, fallback?)
ComputedRef<T | undefined>
Возвращает значение первого активного брейкпоинта из map или fallback, если ни один не активен. Решает порядок в map: побеждает первый ключ, равный true, поэтому при пересекающихся брейкпоинтах ставьте самый узкий первым.
<script setup>
import { useResponsiveValue } from 'responsive-media/vue'
const columns = useResponsiveValue({ mobile: 1, tablet: 2, desktop: 4 })
const title = useResponsiveValue({ mobile: 'Меню' }, 'Навигация')
</script>
<template>
<Grid :columns="columns" />
</template>У хуков из defineResponsive() ключи map проверяются по вашему конфигу.
Пользовательские предпочтения
useUserPreferences()
{ dark, light, reducedMotion, highContrast, lowContrast, noHover, coarsePointer, forcedColors, print }
Реактивные булевы значения предпочтений пользователя по внешнему виду и доступности — AccessibilityPreset как готовое состояние, настраивать ничего не нужно. Он читает собственное состояние, поэтому никогда не мешает вашим брейкпоинтам. На сервере каждый ключ равен false.
<script setup>
import { useUserPreferences } from 'responsive-media/vue'
const prefs = useUserPreferences()
</script>
<template>
<Parallax v-if="!prefs.reducedMotion" />
</template>Размер вьюпорта
useViewportSize(options?)
{ width: number; height: number; stop: () => void }
Размер окна в пикселях, реактивный, — для случаев, когда нужно число, а не брейкпоинт. На сервере он 0 на 0.
<script setup>
import { useViewportSize } from 'responsive-media/vue'
const size = useViewportSize({ throttle: 200 })
</script>
<template>
<span>{{ size.width }} × {{ size.height }}</span>
</template>throttle—number· по умолчанию:100. Миллисекунды между обновлениями при изменении размера окна;0обновляет на каждое событие.
Внутри компонента он останавливается при размонтировании; в любом другом месте вызовите stop() сами.
Media Query
useMediaQuery(query)
Ref<boolean> & { stop: () => void }
Возвращает реактивную ссылку для сырой строки CSS media query. Внутри компонента автоматически убирается при onUnmounted. Возвращаемая ссылка также всегда несёт метод .stop() — благодаря этому код, вызывающий useMediaQuery() вне активного экземпляра компонента (обычная фабричная функция, setup() Pinia-стора и т. п.), может очистить слушатель matchMedia вручную, поскольку там нет жизненного цикла компонента, который сделал бы это автоматически.
<script setup>
import { useMediaQuery } from 'responsive-media/vue'
const isDark = useMediaQuery('(prefers-color-scheme: dark)')
const canHover = useMediaQuery('(hover: hover)')
</script>
<template>
<DarkTheme v-if="isDark" />
</template>// Вне компонента — например, обычный модульный хелпер или setup стора
import { useMediaQuery } from 'responsive-media/vue'
const isDark = useMediaQuery('(prefers-color-scheme: dark)')
// ... позже, когда он больше не нужен:
isDark.stop()Реактивный запрос
query может быть строкой, ref или геттером. Когда он меняется, composable убирает старого слушателя и подписывается на новый запрос.
<script setup>
import { ref } from 'vue'
import { useMediaQuery } from 'responsive-media/vue'
const breakpoint = ref(700)
const wide = useMediaQuery(() => `(min-width: ${breakpoint.value}px)`)
</script>Состояние контейнера
useContainerState(elementRef, config, options?)
Отслеживает размеры элемента и возвращает объект реактивного состояния, ключи которого выводятся из config. Автоматически настраивает и убирает ResizeObserver через watchEffect.
<script setup>
import { useTemplateRef } from 'vue'
import { useContainerState } from 'responsive-media/vue'
const cardRef = useTemplateRef < HTMLDivElement > 'card'
const cardState = useContainerState(cardRef, {
compact: [{ type: 'max-width', value: 300 }],
wide: [{ type: 'min-width', value: 600 }],
})
</script>
<template>
<div ref="card">
<CompactLayout v-if="cardState.compact" />
<WideLayout v-else-if="cardState.wide" />
<DefaultLayout v-else />
</div>
</template>