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)
)
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')
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>
@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'))
Demo
https://codesandbox.io/s/kind-grass-93d5q4
If you find it useful, let me know.
Cheers!
Top comments (0)