CSS: 如何获取浏览器滚动条的宽度? (对于 :hover {overflow: auto} 不错的边距)

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

不知道标题说的清楚不,所以少说几句。我有一些小元素,比方说 div 的 (200px x 400px 常数)。在他们每个人的里面,都有一个段落,大约有 20 行文字。 当然,这对一个可怜的小孩子来说太过分了div。我想做的是:

  1. 通常 divoverflow: hidden 属性.
  2. 鼠标悬停在 (:hover) 上时,此 属性 更改为 overflow: auto;,并出现滚动条。

问题是什么?我想在段落文本和滚动条之间留一点 space(填充或边距)。假设该段落有一个对称的 margin: 20px;。但是:hover触发后,出现滚动条,整个段落右边"scrollbar-width" px向左移动。其他行断句,整个段落看起来都不一样,这很烦人而且不友好。我如何设置我的 CSS,所以悬停后唯一的变化就是滚动条的外观?

换句话说:

/* 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 */

我已经为它做了一个小片段,并在 strong 中解释了我的问题。我希望一切都清楚 :)。 我现在已经搜索了将近两个小时的解决方案,所以我认为我的问题非常独特和有趣。

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>

滚动条宽度因浏览器和操作系统而异,不幸的是 CSS 没有提供检测这些宽度的方法:我们需要使用 JavaScript.

其他人通过测量元素上滚动条的宽度解决了这个问题:

我们创建一个 div .scrollbar-measure,添加一个滚动条,并 return 它的大小。

// 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);

这很简单,但(显然)不纯粹 CSS。

如果有人像我一样通过谷歌搜索 "how to get browser scrollbar width" 找到了这个问题,这里是 jQuery 实现,没有任何额外的 CSS 类:

var div = $("<div style='overflow:scroll;position:absolute;top:-99999px'></div>").appendTo("body"),
    width = div.prop("offsetWidth") - div.prop("clientWidth");
div.remove();

console.log(width);

JSFiddle,如果你愿意。

感谢 的想法

虽然 tomtomtom 的答案非常有效,但它需要一个似乎有些不必要的外部 CSS 片段,而 Yurii 的答案需要 jQuery,这似乎又有点矫枉过正。一个稍微更新和独立的答案(也可以在 JavaScript 模块设置中使用)是:

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;
  }
}

这个 class 可以这样使用:

let scrollbox = new TempScrollBox();

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

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

这里是纯 CSS 解决方案。
我会在您的文本 div 右侧创建一个不可见的 div,其中包含强制滚动条:

<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>

scrollbar-width div 标记为 visibility: hidden 时,它仍然需要 space。

现在当你最终决定显示自己的滚动条时,将display: none添加到不可见的scrollbar-widthdiv,这样就不再需要space了,因此你的 text div 填满了所有可用的 space 并且变宽了。

如果您想验证,请从 scrollbar-width div 的样式中删除 visibility: hidden;。然后你可以看到这个占位符 div 最初是如何使用 space (根据上面的 "for debugging" 样式它显示为红色)并且在悬停时它消失并被文本 div-s 滚动条。

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

innerWidth returns 浏览器视口的宽度,包括滚动条。

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 视口的宽度,不包括滚动条,当用于像 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';

将这两个值相减,就得到了滚动条的宽度。

这个例子通常会 return: 15px

我的解决方案在最新的 Chrome、Mozilla、Edge 和 Opera 中完美运行。不知道 Safari

const scrollbarWidth = window.innerWidth - document.documentElement.offsetWidth

我试过 但效果不是很好。所以我稍微修改了代码,如果他的回答不适合你,试试这个 ;)

export const getScrollbarSize = () => {
  const scrollDiv = document.createElement("div");

  scrollDiv.className = "scrollbar-measure";
  scrollDiv.style.position = "fixed";
  scrollDiv.style.top = "0";
  scrollDiv.style.left = "0";
  scrollDiv.style.right = "0";
  scrollDiv.style.bottom = "0";

  document.body.appendChild(scrollDiv);

  // Get the scrollbar width
  const scrollbarWidth = window.innerWidth - scrollDiv.clientWidth;

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

  return scrollbarWidth;
};

值得一提的是,对于 Webkit/Blink 浏览器来说,一个简单的单行代码是可能的:

getComputedStyle(document.documentElement, '::-webkit-scrollbar').width

这是有效的,因为这些浏览器提供了一个 ::-webkit-scrollbar pseudo element and getComputedStyle 可以检索这些元素的计算样式。

但是,这在 Firefox 中不起作用。

上面的所有解决方案都不适合我,因为我机器上的实际滚动条宽度是分数(16.8 像素),而上面提出的方法(比如 window.innerWidth - document.body.clientWidth)操作整数( discrete) 值,因此返回 17 。这种不精确导致我的页面出现非常负面的视觉效果(我需要在必要时用填充替换滚动条,并且由于这种不精确,每次我都会改变布局)。

为了解决这个问题,我必须编写如下代码:

export const getCurrentDocumentVerticalScrollbarWidth = () => {
  // Create a guaranteed 100% width element, and measure it's real width
  const fullWidthElement = document.createElement('div');
  fullWidthElement.style.height = '0px';
  fullWidthElement.style.width = '100%';
  fullWidthElement.style.visibility = 'hidden';

  document.body.appendChild(fullWidthElement);
  const clientWidth = Number(getComputedStyle(fullWidthElement).width.replace(/[^\d\.]/g, ''));
  document.body.removeChild(fullWidthElement);

  return document.documentElement.offsetWidth - clientWidth;
}

这里我们向 DOM 添加一个临时的(不可见和全角)div,测量它的实际宽度(以像素为单位),然后将其从 DOM 中删除。然后用 window.innerWidth 计算差值 - 我们就可以开始了。

有了这个你应该没问题。不过考虑一下记忆。

无意间看到这个问题,看了答案,除了1个CSS答案外,都是JS答案。我很惊讶没有人正确回答这个问题,所以我假设我误解了这个问题,但如果我没记错的话,OP 想要滚动条宽度的边距...

所以,为什么不这样做:

margin-right: calc(100vw - 100%);
margin-bottom: calc(100vh - 100%);

我认为我什至不需要解释,因为它看起来很明显...100vw 将获得屏幕的整个宽度,而 100% 将获得父元素的宽度(对于这才能工作,也需要 100%——body 标签是默认的),因此减去它会得到应该是滚动条的差异。我们可以将 100% 替换为任何 div 的固定宽度或高度(non-body 标签),我将在下面分叉你的代码片段,以说明我的意思:

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 calc(400px - 100%) 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>

就是这样……它看起来和 OP 的 20px 一样……已经有 7 年了,但答案似乎很简单,我有点想让 OP 让我知道这是否是他们想要的...

在 CSS

中使用的完整解决方案

老问题和许多 JS 解决方案,但没有人真正展示如何在 CSS 中使用它。

步骤 1.

使用任何其他答案计算滚动条宽度并将其保存为根样式 属性。 IE。在你的 JS 文件中:

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

步骤 2.

在 CSS 中使用新创建的变量。 IE。根据 OP 的原始方法:

/* Paragraph margin predicted for future scrollbar on div */
div p {margin: 20px (20px + var(--scrollbar-width)) 20px 50px;}
/* With scrollbar margin is symmetrical */
div:hover p {margin: 20px;} /* With scrollbar */

额外提示: 将相对 DIV 内的 child 扩展到屏幕全宽的常见问题的解决方案。粘贴到您要扩展的 child 中:

width: calc(100vw - var(--scrollbar-width));
margin-left: calc(-50vw + 50% + (var(--scrollbar-width)) / 2);