How to observe responsive breakpoints

Have you ever wanted to know in which breakpoint you page is, so you may adapt part of the page in ways CSS cannot, such as setting aria attributes?
By using custom properties and media queries, it is simpler than ever.

Let say you have defined the following custom properties, such as in Open Props

  ...
  --size-xs: 360px;
  --size-sm: 480px;
  --size-md: 768px;
  --size-lg: 1024px;
  --size-xl: 1440px;
  ...

You can access those value in javascript and map them to media queries

const breakpoints  = ['--size-xs', '--size-sm', '--size-md', '--size-lg', '--size-xl'];
const styles = window.getComputedStyle(document.body);
const mediaQueries = breakpoints.map(function (size) {
    const value = styles.getPropertyValue(size);
    return window.matchMedia(`screen and (min-width: ${value})`);
});

Because the breakpoints are sorted by ascending screen, the matches property of the media query will be true for all breakpoints less than the current screen size.

More importantly, you can also register an event listener that will be called every time the matches property of the media query changes.

So, what we can do is to find the last breakpoint every time one of the media query changes, and then dispatch an event of our own.

By putting it all together, we get

class Responsive {
    constructor() {
        this.breakpoints = [...arguments];

        const styles = window.getComputedStyle(document.body);
        const mediaQueries = this.breakpoints.slice(1).map(size => {
            const value = styles.getPropertyValue(size);
            const m = window.matchMedia(`screen and (min-width: ${value})`);
            m.addEventListener('change', this);
            return m;
        });

        this.currentBreakpoint = null;
        this.handleEvent = function () {
            const found = mediaQueries.findLastIndex(m => m.matches);
            const newBreakpoint = this.breakpoints[1 + found];
            if (this.currentBreakpoint !== newBreakpoint) {
                const oldBreakpoint = this.currentBreakpoint;
                this.currentBreakpoint = newBreakpoint;
                window.dispatchEvent(new CustomEvent("breakpointhit", { detail: { newBreakpoint, oldBreakpoint } }));
            }
        };
    }
}

And, to use it

window.responsive = new Responsive('--size-xs', '--size-sm', '--size-md', '--size-lg', '--size-xl');
window.addEventListener('breakpointhit', function (e) {
    console.log(`Breakpoint changed from ${e.detail.oldBreakpoint} to ${e.detail.newBreakpoint}`);
});

You may have notice that we skipped the first breakpoint when we created the media query, and that we get the index of the last media query that matches, plus 1.

It is because we assume that the lowest breakpoint is always true.

Leave a comment

Please note that we won't show your email to others, or use it for sending unwanted emails. We will only use it to render your Gravatar image and to validate you as a real person.