Hello, my name is: Amy

A Tale of Two Widths

Problem

Have two interrelated things happen at the same window width, one based off of a CSS media query, and the other off of a JavaScript event monitoring the window width.

Original Thoughts

This projects grid is based off of media queries, but once it hit a specific width, I needed JavaScript to shift the position of an element in the DOM.

My thought was this would be easily solved with some straightforward logic.

if (window.innerWidth > 800){
  //move an element around
}
@media (min-width: 800px){
  /* Trigger the grid breakpoints */
}

All seemed to be well, until we started looking at the page in Safari. The element that was changing positions in the DOM wasn't syncing with the media query, which was wrecking havoc on the layout of the page (for about 15px as you resized the browser horizontally).

It seems that the problem lies within the scrollbar, and whether or not the browser decides that it is included in the innerWidth.

The W3C Recommendation for media queries width is that the width of the viewport including the size of a rendered scroll bar (if any). This is also how MDN describes innerWidth working.

Solution

The way I winded up solving this was to change the way the JavaScript to trigger off the presence of a css property rather than the innerWidth property of the window.

The idea being:

@media (min-width: 800px){
  /* Trigger the grid breakpoints, things are changing w/CSS */
  background: rgb(64, 224, 208);
}
//find a specific thing that changed with the CSS breakpoint
var element = document.getElementById('element'),
    style = window.getComputedStyle(element),
    bgColor = style.getPropertyValue('background-color');

if (bgColor === 'rgb(64, 224, 208)') {
  //move an element around
}

The trigger here relies on what the CSS will change with the media query, so the logic is that because the CSS is doing the changing it cannot become out of sync with JavaScript.

I was pressed for time with this problem, and we already had a helper library called Enquire JS loaded in the project so I wound up using that library. In a nutshell, it uses Javascript (with a lot of checks and balances) to respond to CSS media queries.

In this case, I've added a class to the body when the viewport is withing specific widths, then my JS logic is something like:

if ($('body').hasClass('in-tablet')) {
  //do something
}

Takeaways.

Originally I had thought that using jQuery would solve all my problems as my understanding is that it's good at cross browser difficulties; however, using $(window).innerWidth() fixed the problem in Safari but then introduced it into Chrome.

If I needed to solve this problem again, I'd try to write the statement using this clever solution to get the maxium of two ways of calculating the width (presumably the maximum would have the scrollbar, and thus always be the same between browsers).

The most maddening part about this problem is when I went back to try and understand it more I couldn't recreate it.

Since Enquire's library is JS, I'll have to dig through it and figure out exactly they are dealing with this to see if that will help me understand what the problem is.

New Route

Basically right after I delivered the code for this, I ran across (perhaps) a better way to manage this. I've since used it a couple of times with success. Fingers crossed.

Window.matchMedia()

MDN Window.matchMedia

if (window.matchMedia("(min-width: 400px)").matches) {
  /* the viewport is at least 400 pixels wide */
} else {
  /* the viewport is less than 400 pixels wide */
}

Comments