162

As mentioned in the title, is it possible to calculate the vw without the scrollbars in css only?

For example, my screen has a width of 1920px. vw returns 1920px, great. But my actual body width is only something like 1903px.

Is there a way for me to retrieve the 1903px value with css only (not only for direct children of the body), or do I absolutely need JavaScript for this?

Gust van de Wal
  • 5,211
  • 1
  • 24
  • 48
Marten Zander
  • 2,385
  • 3
  • 17
  • 32
  • 2
    but... the scrollbar isn't included in the viewport width!? – Danield Nov 09 '15 at 10:03
  • @Danield sorry, I updated myquestion – Marten Zander Nov 09 '15 at 10:09
  • If we're talking about some artificial scroll-bar which you put there then maybe you're looking for something like this: `width: calc(100vw - scrollbarWidth)` where 'scrollbarWidth' is some fixed value like 15px – Danield Nov 09 '15 at 10:14
  • 2
    @Danield I actually need a cross browser fix where I have the variable width of every native browser scroller – Marten Zander Nov 09 '15 at 10:17
  • That sounds like something that you shouldn't be doing! – Danield Nov 09 '15 at 10:19
  • @Danield and ther is probably no quick fix or work arround for it, I guess? – Marten Zander Nov 09 '15 at 10:57
  • 5
    I am currently working on a page using bootstrap 4 on Chrome v64 and % is the inner width (not including scrollbar) and vw is the outer width (width including scrollbar) -- therefore I cannot use vw and, like the OP, I don't understand why it's happening. – Eric_B May 21 '18 at 19:56
  • Here is a good summary of how a 100vw element will be covered by the vertical scroll bar: https://sbx.webflow.io/100vw-scrollbars – TwoFingerRightClick Jan 24 '23 at 00:49

16 Answers16

176

One way to do this is with calc. As far as i know, 100% is the width including scrollbars. So if you do:

body {
  width: calc(100vw - (100vw - 100%));
}

You get the 100vw minus the width of the scrollbar.

You can do this with height as well, if you want a square that's 50% of the viewport for example (minus 50% of the scollbar width)

.box {
  width: calc(50vw - ((100vw - 100%)/2))
  height: 0
  padding-bottom: calc(50vw - ((100vw - 100%)/2))
}  
Mattias Ottosson
  • 2,090
  • 1
  • 12
  • 7
  • For some reason, despite many answers to this type of question elsewhere, it was this answer that worked the most seamlessly for me - it was so perfect and quick I'm half expecting a massive "gotcha"! – Daniel Nov 04 '16 at 04:19
  • 85
    This is amazing, but unfortunately only works if the element you are sizing is the direct child of `body`, or an element that has the same width as `body` (otherwise `100%` will refer to the wrong element's width). In which case you can simply use percentages. Unless I am missing something? – Nateowami Mar 26 '17 at 05:41
  • After googling around for like 30 minutes this is the only answer that works perfectly! – drake035 Jun 08 '17 at 19:01
  • 8
    Isn't OK for cases when 100vw accounting for scrollbar is really a problem — that is, when you need to put a 100vw block into a container with fixed width. – certainlyakey Jun 09 '17 at 07:22
  • 131
    Math: `100vw - (100vw - 100%) = 100vw - 100vw + 100% = 100%` do I miss something? – Kamil Kiełczewski Nov 08 '17 at 17:34
  • 2
    @KamilKiełczewski that's valid for when you want a full width thing, but he showed an example below that makes sense, `calc(50vw - ((100vw - 100%)/2))` – TKoL Jul 25 '18 at 09:26
  • 6
    Unfortunately this would not work for absolute positioned element since 100% is not available there :( – Hrvoje Golcic Aug 09 '18 at 07:09
  • 1
    Unfortunately this would not work when use `calc` as `height` value since 100% will refer to height. – uzer Apr 03 '19 at 10:45
  • 12
    @TKoL In every case where his solution would actually solve the problem, width:50% would work just as well. Kamil Kiełczewski is right, this solution is useless. – Eliezer Berlin Apr 16 '19 at 08:17
  • 6
    Agree with @KamilKiełczewski I can not understand why this answer has so many voices. 50vw - ((100vw - 100%)/2) = 50vw - (100vw/2 - 100%/2) = 50vw - (50vh - 50%) = 50% – Alexander Petryakov May 04 '19 at 18:17
  • `width: calc(100vw - (100vw - 100%))` makes the width as 20000px in chrome. – Shubh Sheth May 18 '20 at 20:38
  • 2
    @KamilKiełczewski `100vw - (100vw - 100%)` reduces to `100vw - 15px` in CSS, which is not equivalent to `100vw `. The math is correct based on the way operator precedence and unit conversions work while reducing calc()ulations in CSS. – trusktr Aug 19 '20 at 00:14
  • 1
    @EliezerBerlin Mattias is right. See my previous comment. Tested in both Chrome and Edgium. – trusktr Aug 19 '20 at 00:15
  • 1
    @AlexanderPetryakov Mattias is right; the answer is not useless. See my previous comment. – trusktr Aug 19 '20 at 00:16
  • 1
    @AlexanderPetryakov At each reduction, units are converted as necessary and an intermediate calculation is made. `50vw - ((100vw - 100%)/2) = 50vw - ((15px)/2) = 50vw - 7.5px`. Finally, if your viewport is `1000px`, then `50vw - 7.5px = 1000px - 7.5px = 992.5px` which is not equivalent to `50vw = 1000px`. – trusktr Aug 19 '20 at 00:19
  • 8
    @trusktr you are right - in you example `100vw - 15px` is not equivalent to `100vw` but the problem is that this is equivalent to `100%`. For example suppose that 100vw=1015px and 100%=1000px then you have: `100vw - (100vw - 100%) = 1015 - (1015-1000) = 1015-15 = 1000 = 100%` – Kamil Kiełczewski Aug 19 '20 at 07:13
  • 2
    @KamilKiełczewski Oh yeah, that makes sense. :) – trusktr Aug 19 '20 at 22:14
  • @HrvojeGolcic Why do you think it wouldn't work with absolute positioned elements? I thought this is the ideal case for this solution because you then put the element out of the normal document flow and thus 100% is the "real" width of body. For me this solution works perfectly with absolute positioned elements. – Tom Böttger Dec 21 '21 at 10:27
  • @TomBöttger sorry can't remember now but it did not work for me. Maybe you can post your example too, as an alternative answer – Hrvoje Golcic Dec 22 '21 at 22:53
53

I do this by adding a line of javascript to define a CSS variable once the document has loaded:

document.documentElement.style.setProperty('--scrollbar-width', (window.innerWidth - document.documentElement.clientWidth) + "px");

then in the CSS you can use var(--scrollbar-width) to make any adjustments you need for different browsers with/without scrollbars of different widths. You can do something similar for the horizontal scrollbar, if needed, replacing the innerWidth with innerHeight and clientWidth with clientHeight.

user11990065
  • 631
  • 5
  • 2
  • 1
    For me, the size this returns is twice as big as need be. Any hints? Gives me 16px but 8px would be enough... – Fabian Beyerlein Jan 04 '20 at 21:16
  • You kind of answered your own question there. The 16px is most probably the width of the scrollbar, but in some cases you need to divide it by 2 (for example when using negative margins to create a full width overflowing element). You can do it in CSS: `calc(var(--scrollbar-width) / 2)` – Twoch May 25 '21 at 11:42
  • This is awesome, easily the cleanest and most correct solution here. Should be the accepted answer. Thanks! – JBoss Nov 11 '21 at 16:11
  • 4
    This is a very nice solution, but if I'm reading it right this only works if the first page the document loads has a scroll bar already. If it loads a short page --scrollbar-width will be 0, even when the document gets bigger, – GiovanH Feb 05 '22 at 02:17
17

COPY & PASTE solution

Here is an easy drop-in solution based on user11990065's answer to set a css variable --scrollbar-width and keep it updated on resizes. It also gets calculated on DOMContentLoaded and load events so that you don't have to worry about size changes during the initial rendering phase.

You can just copy and paste it to your code as it is vanilla JS (or wrap it in a 'script' tag and paste it directly into your HTML code:

function _calculateScrollbarWidth() {
  document.documentElement.style.setProperty('--scrollbar-width', (window.innerWidth - document.documentElement.clientWidth) + "px");
}
// recalculate on resize
window.addEventListener('resize', _calculateScrollbarWidth, false);
// recalculate on dom load
document.addEventListener('DOMContentLoaded', _calculateScrollbarWidth, false); 
// recalculate on load (assets loaded as well)
window.addEventListener('load', _calculateScrollbarWidth);

If you have dynamic height changes in your page that might show / hide the scrollbar, you might want to look into Detect Document Height Change with which you can trigger the recalculation also on height changes.

As the value is calculated with JS and set to a fixed value you can use it in calc operations in your CSS, like so:

.full-width {
  width: calc(100vw - var(--scrollbar-width));
}

This will give .full-width exactly the available width.

Larzan
  • 9,389
  • 3
  • 42
  • 41
  • In Chrome version 89 `window.innerWidth` & `document.documentElement.clientWidth` are the same value – tonitone120 Apr 03 '21 at 12:34
  • @tonitone120 i can not confirm that, it works fine for me on Win 10, Chrome 89.0.4389.114 64-bit. What OS are you using? Are you sure the scrollbar is visible? If it is not visible the size will be 0 as bothe values are indeed the same :) – Larzan Apr 04 '21 at 13:42
  • OS Catalina (10.15.6). The scrollbar is visible. Maybe I have different settings applied or something :/ – tonitone120 Apr 04 '21 at 16:32
  • @tonitone120 on OSX, as the scrollbar is normally only visible temporarily, transparent and an OVERLAY (does not move the content or change the width of the displayed area) it will show as 0, but that is correct. Compare the screen without the scrollbar visible with the situation when it IS visible, the width of the content stays the same, it does not get reduced. That's why the scrollbarwidth is zero, it does not take space away from the content but is just an overlay! (It should be NOT zero if you ALWAYS show the scrollbar though - set it in OSX -> Preferences -> General) – Larzan Apr 04 '21 at 18:30
  • Ok, fair enough, thank-you for that clarification. The question then becomes, how can I get the width of this 'overlay' scroll-bar cross-browser? – tonitone120 Apr 04 '21 at 18:49
  • This works well for me. I favored doing it as `jQuery(document).ready(function() { document.documentElement.style.setProperty('--sw', (window.innerWidth - document.documentElement.clientWidth) + "px"); });` – CubicInfinity Jun 10 '22 at 20:07
13

According to the specs, the viewport relative length units do not take scrollbars into account (and in fact, assume that they don't exist).

So whatever your intended behavior is, you cannot take scrollbars into account when using these units.

Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
  • Again, `vw`, per spec, are not aware of any scrollbar's existance. So you cannot take them into account. – Madara's Ghost Nov 09 '15 at 10:10
  • 6
    According to the specs, vw does take scrollbar width away UNLESS the overflow is on auto, then it works like "hidden" for the vw value. So if the page must overflow, set it to "scroll". With overflow-x & overflow-y you choose which scrollbar to display. – Kulvar Aug 23 '17 at 23:00
  • 1
    @Kulvar just tried it in chrome, setting the body to `overflow-y: scroll` vs `overflow-y: auto` does not seem to change the calculated value for vw units. Specifically I have something set on my site to 3.82vw, and my computer width is 1920px. With overflow auto, 3.82vw = 73.344px, and then I tried with overflow scroll which also puts out 73.344px, no change, when it should actually put out 72.6946px if you were correct – TKoL Jul 25 '18 at 09:36
  • 100% takes scrollbars into account, so if your case is 100vw, if you can, change it to 100% – Jairo Apr 09 '19 at 09:39
7
body { overflow: overlay; }

If you don't want to overcomplicate things, this might be sufficient in certain situations. At least it fixed my issues well enough, since there was enough whitespace between the content and the viewport edges (Windows scrollbar would overlap your 20-ish most right pixels).

jnaklaas
  • 1,619
  • 13
  • 16
3

Webkit browsers exclude the scrollbars, other include them in the returned width. This may of course lead to problems: for instance if you have dynamically generated content with ajax that add height dynamically, Safari might switch from a layout to another during page visualization... Ok, it doesn't happen often, but it's something to be aware about. On mobile, less problems, cause scrollbars are generally not showed.

That's said, if your problem is calculate exactly the viewport width without scrollbars in all browser, as far as i know, a good method is this:

width = $('body').innerWidth();

having previously set:

body {
    margin:0;
}
Peter
  • 37,042
  • 39
  • 142
  • 198
holden
  • 1,721
  • 12
  • 19
1

100vw = width of the screen with scrollbar 100% = width of the screen without scrollbar

It is always preferable to use calc(100% - 50px) while measuring the screen width. Even on windows browsers where scrollbar is visible directly, return the screen width differently when compare with macOS browsers.

0

It's possible just very "ugly" looking.

First you need to have this script running to get the scrollbar width into a css variable:

document.documentElement.style.setProperty('--scrollbar-width', (window.innerWidth - document.documentElement.clientWidth) + "px");

Now for example if you want "real" 80vw do this:

calc(0.8 * (100vw - var(--scrollbar-width)));

"real" 40vw

calc(0.4 * (100vw - var(--scrollbar-width)));
Nexarius
  • 346
  • 4
  • 11
0

As long as you're not expecting any actual horizontal scroll, you could use this:

body {
  overflow-x: hidden;
}

Which will then just hide the tiny amount of horizontal scroll caused by the auto scrolling Y.

Sinister Beard
  • 3,570
  • 12
  • 59
  • 95
0

I came across this question while looking for an answer for my case.

I wanted to use WordPress's solution to center a div on the viewport with the viewport's width just like .alignfull would normally.

Situation:

<html>
  <body>
    <div class="main">
      <div class="continer">
        <div class="row">
          <div class="col-12">
            <article>
              <div class="content">
                <div class="alignfull-or-alignwide">
                  <p>The content.</p>
                </div>
              </div>
            </article>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

My solution:

html {
    width: 100vw;
    overflow-x: hidden;
}

.alignfull-or-wide {
    margin-right: calc(50% - 50vw);
    margin-left: calc(50% - 50vw);
    width: 100vw;
    max-width: 100vw; // change this for wide or w/e.
}

This solved my problem by making the root of the document as wide as the viewport. With this, you essentially ignore the width of any scrollbar.

By setting to 100vw we eliminate the width of the scrollbar on any platform. By setting the overflow parameter, we prevent any content from being rendered outside of the viewport. By setting margins, we center the left side of the div to it's relative positioned parent. This usually is the center of the viewport too. Then, the negative margin pulls it to the left side of the viewport. By doing the same on the right we create the illusion of the div being centered on the page.

Also something to watch out for: scrollbar-width on csswg.

odil-io
  • 180
  • 9
-1

The vw unit doesn't take the overflow-y scrollbar into account when overflow-y is set to auto.

Change it to overflow-y: scroll; and the vw unit will be the viewport with the scrollbar. Then you can subtract the scrollbar size from the vw value using calc(). You can also define the scrollbar width, so it will be browser-independent.

Only downside to take into account. If the content fits into the screen, the scrollbar is shown anyway. Possible solution is to change from auto to scroll in javascript.

Ambrus Tóth
  • 552
  • 6
  • 14
Ilario Engler
  • 2,419
  • 2
  • 27
  • 40
-1

The only way I found it to work without messing your code with "calc" is to make the container element size to 100vw; Adding a wrapper around the container for overflow-x; This will make the container to be fullwidth like if the scrollbar was over the content.

<!DOCTYPE html>
<html>
<head>
 <style type="text/css">
 html{ overflow-y: scroll; }
 html, body{ padding:0; margin: 0;}
 #wrapper{ overflow-x: hidden; }
 .row{ width: 100vw; }
 .row:after{ clear: both; content: ''; display: block; overflow: hidden; }
 .row-left{ background: blue; float: left; height: 40vh; width: 50vw; }
 .row-right{ background: red; float: right; height: 40vh; width: 50vw; }
 </style>
</head>
<body>

<div id="wrapper">
<div class="row">
 <div class="row-left"></div>
 <div class="row-right"></div>
</div>
</div>


</body>
</html>
SequenceDigitale.com
  • 4,038
  • 1
  • 24
  • 23
-1

No, there's no way to calculate the vw without the scrollbars in CSS.

However, there's a way to solve the 100vw ruined by the scrollbar on Windows issue. You have to create a full-width element, in this case row--full-width, that beelds out of a Flex container. This solution works on both Mac and Windows:

HTML:

<section> 
  <div class="container">
    <div class="row--full-width"></div>
    <div class="row">
      <div class="card">
      </div>
      <div class="card">
      </div>
    </div>
  </div>
</section>

As you can see in the example above, the row--full-width element bleeds out of the container, and it aligns with the header even when there's a scrollbar.

Tested on Edge 18 (Win), Edge 88 (Win/Mac), and Chrome 88 (Win/Mac).

fedecarg
  • 1
  • 2
-3

The easiest way is set the html & body to 100vw:

html, body{ width:100vw; overflow-x: hidden; overflow-y: auto; margin: 0; }

The only problem is the right-most will be cut a little if scrollbar is shown.

gonnavis
  • 336
  • 1
  • 7
  • 16
-3

If the case were something similar to a slider: As posted in many answers, width 100% doesn't take into account the scrollbar, while 100vw does. In the case of having many elements that need to take the width of the window and that are nested inside a container already with 100% window width (or whose natural block width would be such), you can use:

  • Display flex for container
  • Flex: 0 0 100% for child elements
-4

It's not my solution, but helps me create dropdown fullwidth menu with absolute in relative element in not fullwith span.

We should get scroll with in css var in :root and then use it.

:root{
 --scrollbar-width: calc(100vw - 100%);
}


div { margin-right: var(--scrollbar-width); }

https://codepen.io/superkoders/pen/NwWyee

Denis Savenko
  • 829
  • 1
  • 13
  • 29
  • 4
    This only works because the div is at the root. If the div isn't at the root it breaks. This is because calc is resolved when it is referenced, relative to the selector it is referenced in, not where it is defined. codepen.io/Pedr/pen/YorLPM – Undistraction Jun 26 '19 at 18:32