Skip to content

Instantly share code, notes, and snippets.

@nathanchicken
Last active October 9, 2019 14:21
Show Gist options
  • Select an option

  • Save nathanchicken/9307fd64c4e8bc44f34ac1830c692b54 to your computer and use it in GitHub Desktop.

Select an option

Save nathanchicken/9307fd64c4e8bc44f34ac1830c692b54 to your computer and use it in GitHub Desktop.
Update to media-queries, makes it easier to watch for changes and you can use the media libraries for reactive data, or just use events.
<script>
import { media } from "@parts/media";
export default {
data() {
return {
mobile: media.matches('mobile'),
md: media.matches('md'),
sm: media.matches('sm'),
lg: media.matches('lg'),
xl: media.matches('xl'),
xl2: media.matches('xl2'),
reducedMotion: media.matches('reducedMotion'),
}
},
mounted() {
[
'mobile',
'md',
'sm',
'lg',
'xl',
'xl2',
'reducedMotion',
].forEach( k => {
this[k] = media.matches(k);
this.$emit( k, media.matches(k) );
});
[
'mobile',
'md',
'sm',
'lg',
'xl',
'xl2',
'reducedMotion',
].forEach( k => media.on( k, (matches) => {
this[k] = matches;
this.$emit( k, matches );
}) );
},
render() {
return this.$scopedSlots.default ? this.$scopedSlots.default({
mobile: this.mobile,
md: this.md,
sm: this.sm,
lg: this.lg,
xl: this.xl,
xl2: this.xl2,
reducedMotion: this.reducedMotion,
}) : false;
}
}
</script>
import { media } from "@parts/media";
media.on('md', function( matches ) {
if ( matches ) {
console.log("md now matches");
} else {
console.log("md now DOES NOT match");
}
});
<template>
<div class="p-8">
<div class="p-8 border my-8" v-if="md">md</div>
<div class="p-8 border my-8" v-if="sm">sm</div>
<div class="p-8 border my-8" v-if="lg">lg</div>
<div class="p-8 border my-8" v-if="xl">xl</div>
<div class="p-8 border my-8" v-if="xl2">xl2</div>
</div>
</template>
<script>
import { media } from "@parts/media";
export default {
data() {
return {
md: media.matches('md'),
sm: media.matches('sm'),
lg: media.matches('lg'),
xl: media.matches('xl'),
xl2: media.matches('xl2'),
}
},
mounted() {
[
'md',
'sm',
'lg',
'xl',
'xl2',
].forEach( k => this[k] = media.matches(k) );
[
'md',
'sm',
'lg',
'xl',
'xl2',
].forEach( k => media.on( k, (matches) => this[k] = matches ) );
}
}
</script>
// NEW NODE / BROWSERIFY VERSION
const mq = ('matchMedia' in window);
const generateRule = function( query ) {
if (typeof query === "string") {
return query;
}
if (query.hasOwnProperty('max') && query.hasOwnProperty('min')) {
return `(min-width: ${query.min}) and (max-width: ${query.max})`;
}
if (query.hasOwnProperty('min')) {
return `(min-width: ${query.min})`;
}
if (query.hasOwnProperty('max')) {
return `(max-width: ${query.max})`;
}
return false;
};
export class MediaQueries {
constructor(config, debug) {
if (!mq) {
console.error("matchMedia not supported on this browser");
return false;
}
const defaults = {
desktop: {
min: "1367px"
},
laptop: {
max: "1366px"
},
tablet: {
max: "1024px"
},
mobile: {
max: "480px"
},
};
this.breakpoints = config || defaults;
this.queries = {};
Object.entries( this.breakpoints ).forEach( ([ key, query ]) => {
console.log( key, query );
console.log( generateRule( key ) );
this.queries[ key ] = window.matchMedia( generateRule( query ) );
});
return this;
}
_getMatchMedia( key ) {
return this.queries[ key ] || false;
}
matches(key) {
return this._getMatchMedia( key ).matches;
}
on(key, fn) {
const mediaQuery = this._getMatchMedia( key );
if (!mediaQuery) {
console.error(`${key} not found`);
return;
}
console.log( mediaQuery, key );
mediaQuery.addEventListener('change', function() {
fn(mediaQuery.matches);
});
}
}
export default { MediaQueries };
import { MediaQueries } from "@libs/media-queries";
const screens = {
mobile: { max: '639px' },
sm: { min: '640px' },
md: { min: '768px' },
lg: { min: '1025px' },
xl: { min: '1441px' },
xl2: { min: '1681px' },
};
const m = new MediaQueries( screens );
export const media = m;
export default { media: m };
@nathanchicken
Copy link
Author

base-media-query.vue is an example of a reusable component that will provide either a scoped slot to access the media queries, or it'll emit events with the same name, eg:

<base-media-query @mobile="isMobile = $event" />

or

<base-media-query v-slot="{ md, sm, lg, xl, xl2, mobile, reducedMotion }">
	<div>
		<div class="p-8 border my-8" v-if="reducedMotion">Reduced Motion</div>
		<div class="p-8 border my-8" v-if="xl2">xl2</div>
		<div class="p-8 border my-8" v-else-if="xl">xl</div>
		<div class="p-8 border my-8" v-else-if="lg">lg</div>
		<div class="p-8 border my-8" v-else-if="md">md</div>
		<div class="p-8 border my-8" v-else-if="sm">sm</div>
		<div class="p-8 border my-8" v-else-if="mobile">mobile</div>
	</div>
</base-media-query>

@nathanchicken
Copy link
Author

There's a big problem here where there's a few assumptions and the parts are split, eg the media queries config, the media library, and the vue component. So, possibly he base-media-query component really needs to include the library functionality on its own, and perhaps get its config from somewhere, but this works within a smaller website project at least where we might use the media thing.

Vue 3 and the composition API will make all this a lot simpler though!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment