DEV Community

Andrei Gheorghiu
Andrei Gheorghiu

Posted on • Edited on

Vue. Responsiveness made easy

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

It returns an instance of MediaQueryList.

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

const isSm = ref(false)
const queryList = window.matchMedia(
  '(min-width: 640px) and (max-width: 767.9px)'
)
queryList.addEventListener(
  'change',
  ({ matches }) => (isSm.value = matches)
)
Enter fullscreen mode Exit fullscreen mode

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), 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

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

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

Enter fullscreen mode Exit fullscreen mode

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

<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>
Enter fullscreen mode Exit fullscreen mode

@matches.current returns the current interval's key.


Use in setup()

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'))
Enter fullscreen mode Exit fullscreen mode

Demo

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


If you find it useful, let me know.

Cheers!

Top comments (0)