Created
October 30, 2020 10:56
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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