destroytoday.com

100vw and the horizontal overflow you probably didn’t know about

If you use width: 100vw on a website, there’s a good chance the horizontal scrollbar is visible for many users. Setting an element’s width to 100vw does tell the browser to span the entire viewport width, but what is considered the viewport? Many assume that width: 100vw is the same as width: 100%. This is true on a page that doesn’t scroll vertically, but what you might not realize is that the viewport actually includes the scrollbars, too. This isn’t a problem for anyone using a touch device or trackpad, where the scroll indicator overlays the content, but for folks who keep the vertical scrollbar visible, your website is busted.

Luckily, there are two easy ways to avoid this issue along with the humiliation of having a slight horizontal overflow on your website. First, set your OS preferences to always show scrollbars. On macOS, this is in System Preferences > General > Show scroll bars: Always. Setting this preference might seem extreme, but trust me—it’s like taking the red pill. If you work on a team, these 100vw issues always seem to creep into the codebase. It’s better to be the one who notices first and fixes them, rather than receiving an unexpected looping gif of your prized website, scrolling side-to-side in a way that was never intended.

Setting the OS preference will only help you detect the issue, but to fix it, prioritize using width: 100% whenever possible. If you catch yourself going to use width: 100%, then think to yourself, “Oh, I can finally use width: 100vw, which is pretty much the same as width: 100%,” stop what you’re doing and remember this post. If width: 100% is your friend, then width: 100vw is the kid who only pretends to be your friend, so that he can swim in your pool. (I’ve never had a pool, but I know this kid exists from friends of mine who grew up with pools. Also, I am this kid)

Now, there are rare cases where width: 100% doesn’t fit the bill, like a full-width element within a max-width container. If you can’t restructure this layout to apply the max-width to the siblings instead of the container and keep the container full-width, you might need to rely on… JavaScript. I know, I know—needing to use JavaScript when writing CSS does feel like defeat, but unless there’s a vwWithoutTheScrollbarPlease unit in CSS, you’re going to need use JavaScript.

With JavaScript, you can actually calculate the width of the scrollbar and whether it’s visible by comparing two properties—window.innerWidth and document.body.clientWidth. If these are equal, the scrollbar isn’t visible. If these are different, we can subtract the body width from the window width to get the width of the scrollbar:

const scrollbarWidth = window.innerWidth - document.body.clientWidth

We’ll want to perform this both on page load and on resize, in case someone resizes the window vertically and changes the overflow. Then, once we have the scrollbar width, we can assign it as a CSS variable:

document.body.setProperty("--scrollbarWidth", `${scrollbarWidth}px`)

From here, we can use --scrollbarWidth along with our pool friend, 100vw, to create our own viewport variable:

--viewportWidth: calc(100vw - var(--scrollbarWidth));

I still use 100vw here because we don’t want to change this variable often if we don’t need to—especially since it’s a root-level variable that cascades through everything. If I didn’t care about performance, I’d just set --viewportWidth to document.body.clientWidth on every resize event and call it a day, but if I truly didn’t care about performance, I probably wouldn’t care about the horizontal overflow either.

Now that you’re equipped to detect this problem and fix it, there’s one last thing you need to do—warn others when you notice horizontal overflow on their website. I specifically didn’t say “if you notice” because, I promise, you will see it everywhere now. Ever since enabling the OS preference to always show scrollbars, I can’t go a day without visiting a website that doesn’t have a horizontal overflow. I wish I could simply ignore it, but I can’t. Maybe someday, we’ll get a viewport unit that doesn’t factor in the scrollbars, but until then, we can only work around the current one.