CSS: How to get browser scrollbar width? (for :hover {overflow: auto} nice margins)

53,332

Solution 1

Scrollbar widths can vary between browsers and operating systems, and unfortunately CSS does not provide a way to detect those widths: we need to use JavaScript.

Other people have solved this problem by measuring the width of the scrollbar on an element:

We create a div .scrollbar-measure, add a scrollbar, and return its size.

// Create the div
var scrollDiv = document.createElement("div");
scrollDiv.className = "scrollbar-measure";
document.body.appendChild(scrollDiv);

// Get the scrollbar width
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
console.warn(scrollbarWidth);

// Delete the div
document.body.removeChild(scrollDiv);

This is fairly straightforward, but it is (obviously) not pure CSS.

Solution 2

innerWidth returns the width of the browser viewport including the scrollbar.

The read-only Window property innerWidth returns the interior width of the window in pixels. This includes the width of the vertical scroll bar, if one is present.

clientWidth returns the width of viewport, excluding the scrollbar, when used on a root element like document.

When clientWidth is used on the root element (the element), (or on if the document is in quirks mode), the viewport's width (excluding any scrollbar) is returned.

let scrollbarWidth = (window.innerWidth - document.body.clientWidth) + 'px';

Subtract the two values and voila, you get the width of the scrollbar.

This example would usually return: 15px

Solution 3

Whilst tomtomtom's answer works fantastically, it requires an external CSS snippet that seems somewhat unnecessary, and Yurii's answer requires jQuery, which again seems sorta overkill for this. A slightly more up-to-date and self-contained answer (which can also be used in a JavaScript module setup) is:

class TempScrollBox {
  constructor() {
    this.scrollBarWidth = 0;

    this.measureScrollbarWidth();
  }

  measureScrollbarWidth() {
    // Add temporary box to wrapper
    let scrollbox = document.createElement('div');

    // Make box scrollable
    scrollbox.style.overflow = 'scroll';

    // Append box to document
    document.body.appendChild(scrollbox);

    // Measure inner width of box
    this.scrollBarWidth = scrollbox.offsetWidth - scrollbox.clientWidth;

    // Remove box
    document.body.removeChild(scrollbox);
  }

  get width() {
    return this.scrollBarWidth;
  }
}

This class can then be used like so:

let scrollbox = new TempScrollBox();

console.log(scrollbox.width) //-> 17 (Windows, Chrome)

Fiddle: https://jsfiddle.net/nu9oy1bc/

Solution 4

Pure CSS solution here.
I would create an invisible div on the right of your text div with forced scrollbar inside it:

<div class="container">
  <div class="text">Your very and very long text here</div>
  <div class="scrollbar-width"></div>
</div>
<style>
.container {
   display: flex;
   width: 100px; /* to make scrollbar necessary */
   height: 30px; /* to make scrollbar necessary */
   border: 1px solid blue;
}
.text {
   flex: 1; 
   float: left;
   overflow-y: hidden; 
}
.scrollbar-width {
   float: left; 
   overflow-x: hidden; 
   overflow-y: scroll; 
   visibility: hidden;
}
.scrollbar-width::-webkit-scrollbar { /* for debugging */
  background-color: red;
}
.text:hover {
   overflow-y: scroll; 
}
.text:hover + .scrollbar-width {
  display: none;
}
</style>

When the scrollbar-width div is marked with visibility: hidden it will still take space.

Now when you finally decide to show your own scrollbar, add display: none to the invisible scrollbar-width div, so it does not take space anymore, and therefore your text div fills all available space and becomes wider.

In case you wish to verify, remove visibility: hidden; from the style of scrollbar-width div. Then you can see how this placeholder div is initially taking space (it appears red according to the "for debugging" style above) and on hover it disappears and is replaced by the text div-s scrollbar.

JS fiddle link: https://jsfiddle.net/rb1o0tkn/

Solution 5

My solution, works perfectly in latest Chrome, Mozilla, Edge, and Opera. Don't know about Safari

const scrollbarWidth = window.innerWidth - document.documentElement.offsetWidth
Share:
53,332

Related videos on Youtube

Jacek Kowalewski
Author by

Jacek Kowalewski

Hello! I'm an All-end developer, and founder of Flowtar Studio.

Updated on April 04, 2022

Comments

  • Jacek Kowalewski
    Jacek Kowalewski about 2 years

    I'm not sure if title is clear, so few words of explanation. I've got few little elements, let say div's (200px x 400px CONSTANT). Inside each of them, there is a paragraph with about 20 lines of text. Of course, this it to much for a poor little div. What I want to do is:

    1. Normally div has overflow: hidden property.
    2. On mouse over (:hover) this property is changed to overflow: auto;, and the scrollbar appears.

    What is the problem? I want a little space (padding or margin) between the paragraph text and the scrollbar. Let's say that paragraph has a symetrical margin: 20px;. But after :hover triggers on, the scrollbar appears, and the whole right side of the paragraph is moved "scrollbar-width" px to the left. Sentences are broken in other lines, and the whole paragraph look different, which is quite annoying and not user friendly. How can I set my CSS, so the only change after hover will be the appearance of scroolbar?

    In other words:

    /* Normally no scrollbar */
    div {display: block; width: 400px; height: 200px; overflow: hidden;}
    /* On hover scrollbar appears */
    div:hover {overflow: auto;}
    
    /* Paragraph margin predicted for future scrollbar on div */
    div p {margin: 20px (20px + scrollbarwidth px) 20px 50px;}
    /* With scrollbar margin is symmetrical */
    div:hover p {margin: 20px;} /* With scrollbar */
    

    I have done a little snippet for it, with exaplanation of my problem in strong. I hope everything is clear :). I'm searching for a solution for almost two hours now, so I think my question is quite unique and interesting.

    div.demo1 {
      width: 400px;
      height: 200px;
      overflow: hidden;
      background: #ddd;
    }
    div.demo1:hover {
      overflow: auto;
    }
    div.demo1 p {
      background: #eee;
      margin: 20px;
    }
    div.demo2 {
      width: 400px;
      height: 200px;
      overflow: hidden;
      background: #ddd;
    }
    div.demo2:hover {
      overflow: auto;
    }
    div.demo2 p {
      background: #eee;
      margin: 20px 40px 20px 20px;
    }
    div.demo2:hover p {
      margin: 20px 20px 20px 20px;
    }
    <div class="demo1">
      <p>
        This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.
        This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.
        This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.
        This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.
        This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.
      </p>
    </div>
    <br>
    <br>
    <strong>As you can see, on hover right side of the paragraph "moves" left. But I want something like:</strong>
    <br>
    <br>
    <div class="demo2">
      <p>
        This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.
        This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.
        This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.
        This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.
        This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.
      </p>
    </div>
    
    <strong>But with a "perfect" scrollbar width (in this example I used 20px)</strong>

    • Jacek Kowalewski
      Jacek Kowalewski over 9 years
      BTW. I know that I can hardcode the paragraph width and use for example 350px, but this is not the solution that I want - it is ugly and assumes that the max width of scrollbar is only 50px :P. Thx for your time, best regards.
    • SW4
      SW4 over 9 years
      JS is the only option- as such possible duplicate: stackoverflow.com/questions/13382516/…
  • Jacek Kowalewski
    Jacek Kowalewski over 9 years
    Your answer is really great, +1 from me. However if you don't mind I will wait few hours with accept, maybe someone will come with a pure CSS solution. If not, I will of course accept it. Thanks for your time, and great reasearch.
  • mtx
    mtx almost 7 years
    i tried this code on Linux and Chrome and console warning shows 0 (zero)... any idea what could be wrong?
  • Koh
    Koh over 6 years
    @mtx Did you define the CSS for your scrollbar-measure class? It probably hadn't overflown. The jsfiddle should help.
  • TacB0sS
    TacB0sS over 5 years
    very nice clean code... works on mac.. wonder if this works on mobile browsers as well!!
  • Ferrybig
    Ferrybig over 5 years
    This fails to account for the systems where the scrollbar width is dynamic, like on ma OS there is an option to only show the scrollbar when hovering. As there is no hover on your DIV, it thinks the scrollbar width is 0
  • matt freake
    matt freake over 4 years
    Adding an explanation would improve this answer
  • Raphael Parent
    Raphael Parent over 2 years
    Simplest and easiest method, using this to set a CSS variable on the body (or the appropriate element) and using it in the stylesheet has proven to be reliable on my end.
  • Chris
    Chris over 2 years
    I believe this only works if the document.body has 100% width. It will not work if your document.body has a max-width. Dennis Icllchishin's works if your document.body has a max-width: const scrollbarWidth = window.innerWidth - document.documentElement.offsetWidth
  • krulik
    krulik over 2 years
    most accurate solution
  • Kostiantyn Ko
    Kostiantyn Ko about 2 years
    this returns auto in my Chrome
  • CanonicEpicure
    CanonicEpicure about 2 years
    hm.. yup, same here. something must have changed since this answer (chrome version obsviously)