TIL: Fixing horizontal scrolls due to full bleed blocks without overflow: hidden

A while back I worked on a project with an existing seven year old codebase and it was my task to refresh the design of it without a rebuild. This meant that I found a few constraints and one of them was allowing full bleed components (like a newsletter banner) which didn’t exist before.

Luckily, Andy Bell shared how to create a full bleed CSS utility which was what I ended up using. Towards the end of the article, in the section how the “full-bleed” utility works, Andy points out that using that CSS method might allow the possibility of having horizontal scrollbars and suggests using “overflow-x: hidden” in the body tag to fix it.

Since this all worked fine and as expected, I overlooked the reason why having to hide the overflow was necessary. Tepy Thai explains why 100vw causes horizontal scrollbar:

When you set an element's width to 100vw, the element's width now will be the whole view width of the browser and the important part is 100vw does not include the vertical scrollbar's width into its calculation at all. Therefore, when there is a vertical scrollbar, the total width will be the sum of the element's width and the vertical scrollbar's width, which causes the overflow on the x-axis and thus the horizontal scrollbar.

Tepy Thai, Why 100vw causes horizontal scrollbar

And then last week I was presented with a problem. Another developer in this project messaged me and asked if we could delete the overflow-x: hidden from the body. They were trying to build a feature that uses position: sticky and as it turns out, there is a ticket reporting that position sticky inside overflow hidden doesn’t work.

A solution could be to do a refactor and create a full-bleed layout using CSS grid like Joshua Comeau suggests. Due to a variety of constraints a refactor wasn’t possible.

After making the overflow visible, I needed to fix the horizontal scrollbar and that led me to an article from Jonnie Hallman called “100vw and the horizontal overflow you probably didn’t know about”. My solution was based on the one presented in that article.

I’m not feeling 100% confident over my solution - but it works!

Solution (I think)

Javascript

//remove small horizontal scrollbar when a block is full bleed
var scrollbarWidth = window.innerWidth - document.body.clientWidth;
var halfScrollbarWidth = scrollbarWidth / 2;
document.body.style.setProperty('--scrollbarWidth', `${scrollbarWidth}px`);
document.body.style.setProperty('--halfScrollbarWidth', `${halfScrollbarWidth}px`);

My javascript steps were similar to the ones in Jonnie Hallman’s article except that when I needed to set the margin-left I needed to take into account the possible existence of the scrollbar. After some poking, it looked like half of the width of the scrollbar would fix that.

CSS

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

/* finalHalfScrollbar: value must be negative */
--finalHalfScrollbar: calc(var(--halfScrollbarWidth) * -1);

width: 100%;
width: calc(100vw - 15px);
width: var(--viewportWidth);
margin-left: 0;
margin-left: calc(50% - 50vw - var(--finalHalfScrollbar, -7px));

As a fallback - which I am not very confident about - in case javascript is disabled, is giving the scrollbar a width of 15px and then I used half of it as an integer (the 15px was the most common value from the browsers I tested). I also tried to cover the scenario where CSS custom properties and vw properties weren't supported, so in this case it would be contained within the width of the parent. It seems to work and I suppose that the worst case scenario is a horizontal scrollbar in the end.

There's a good chance there's a better solution for this but I did learn something regardless and do enjoy when CSS makes me scratch my head!

30