CSS 100%宽度但避免滚动条

44

我在尝试构建一个可按多种方式定位和调整大小的小部件。它有一个相当简单的布局-固定高度的页眉、固定高度的页脚和填充其余空间的主体。总宽度和高度会有所变化,主体内容需要滚动。目前我已经设置好了整个容器、页头、页脚和主体的尺寸。

但是我希望主体在需要滚动时可以滚动而不会使内容向左缩小以适应滚动条。也就是说,我希望主体的宽度尽可能地大,减去需要滚动时出现的滚动条的宽度,这样当它需要滚动时,就不会出现收缩。实际上,我想要的效果是这样的:

| - - - unknown width - - -|
+--------------------------+
| content                |*|
| might                  |*|
| scroll                 |*|
+--------------------------+

我希望可能滚动的内容宽度最大,但需要减去潜在滚动条的宽度(|*| 区域)。

目前的情况类似于这样:

<div id="content" style="position: absolute; overflow-y: auto; width: 100%">
    <div id="scrollContent" style="???">
    </div>
</div>

我已尝试使用边距、内边距,甚至是内部 div 的百分比宽度,并尝试了各种方法来“移动”。 我还需要这在 FF3、IE7/8 和(幻想?)IE6 中正常工作。


3
有人知道如何实现这个吗?似乎是一个常见的问题。 - Neil
看这个,可能会有用。https://dev59.com/f2cs5IYBdhLWcg3wOBbl#41969534 - user3280060
13个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
23

使用overflow:scroll替代overflow:auto - 这将始终强制出现滚动条。


11
那确实可以防止改变/收缩,但我真的希望不必这样做。 - n8wrl
4
这真的是只用CSS能做到的最好的了。 - casablanca
1
我使用了 overflow: scroll;width: 100%;,但元素仍然太宽(出现了水平滚动条)。 - Aaron Franke

18

Mattias Ottosson的答案给出了关键信息 - vw单位基于包括滚动条在内的视口宽度,而百分比将基于不包含滚动条占用空间的可用宽度。换句话说,对于一个占据整个页面宽度的元素,滚动条的宽度可以表示为calc(100vw - 100%)

如果我们有一个占用可用宽度的100%的顶级元素,那么我们可以使用这个来控制当滚动条变得可见时什么会改变大小。假设我们的目标布局如下:

.app {
  display: grid;
  grid-template-columns: 1fr 50vh 1fr;
}

我们希望中间列宽度为视口高度的50%,其余宽度分配给左右两列。如果我们这样做,那么添加滚动条意味着水平空间会损失给滚动条(在Chrome上约为15px),左右两列的宽度将相应减少。当ui更改导致滚动条出现而网格中的主内容保持不变或类似时,这可能会导致丑陋的移位。请参见下面的第一个代码段。

我们可以使用计算出的滚动条宽度来缩小右侧列的宽度:

.app {
  display: grid;
  grid-template-columns: calc((100vw - 50vh)/2) 50vh calc(100% - (50vh + 100vw)/2);
}

请查看下面的第二个代码片段。不幸的是,这意味着无法使用 fr 单位,列的宽度必须手动指定。在这种情况下,左列的宽度是视口宽度的一半减去中央列占用的 50vh。右列的宽度是剩余的空间,即左列和中央列的宽度相加后从可用宽度(100% 而不是 100vw)中减去。这在以下公式中更清晰地表示:

calc(100% - ((100vw - 50vh)/2) - (50vh))

这将简化到上面那个

First snippet, ugly jump when scrollbar appears

$('button').click(() => {
  $('.footer').toggle()
})
body, html {
  height: 100%;
  width: 100%;
  padding: 0;
  margin: 0;
  overflow: auto;
  font-family: 'Archivo', sans-serif ;
}
.app {
  margin: auto;
  display: grid;
  grid-template-columns: 1fr 50vh 1fr;
  text-align: center;
  height: 100%;
  width: calc(100% - 10px);
}
.left-column, .center-column, .right-column {
  padding: 10px;
  min-height: 50vh;
  border: 1px solid black;
}
.left-column {
  border-right: none;
  background-color:#def;
}
.center-column {
  background-color:#e1ebbd;
}
.right-column {
  text-align: left;
  border-left: none;
  background: #fb1;
}
.footer {
  grid-column: 1 / span 3;
  height: 2000px;
  background: #753;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

</head>
<body>
  <div class="app">
    <div class="left-column">
      Left
    </div>
    <div class="center-column">
      Center
<script src="https://code.jquery.com/jquery-3.1.0.js"></script><br>
      <button>Toggle footer</button>
    </div>
    <div class="right-column">
      Right
    </div>
    <div class="footer">
 </div>
  </div>
</body>
</html>

第二段代码片段:当出现滚动条时,右侧列会缩小

$('button').click(() => {
  $('.footer').toggle()
})
body, html {
  height: 100%;
  width: 100%;
  padding: 0;
  margin: 0;
  overflow: auto;
  font-family: 'Archivo', sans-serif ;
}
.app {
  margin: auto;
  display: grid;
  grid-template-columns: calc((100vw - 50vh)/2) 50vh calc(100% - (50vh + 100vw)/2);
  text-align: center;
  height: 100%;
  width: calc(100% - 10px);
}
.left-column, .center-column, .right-column {
  padding: 10px;
  min-height: 50vh;
  border: 1px solid black;
}
.left-column {
  border-right: none;
  background-color:#def;
}
.center-column {
  background-color:#e1ebbd;
}
.right-column {
  text-align: left;
  border-left: none;
  background: #fb1;
}
.footer {
  grid-column: 1 / span 3;
  height: 2000px;
  background: #753;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

</head>
<body>
  <div class="app">
    <div class="left-column">
      Left
    </div>
    <div class="center-column">
      Center
<script src="https://code.jquery.com/jquery-3.1.0.js"></script><br>
      <button>Toggle footer</button>
    </div>
    <div class="right-column">
      Right
    </div>
    <div class="footer">
 </div>
  </div>
</body>
</html>


很好的解释!但是有一个更简单的公式:grid-template-columns: calc((100vw - 50vh) / 2) 50vh 1fr;,其中左列的宽度是视口宽度减去中间列宽度的一半,右列的宽度是剩余容器宽度(即左列宽度减去滚动条宽度,或者等同于视口宽度减去左列宽度减去中间列宽度减去滚动条宽度,或者等同于容器宽度减去左列宽度减去中间列宽度)。 - undefined

4

您只能通过纯CSS获取和使用滚动条宽度的唯一方法是实际拥有滚动条。现在,我们不希望强制滚动条始终可见,所以我们需要做的是:

为所有网站内容创建一个容器,该容器始终显示滚动条并将其隐藏。这非常简单!

我已经创建了一个Fiddle。以下是代码片段:

/* The trick: */

html {
  overflow-x: hidden;
}

body {
  margin: 0;
  width: 100vw;
}

body > * {
  overflow-y: scroll;
  margin-right: -100px;
  padding-right: 100px;
}

/* Other styling: */

body {
  font-family: sans-serif;
  user-select: none;
  --color: rgb(255 191 191);
}

header {
  position: sticky;
  top: 0;
  z-index: 1;
  --color: rgb(191 191 255);
}

body > * > div {
  background-color: var(--color);
  border: 3px solid;
  margin: 10px;
  padding: 10px 20px;
  font-size: 20px;
  font-weight: bold;
}

label::before {
  position: relative;
  content: '';
  display: inline-block;
  width: 1em;
  height: 1em;
  margin: 0 10px;
  top: .2em;
  border: 1px solid;
  border-radius: .1em;
}

input:checked + label::before {
  background-color: var(--color);
  box-shadow: inset 0 0 0 1px #FFF;
}

input {
  display: none;
}

input:not(:checked) ~ div {
  display: none;
}

input ~ div {
  height: 200vh;
}
<header>
  <div>I am sticky!</div>
</header>
<main>
  <div>Hello world!</div>
  <input id="foo-2" type="checkbox" />
  <label for="foo-2">Click me</label>
  <div>Let's scroll</div>
</main>

给包含元素设置负边距和正的右内边距可以实现这个技巧。这两个属性的偏移量可以超过滚动条宽度,因此将其设置为100像素就足够了 - 我无法想象任何浏览器或网站会有比20像素甚至100像素更宽的滚动条。 顺便说一下:我将这些样式应用于body的每个直接子元素,而不是单个的#container元素,是因为否则position: sticky将无法工作。要使该特性在一个元素上工作,它只能具有一个带有滚动功能的祖先元素。 html 包含 #container包含sticky元素 -> 不起作用 html 包含sticky容器 -> 起作用

非常好的答案。 - huyz
与此问题无关,在您的代码片段中,为什么要用自己的复选框替换原有的复选框? - huyz
1
@huyz,谢谢!如果UI元素可以保持可访问性(在片段中它不是),我会重新创造几乎所有的UI元素。这样做的好处是,我可以更好地动画元素(例如,输入元素是空元素,不能有伪元素),并提供更强大的实现(通过从头开始创建自己的样式而不允许浏览器样式,使其更跨浏览器和未来)。请参见此Fiddle:https://jsfiddle.net/GustvandeWal/chopr7gj/ - Gust van de Wal

2
为什么不总是显示滚动条,即使不需要滚动呢?您可以通过将overflow:scroll;设置为容器来实现此目的。

2
在具有overflow:auto样式的元素内添加另一个包装器,并将其设置为窄18px左右。滚动条应出现在这18px的间隙中。

4
+1,但由于原帖作者不知道容器的宽度,他需要使用JavaScript来实现此功能。 - casablanca
1
使用FF和IE9(即将在您的浏览器中推出!)可以这样做:(FF)width: -moz-calc(100% - 16px); - Stephen
3
除了滚动条是27倍之外(或任何其他值),就可以欢呼了。 - user2246674
我使用了16像素,我宁愿滚动条略微裁剪一点也不想留下一个小缝隙。同时,我通过使用包装器并设置右边距16像素来实现这一点,使原始元素保持100%而没有任何麻烦。 - Chris Marisic

1

我曾遇到类似的情况,我有一个网格 - 标题行和实际数据行。数据行可能太多而需要滚动条,这会导致行中的单元格与标题行中的单元格错位。

我通过在包含几个按钮的行的右侧列上使用position:absolute来解决了这个问题。不知道这是否适用于您的情况,但对我来说非常完美。

以下是示例:

function toggleMoreRows() {
  document.getElementById('grid-body').classList.toggle('more-rows');
}
body {
  font-size: 13px;
  font-family: Arial, Verdana, sans-serif;
}
.grid-heading {
  width: 500px;
  background: #CCC;
  font-weight: bold;
}
.grid-body {
  width: 500px;
  max-height: 200px;
  overflow-y: auto;
  overflow-x: hidden;
}
.row {
  height: 32px;
  display: flex;
  align-items: stretch;
  position: relative;
}
.row.hidden {
  display: none;
}
.more-rows .row.hidden {
  display: flex;
}
.cell {
  width: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cell.actions {
  width: 200px;
  height: 100%;
  justify-content: space-around;
  position: absolute;
  left: 300px;
  top: 0;
}
<p><button onClick="toggleMoreRows()">Toggle more rows</button></p>

<div class="row grid-heading">
  <div class="cell">First name</div>
  <div class="cell">Last name</div>
  <div class="cell">Age</div>
  <div class="cell actions">Actions</div>
</div>

<div class="grid-body" id="grid-body">
  <div class="row">
    <div class="cell">Asdf</div>
    <div class="cell">Asdf</div>
    <div class="cell">12</div>
    <div class="cell actions">
      <button>Edit</button>
      <button>Delete</button>
    </div>
  </div>
  <div class="row">
    <div class="cell">Asdf</div>
    <div class="cell">Asdf</div>
    <div class="cell">12</div>
    <div class="cell actions">
      <button>Edit</button>
      <button>Delete</button>
    </div>
  </div>
  <div class="row">
    <div class="cell">Asdf</div>
    <div class="cell">Asdf</div>
    <div class="cell">12</div>
    <div class="cell actions">
      <button>Edit</button>
      <button>Delete</button>
    </div>
  </div>
  <div class="row">
    <div class="cell">Asdf</div>
    <div class="cell">Asdf</div>
    <div class="cell">12</div>
    <div class="cell actions">
      <button>Edit</button>
      <button>Delete</button>
    </div>
  </div>
  <div class="row hidden">
    <div class="cell">Asdf</div>
    <div class="cell">Asdf</div>
    <div class="cell">12</div>
    <div class="cell actions">
      <button>Edit</button>
      <button>Delete</button>
    </div>
  </div>
  <div class="row hidden">
    <div class="cell">Asdf</div>
    <div class="cell">Asdf</div>
    <div class="cell">12</div>
    <div class="cell actions">
      <button>Edit</button>
      <button>Delete</button>
    </div>
  </div>
  <div class="row hidden">
    <div class="cell">Asdf</div>
    <div class="cell">Asdf</div>
    <div class="cell">12</div>
    <div class="cell actions">
      <button>Edit</button>
      <button>Delete</button>
    </div>
  </div>
  <div class="row hidden">
    <div class="cell">Asdf</div>
    <div class="cell">Asdf</div>
    <div class="cell">12</div>
    <div class="cell actions">
      <button>Edit</button>
      <button>Delete</button>
    </div>
  </div>
  <div class="row">
    <div class="cell">Asdf</div>
    <div class="cell">Asdf</div>
    <div class="cell">12</div>
    <div class="cell actions">
      <button>Edit</button>
      <button>Delete</button>
    </div>
  </div>
</div>


1

我知道你想只用CSS实现这个,但是我提供了一个jQuery的解决方案,以防有人需要帮助。

使用jQuery,你可以获取滚动条的大小,然后给容器应用一个margin。

类似这样:

var checkScrollBars = function(){
    var b = $('body');
    var normalw = 0;
    var scrollw = 0;
    if(b.prop('scrollHeight')>b.height()){
        normalw = window.innerWidth;
        scrollw = normalw - b.width();
        $('#container').css({marginRight:'-'+scrollw+'px'});
    }
}
以上代码片段将在所有内容的高度大于容器高度时添加一个边距。 如果不需要水平滚动条,我们也可以将其移除:
body{
    overflow-x:hidden;
}

这是jQuery。你应该始终声明你正在使用的库/框架。 - Gust van de Wal
@GustvandeWal 谢谢您的建议。我已经进行了更改。 - Shounak Das

1
我曾经遇到一个类似的问题,为此我使用了以下解决方法。我不确定这是否可以解决你想做的事情,但是可能有帮助。 我有一个div自动调整大小以适应内容,然后添加滚动条,从而使里面的表格包裹文本,而不是使容器变得更宽。如果textarea向下调整大小并出现滚动条,则可以在下面的旧示例中看到不需要的效果。 我的丑陋解决方案是,在overflow-y:auto-div内添加一个具有display:inline-block属性的div,然后在其后再添加另一个小的inline-block div,宽度为19像素(保留滚动条),高度为1个像素。当浏览器调整div以适应内容时,这个小div将出现在真实的div旁边,当滚动条出现时,小div被推到真实div下面,而真实的div保持原来的宽度。这将导致一个像素的底部“间距”,但希望不会造成问题。当没有滚动条出现时,真实div旁边有19像素的未使用空间,如问题所述。 (最外层的div只是为了复制我的设置/问题。)
<div style="display:inline-block">
  <div style="max-height:120px; overflow-y:auto; border:1px solid gray">
    <table border=1><tr>
       <td>I do not</td><td>want this to</td>
       <td>add breaks in the text.</td>
    </tr></table>
    <textarea rows=3 cols=15>Resize me down</textarea>
  </div>
</div>
<br>
新的:
<div style="display:inline-block">
  <div style="max-height:150px;overflow-y:auto; border:1px solid gray">
    <div style="display:inline-block">
      <table border=1><tr>
          <td>I do not</td><td>want this to</td>
          <td>add breaks in the text.</td>
      </tr></table>
      <textarea rows=3 cols=15>Resize me down</textarea>
    </div>
    <div style="display:inline-block; width:19px; height:1px"></div>
  </div>
</div>
<br>

1
You can do it with using this css on the content element: `calc(100% - 15px)`.
// calc(100% - the width we want to give on right hand side of content)
请阅读以下内容,以了解它是如何工作的。 使用overflow:scroll绝对是最简单和最不混乱的修复方法。 但是,如果您不想在不需要时显示滚动条,则应根据viewport(vw)使用宽度,而不是使用100%。由于滚动条出现在视口宽度中,因此,如果我们知道滚动条的宽度,则可以使用下面的公式完成任务。在这里,我将内容宽度设置为视口宽度减去滚动条的宽度(假设为15px)。 您需要提供宽度: calc(100% - 15px)。您可以以百分比、em等形式提供宽度。 最好的方法是像下面那样覆盖滚动条的宽度,然后在公式中使用该宽度值进行减法运算。
/* width */
::-webkit-scrollbar {
  width: 10px;
} 
#content {
  width: calc(100% - 10px).
}
注意:自定义滚动条在 Firefox 或 Edge 版本 79 之前不受支持。而且这个 CSS 只在 Webkit 浏览器中起作用,所以在 IE 中可能无法工作。 因此,您可以使用最大宽度的20px来减去,因为滚动条永远不能占用超过20px的宽度。请查看下面的工作代码。

.scroll {
    height: 100px;
    overflow: auto;
    border: 1px solid black;
    width: 75%;
}

.zui-table {
      width: calc(100% - 10px);
    border: solid 1px #DDEEEE;
    border-collapse: collapse;
    border-spacing: 0;
    font: normal 13px Arial, sans-serif;
}
.zui-table thead th {
    background-color: #DDEFEF;
    border: solid 1px #DDEEEE;
    color: #336B6B;
    padding: 10px;
    text-align: left;
    text-shadow: 1px 1px 1px #fff;
}
.zui-table tbody td {
    border: solid 1px #DDEEEE;
    color: #333;
    padding: 10px;
    text-shadow: 1px 1px 1px #fff;
}
<div class="scroll">
  <table class="zui-table">
    <thead>
      <tr>
        <th>Name</th>
        <th>Position</th>
        <th>Height</th>
        <th>Born</th>
        <th>Salary</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>DeMarcus Cousins</td>
        <td>C</td>
        <td>6'11"</td>
        <td>08-13-1990</td>
        <td>$4,917,000</td>
      </tr>
      <tr>
        <td>Isaiah Thomas</td>
        <td>PG</td>
        <td>5'9"</td>
        <td>02-07-1989</td>
        <td>$473,604</td>
      </tr>
      <tr>
        <td>Ben McLemore</td>
        <td>SG</td>
        <td>6'5"</td>
        <td>02-11-1993</td>
        <td>$2,895,960</td>
      </tr>
      <tr>
        <td>Marcus Thornton</td>
        <td>SG</td>
        <td>6'4"</td>
        <td>05-05-1987</td>
        <td>$7,000,000</td>
      </tr>
      <tr>
        <td>Jason Thompson</td>
        <td>PF</td>
        <td>6'11"</td>
        <td>06-21-1986</td>
        <td>$3,001,000</td>
      </tr>
    </tbody>
  </table>
</div>


整体的宽度和高度是不固定的。您假设宽度等于屏幕宽度。 - Temani Afif
在这种情况下,我们可以使用我提供的宽度设置最顶层的父元素的宽度。这样当滚动条出现或消失时,它就不会切换了。我认为我们可以通过这种方法来解决这个问题。 - Jasdeep Singh
@TemaniAfif,那么就用calc(100% - 10px)吧。这是这里最好的答案,只需将100vw替换为100%即可。水平滚动条以这种方式更难解决,但幸运的是,这似乎不是OP需要的东西。 - Slbox
@Slbox calc(100% - 10px) 的行为与 100% 相同,因为它已经考虑了滚动条。这个答案的诀窍在于使用 vw 单位,它是一个不受滚动条宽度影响的尺寸单位(类似像素但像素不具有响应性)。 - Temani Afif
如果元素已经占据了100vw,那么100%会表现得一样,否则就不同了。你自己注意到了这一点,"你假设宽度等于屏幕宽度"。所以使用100%就没有问题了。至于它是否占据整个宽度,嗯,是的 - 这就是答案中使用calc()的原因。 - Slbox
@Slbox 请尝试并向我展示一个使用 calc(100% - 10px) 的工作示例。 - Temani Afif

0
问题在于一旦你将鼠标悬停在容器上并出现滚动条,那么内容宽度就会缩小。因此,即使您使用一个嵌套容器,其宽度等于容器减去其滚动条的宽度,嵌套容器的宽度也会缩小。 一种解决方案是通过增加悬停时滚动条的宽度来增加内容的大小。以下是一种解决方案,不需要使用任何嵌套外部容器(`categoryCont` 是滚动容器,每个 `menuBlock` 是要滚动的项目之一):
<div id="categoryCont" style="position:relative;width:200px; overflow:hidden;">
    <div class="menuBlock" style="width:200px">a</div>
    <div class="menuBlock" style="width:200px">b</div>
    <div class="menuBlock" style="width:200px">c</div>
    ...
</div>

<style type="text/css">
    #categoryCont:hover{
        overflow-y: auto;
    }
    #categoryCont:hover .menuBlock{
    /* Increase size of menu block when scrollbar appears */
        width: 218px;    /* Adjust for width of scroll bar. */
    }
</style>

以上的一个问题是滚动条的宽度在不同的浏览器中略有不同。以下其中之一应该会有所帮助:

  • 使用像素(px)

将内容设置为绝对值,并将左缩进设置为绝对值

<div class="menuBlock" style="width:200px">
    a
</div>

更改为

<div class="menuBlock" style="width:200px">
    <span>a</span>
</div>

<style>
    .menuBlock span{        /* Good cross-browser solution but cannot use % */
        position:absolute;
        left:70px;
    }
</style>
通过使用%,你需要同时使用CSS和jQuery(第一步相同)。
<div class="menuBlock" style="width:200px">
    a
</div>

更改为

<div class="menuBlock" style="width:200px">
    <span>a</span>
</div>

<style>
    .menuBlock span{        /* Good cross-browser solution but cannot use % */
        position:absolute;  /* This one does not use 'left' */
    }
</style>

<script>
    // Indent left 30% because container width remains same but content width changes
    var leftIndent = (30/100) * $("#categoryCont").width();
    $(".menuBlock span").css({"left":leftIndent});
</script>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,