Skip to content

Instantly share code, notes, and snippets.

@wwerner
Created October 30, 2020 10:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wwerner/db0d7eab49e8178e2ae3561432f071d7 to your computer and use it in GitHub Desktop.
Save wwerner/db0d7eab49e8178e2ae3561432f071d7 to your computer and use it in GitHub Desktop.
Provides a `v-responsive-class` Vue directive to toggle css classes based on the containing element's width.
/**
* Provides a `v-responsive-class` Vue directive to toggle css classes based on the containing element's width.
* Think @media queries, but based on the parent element. Useful for web components.
*
* The breakpoints match the ones in Bootstrap:
* * xs: < 576 - Extra small devices (portrait phones, less than 576px)
* * s: >= 576 - Small devices (landscape phones, 576px and up)
* * m: >= 768 - Medium devices (tablets, 768px and up)
* * l: >= 992 - Large devices (desktops, 992px and up)
* * xl: >= 1200 - Large desktops, 1200px and up
* For details, see https://getbootstrap.com/docs/4.4/layout/overview/#responsive-breakpoints
*
* Class selection mimics Bootstrap's breakpoint-and-up semantics: https://getbootstrap.com/docs/4.4/layout/overview/
*
* Note that the parent element's width need to be flexible for this directive to be useful;
* typically you'd be setting the parent width to 100%.
*
* Usage:
* * Set the parent's width to a dynamic value
* * Use the `v-responsive-class` directive on the element to style
* * Pass the directive a map 'class' -> 'breakpoint',
* i.e. if you want to toggle the class 'big' starting from breakpoint 'm',
* you'd say `v-responsive-class='{"big": "m"}'`
*
* Example (using Bulma's column grid):
<div
id='parent'
class="columns is-mobile is-multiline"
style="width: 100%"
>
<div
v-for="col in [1,2,3]"
:key="col"
v-responsive-class="{
'column is-12':'xs',
'column is-6':'s',
'column is-4':'m',
'column':'l'
}"
>
</div>
* Requirements
* * "lodash.throttle": "^4.1.1",
* * "resize-observer-polyfill": "^1.5.1",
*/
import ResizeObserver from "resize-observer-polyfill";
import throttle from "lodash.throttle";
export const breakpoints = {
xs: 0,
s: 576,
m: 768,
l: 992,
xl: 1200,
}
const activeBreakpoint = (currentWidth) =>
Object.entries(breakpoints)
.sort(([_, minWidthA], [__, minWidthB]) => minWidthA - minWidthB)
.filter(([_, minWidth]) => minWidth < currentWidth)
.pop()[0] || breakpoints.xs
export const ResponsiveClass = {
inserted(el, binding) {
if (typeof process === "undefined" || !process.server) { // required to support nuxt
const handleResize = throttle(entries => {
const width = entries[0].target.parentElement.clientWidth;
// remove inactive classes
for (const [classDefs, breakpoint] of Object.entries(binding.value)) {
if (classDefs && breakpoint !== activeBreakpoint(width)) {
el.classList.remove(...classDefs.split(' '));
}
}
// find active classes
const classes = Object.entries(binding.value)
.map(([clazz, breakpoint]) => [clazz, breakpoints[breakpoint]])
.sort(([_, minWidth1], [__, minWidth2]) => minWidth1 - minWidth2)
.filter(([_, minWidth]) => minWidth < width)
.pop()[0]
// add active classes
if (classes) {
el.classList.add(...classes.split(' '))
}
}, 200);
const observer = new ResizeObserver(handleResize);
observer.observe(el);
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment