Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Window.matchmedia listener firing twice

I'm trying to write some javascript which will change some values held within a JS config object at certain browser breakpoints.

I have stored the window.matchmedia tests within the config object and then I'm looping over this object's keys to add an event listener to each test like so:

Object.keys(config.mediaQueries).map((key) =>{
     config.mediaQueries[key].addListener(function(){
         console.log("breakpoint change");
     });
});

https://codepen.io/decodedcreative/pen/YQpNVO

However when the browser is resized and these listener callback functions run, they appear to run twice. Check the CodePen above with your Console open and you'll see what I mean.

Does anyone know what I've done wrong here?

like image 628
James Howell Avatar asked Jun 29 '17 16:06

James Howell


3 Answers

To answer your direct question, you haven't done anything wrong. And the JS is doing exactly what it's supposed to do.

trld There are 2 events firing but only one contains a matching media query.

What's happening is that when the browser hits a breakpoint there are 2 events being logged. As an example, let's consider the case of when the browser is being resized from 1250px down to 1150px. When the window hits 1199px in width your function:

Object.keys(config.mediaQueries).map((key) =>{
  config.mediaQueries[key].addListener(function(event){
    console.log("breakpoint change");
  });
});

will log 2 events. If you dive into this deeper and log the events with:

Object.keys(config.mediaQueries).map((key) =>{
  config.mediaQueries[key].addListener(function(event){
    console.log(event);
  });
});

you'll see some more information on the media queries. I've boiled the event object down to the important props below.

// First event
matches: true
media: "(max-width: 1199px) and (min-width: 992px)"

// Second event
matches: false
media: "(min-width: 1200px)"

What's happening here is 2 events are being logged, but only one of the events contains the matching query.

So, if we wanted to improve your script for logging, you could check the matches property:

Object.keys(config.mediaQueries).map((key) =>{
  config.mediaQueries[key].addListener(function(event){
    if (event.matches) { 
      console.log(event);
    }
  });
});

With this small change only the matching media query will be logged.

like image 159
Brett DeWoody Avatar answered Nov 08 '22 18:11

Brett DeWoody


If you change your console.log("breakpoint change"); to console.log(key, "breakpoint change");, you'll see that both the xs and s queries are being triggered when you manually resize the browser window.

If you only want your callback to fire once within a time window, you'll need to throttle the events or achieve the behavior in some other way.

Edit: Tomasz Bubała and Brett DeWoody's answers both point to the event.matches property as the proper and more .matchMedia specific solution to this problem.

like image 30
Noah Freitas Avatar answered Nov 08 '22 18:11

Noah Freitas


Seems like the event is firing just before resizing and right after resizing. Add an if statement if you want to log "breakpoint" change right after it changed.

Edit: Noah Freitas might be right about the event firing for 2 keys when resizing. Still e.matches returns true if it matches the key so it should work fine

    function(e) {
      if(e.matches) {
        console.log("breakpoint change");
      }
    }
like image 35
Tomasz Bubała Avatar answered Nov 08 '22 18:11

Tomasz Bubała