7

I noticed a difference between css media query definition and the javascript window.matchMedia media query definition:

The css rules are apllied initially to a loaded page.

The rules defined in javascript are not executed after the page load, but only after a new condition is entered.

An example:

I have two different pages with equivalent media query definitions, the first one defined in css and the second one defined in javascript:

the css version (defined in a style element):

@media (min-width: 401px) and (max-width: 600px) { body {background-color: red; } }
@media (min-width: 601px) and (max-width: 800px) { body {background-color: blue; } }

the javascript version (defined either globally or in a function called after body onload):

window.matchMedia("(min-width: 401px) and (max-width: 600px)")
.addListener(function(e) {
  if (e.matches) {
    document.body.style.background = "red";
  }
});

window.matchMedia("(min-width: 601px) and (max-width: 800px)")
.addListener(function(e) {
  if (e.matches) {
    document.body.style.background = "blue";
  }
});

When I load a page and the window is 700 px wide

  • the css version page is blue
  • the javascript version is white and changes its state only after a new condition is met, i.e. the window is sized below 601 px.

How can I force a matching window.matchMedia to execute on page load?

  • Check this https://stackoverflow.com/questions/1818474/how-to-trigger-the-window-resize-event-in-javascript – Asons Apr 23 '18 at 21:29
  • I know window.onresize and body onload and I am aware that I could use those to solve my problem. But I assumed that this is not necessary. I believed window.matchMedia behaves the same as the css solution. I thought registering the queries once with callback functions would and should be enough. – Nathan Perigreth Apr 24 '18 at 07:02

5 Answers5

8

To fire a matchMedia on load, you could do like this instead (with a somewhat cleaner code base).

Stack snippet

<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <script>
      document.addEventListener("DOMContentLoaded", function(e) {
      
        // medias (as an array to make it a little easier to manage)
        var mqls = [
          window.matchMedia("(max-width: 400px)"),
          window.matchMedia("(min-width: 401px) and (max-width: 600px)"),
          window.matchMedia("(min-width: 601px) and (max-width: 800px)"),
          window.matchMedia("(min-width: 801px)")
        ]
        
        // event listeners
        for (var i=0; i<mqls.length; i++){
          mqls[i].addListener(mqh)
        }
        
        // matches methods
        function mqh(){
          if (mqls[0].matches) {
            console.log("CALLBACK (max-width: 400px)");
            document.body.style.background = "green";
          } else if (mqls[1].matches) {
            console.log("CALLBACK (max-width: 600px)");
            document.body.style.background = "red";
          } else if (mqls[2].matches) {
            console.log("CALLBACK (max-width: 800px)");
            document.body.style.background = "blue";
          } else if (mqls[3].matches) {
            console.log("CALLBACK (min-width: 801px)");        
            document.body.style.background = "gray";
          }
          console.log("window.innerWidth: " + window.innerWidth);
        }

        // call once on load
        mqh();
      });
      
    </script>
  </head>

  <body>
  </body>

</html>

Org. src: http://www.javascriptkit.com/javatutors/matchmediamultiple.shtml

Asons
  • 84,923
  • 12
  • 110
  • 165
  • Thanks for your answer. Looks like the same approach in my answer. – Nathan Perigreth Apr 24 '18 at 07:31
  • @NathanPerigreth The main difference is mine doesn't need the `resize` event – Asons Apr 24 '18 at 07:33
  • You're right. But that means you have to 1) explicitly call the match function for the page load and you have to 2) bind the match function as a callback function in case of a page resize. Is that the better solution? – Nathan Perigreth Apr 24 '18 at 07:41
  • @NathanPerigreth If you post the code for your own suggestion, I'll either stick to mine, or delete it and upvote yours :) – Asons Apr 24 '18 at 07:44
  • From your experience, which solution would you prefer? I find both a bit clumsy compared to the css solution (which of course cannot be used when javascript functionality is needed). – Nathan Perigreth Apr 24 '18 at 07:46
  • Then I'd go with the built-in callback I showed, since that one is most likely much more efficient than copy its behavior using `resize` – Asons Apr 24 '18 at 07:48
  • @NathanPerigreth Updated with a somewhat smoother setup :) – Asons Apr 24 '18 at 08:34
3

A callback function bound to window.matchMedia is not called on page load.

A solution to the problem would be:

  • to define a function in which the media queries are explicitly checked via if(window.matchMedia("...").matches){
  • to call that function on page load via <body onload
  • to call that function on resize via window.onresize
3

I was facing the same problem today, and I've used the following solution inspired by Nathan:

const gate = 800

function listener(
  matches,
) {
  document.getElementById('tag').innerHTML = matches ? 'wider' : 'narrower'
}

window.onload=() => {
  const match = window.matchMedia(`(min-width: ${gate}px)`)
  match.addListener(e => listener(e.matches))
  listener(match.matches)
}
<h1>
  This window is 
  <span id='tag'></span>
  than 800px
</h1>

The core concept is to run your listener function once with MediaQueryList.matches passed as a parameter.

And if someone is trying to achieve this with a framework, do remember to register and trigger the listener during the component mount event.

John Cido
  • 426
  • 1
  • 8
  • 7
  • This should be the top answer – Zach Saucier Jul 24 '20 at 13:01
  • 1
    @ZachSaucier -- If you also explain why it should be the top answer, users would have something more substantial to consider an upvote. Also do note, the OP has several media queries to set up, which I can't see how that can be simpler (and better) than I suggested in my answer. – Asons Sep 21 '20 at 13:49
1

This method worked for me

function media(args, callback) {
let mqList = window.matchMedia(args);
function handle(mediaEvent) {
    if (mediaEvent.matches) {
        callback(mediaEvent)
    } else {
        callback(mediaEvent)
    }
}
mqList.addEventListener('change', handle);
handle(mqList) // Initial check
}

Usage

media('(max-width: 920px)', (e) => {
    console.log(e.matches)//false/true
})

From https://css-tricks.com/working-with-javascript-media-queries/

0

In React + TS you would want something like the following. Lodash's throttle is optional.

  const [isSmallScreen, setIsSmallScreen] = useState(false);

  useEffect(() => {
    const throttleUpdate = throttle((ev: MediaQueryListEvent) => setIsSmallScreen(ev.matches), 150);
    // 768 = md, https://ant.design/components/layout#breakpoint-width
    const matchMedia = window.matchMedia('(max-width: 768px)');
    setIsSmallScreen(matchMedia.matches);

    matchMedia.addEventListener('change', throttleUpdate);
    return () => matchMedia.removeEventListener('change', throttleUpdate);
  }, []);
  
  // ...
Yannic Hamann
  • 4,655
  • 32
  • 50