4
\$\begingroup\$
  • Some JavaScript libraries relies on window innerWidth and innerHeight to do their calculations
  • window.innerWidth is not 100% reliable, which may lead to bugs
  • matchMedia().matches is 100% accurate but it returns a boolean value
  • come around: use matchMedia to verify window.innerWidth value; if it returns false use matchMedia to do a binary search until it finds the correct value

I faced that issue with some libs that relies on window.innerWidth. In cases where I had some media queries the return value of window.innerWidth used by the library would be off and that library would have issues with incorrect value.

I've seen that matchMedia().matches always return the correct value, but it returns a boolean value, not the width value. I've not seen a solution so far for that (maybe some of you know a better solution?), so I came up with a solution using matchMedia.

I created a function getCorrectDimension that performs a binary search around the window innerWidth (or innerHeight) to find the correct value if its value is wrong as you can see below:

const binarySearch = dim => function bin(start, end) {
    const guess = Math.floor((start + end)/2)

    // this checks if we have the correct value, if not it will keep calling itself until there's a match
    if(window.matchMedia(`(${dim}: ${guess}px)`).matches) {
        return guess
    }

    // since it is not a match, then we need to recalibrate the range and call again.
    // for that we check the boolean value using with min-width (height) rule.
    return window.matchMedia(`(min-${dim}: ${guess}px)`).matches 
        ? bin(guess, end) 
        : bin(start, guess)
}

const getCorrectDimension = (dim = 'width', range = 300) => {
    if(dim !== 'width' && dim !== 'height') {
        throw Error('`getCorrectDimension` accepts "width" or "height" as parameter')
    }

    let prop = 'inner' + dim.charAt(0).toUpperCase() + dim.slice(1)

    // here checks if the window.innerWidth or Height it's the correct one
    if(window.matchMedia(`(${dim}: ${window[prop]}px)`).matches) {
        return window[prop]
    }

    // here, since the value is wrong we use binarySearch to find its correct value
    const start = window[prop] - range >= 0 ? window[prop] - range : 0
    const end = window[prop] + range

    return binarySearch(dim)(start, end)

}
\$\endgroup\$
1
\$\begingroup\$

I have never seen innerWidth or innerHeight report the wrong value. There are a few bugs listed for chrome and firefox but they are all closed.

Could you provide any evidence?

UPDATE

Media query matches down to fractions of a CSS pixel while innerWidth returns integer CSS pixel values. This is a problem when devicePixelRatio !== devicePixelRatio | 0

I tried to find a cover all solution, but too much time need to complete so I withdraw the answers content.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ example of the issue: at link if you attach an event listener like window.addEventListener('resize', () => console.log(window.innerWidth)), you can see window.innerWidth value make jumps around 950px, a place with a media query breakpoint. you can play around and look into other sites. I started to look recently into this when checking an issue opened at react-sizes link. \$\endgroup\$ – buzatto Feb 19 '19 at 21:24
  • \$\begingroup\$ @buzatto The only way i can get innerWidth to not match the media query is to use a zoom that results in a fractional pixel size such that matchMedia((min-width: ${innerWidth-1}px) and (max-width: ${innerWidth+1}px)).matches is true but matchMedia((width: ${innerWidth}px)).matches is false. It's not innerWidth that is at fault but the media query. The spec is ambiguous, I will update answer when i have done further research. As it stands searching for a matching integer size will fail if devicePixelRatio !== devicePixelRatio | 0 or the scroll bar size has the same condition. \$\endgroup\$ – Blindman67 Feb 19 '19 at 23:21
  • \$\begingroup\$ you said that it had bugs related to chrome and firefox before, but not anymore. I mostly use chrome on daily basis, I tested now on firefox and edge, no apparent issue on them everything is fine, but on chrome there is 16 px jump on average on that example link. I also have a personal project and only on chrome I see jumps as well, not on firefox or edge. \$\endgroup\$ – buzatto Feb 20 '19 at 1:27
  • \$\begingroup\$ @buzatto Your code as it is in the question can fail if the range argument is smaller than the error eg innerWidth reports 1000, media query passes 1016 and range is 10. Also fails on latest Chrome (win10) on some devicePixelRatio non integer values. Eg devicePixelRatio = 1.100000036 (menu setting 110%) the media query matched 1427.6px but not 1428px the closest integer value (I forget the exact values). When it fails it throws a call stack overflow. \$\endgroup\$ – Blindman67 Feb 20 '19 at 2:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.