Vue. Responsiveness made easy
Tags: css, vue, frontend, performance
Published: 2023-03-06 01:25:03

### The problem

Sometimes you just want the current responsiveness interval in JavaScript.

Most attempts (and answers on StackOverflow) are placing an event listener on `window.resize` and use some custom logic (a `switch` statement) to output the current interval.

Works, but not ideal. 

Biggest problem is it runs on `window.resize`, which fires tens of times per second  while resizing, creating tiny freezes in page display, a clear sign the browser is struggling.
___
### The solution

A cleaner (and more performant) alternative is to use [`window.matchMedia(queryString)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia). 

It returns an instance of [`MediaQueryList`](https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList).

Binding a callback on this instance's `change` event ensures the callback will only be called when the media query starts/stops matching. 

```ts
const isSm = ref(false)
const queryList = window.matchMedia(
  '(min-width: 640px) and (max-width: 767.9px)'
)
queryList.addEventListener(
  'change',
  ({ matches }) => (isSm.value = matches)
)
```
The listener only runs when the interval changes, not on every `window.resize`. Huge performance gain. 

The other cool thing about it is it doesn't need cleanup. Removing the `MediaQueryList` instance garbage collects its listeners, so nothing remains bound on `<body>` or `window` object after your component unmounts.
___
### Wrap up

I decided to wrap the above as a tiny plugin ([vue-responsiveness](https://www.npmjs.com/package/vue-responsiveness)), taking an optional config object (`interval: min` dictionary - defaults to `Bootstrap_5`'s breakpoints) and returns a reactive object containing current interval and matches.

I added presets for commonly used frameworks: `Bootstrap_3`, `Bootstrap_4`, `Bootstrap_5`, `Bulma`, `Chakra`, `Foundation`, `Ionic`, `Material_Design`, `Materialize`, `Material_UI`, `Quasar`, `Semantic_UI`, `Skeleton`, `Tailwind_CSS`, `Vuetify`, `Windi_CSS`, so you don't have to hunt them down yourself.
___
#### Install

```ts
import {
  VueResponsiveness,
  Presets
} from 'vue-responsiveness'

createApp(App)
  .use(VueResponsiveness, Presets.Tailwind_CSS)
  .mount('#app')

```
Without a preset, it defaults to `Bootstrap_5`

The above exposes a `$matches` reactive object, which can be used anywhere to show/hide things responsively:

`v-if="$matches[key][type]"`, where `key` is one of the intervals (`xs`, `sm`, `md`, `lg`, `xl` and `2xl` in the case of `Tailwind_CSS`) and `type` is `min`, `max` or `only`, all boolean.
___
#### Use in template

```html
<template v-if="$matches.sm.min">
  <!-- or: v-if="$matches.isMin('sm')" -->
  <!-- @media (min-width: 640px) -->
  ...content
</template>

<SomeComponent v-if="$matches.sm.max">
  <!-- or: v-if="$matches.isMax('sm')" -->
  <!-- @media (max-width: 767.9px) -->
  ...content
</SomeComponent>

<div v-if="$matches.sm.only">
  <!-- or: v-if="$matches.isOnly('sm')" -->
  <!-- @media (min-width: 640px) and (max-width: 767.9px) -->
  ...content
</div>
```
`@matches.current` returns the current interval's key.
___
#### Use in `setup()`
```ts
import { useMatches } from 'vue-responsiveness'

const matches = useMatches()

const currentInterval = computed(() => matches.interval)
const trueOnSmOnly = computed(() => matches.isOnly('sm'))
const trueOnMdAndAbove = computed(() => matches.isMin('md'))
```
___
#### Demo

https://codesandbox.io/s/kind-grass-93d5q4
___

If you find it useful, let me know. 

Cheers!