Skip to content

Интеграция с Vue ​

Vue composables включены в основной бандл и активируются, когда Vue установлен как peer-зависимость.

Регистрация плагина ​

ts
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 — без обобщённого параметра, который нужно писать вручную. Вызовите её в одном модуле и импортируйте хуки оттуда.

ts
// 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>.

vue
<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.

vue
<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-контекста, например внутри одного поддерева большого приложения.

vue
<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, поэтому при пересекающихся брейкпоинтах ставьте самый узкий первым.

vue
<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.

vue
<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.

vue
<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 вручную, поскольку там нет жизненного цикла компонента, который сделал бы это автоматически.

vue
<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>
ts
// Вне компонента — например, обычный модульный хелпер или setup стора
import { useMediaQuery } from 'responsive-media/vue'

const isDark = useMediaQuery('(prefers-color-scheme: dark)')
// ... позже, когда он больше не нужен:
isDark.stop()

Реактивный запрос ​

query может быть строкой, ref или геттером. Когда он меняется, composable убирает старого слушателя и подписывается на новый запрос.

vue
<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.

vue
<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>