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)