如何防止窗口范围的粘性元素在更宽的块内滚动?

Posted

技术标签:

【中文标题】如何防止窗口范围的粘性元素在更宽的块内滚动?【英文标题】:How to keep a window-wide sticky element from scrolling within a wider block? 【发布时间】:2019-09-14 22:11:37 【问题描述】:

我正在开发一个涉及大量滚动的网络界面,利用粘性定位,除了一个小细节外,它工作得很好。下面是一个极简主义的例子。您可以try it on CodePen 了解水平和垂直滚动时即时贴的工作原理。

<header>
 <nav>
  <ul id=menu class=flex><li><a href="#">Menu bar item</a></li></ul>
 </nav>
 <div class=flex>
  <div id=button>Button</div>
  <div id=bar>Bar wider than the window</div>
 </div>
</header>
<main class=flex>
 <div id=leftbar></div>
 <div id=content></div>
</main>
*           line-height: 2em; background-position: center center
body        margin: 0; padding: 0
body, a     color: #fff
ul,li       margin: 0; padding: 0; list-style-type: none
.flex       display: flex
.flex > *   flex: 0 0 auto
header      display: block; position: sticky; top: 0; left: 0; z-index: 1;
             width: min-content; min-width: 100%; background: #000066
#menu       flex-wrap: wrap; position: sticky; left: 0; width: 100vw;
             background: #006600
#button     position: sticky; left: 0; width: 4em; background: #000066
#bar        width: 150vw
main        min-height: 100vh /* Ensure scrollbar */
#leftbar    position: sticky; left: 0; width: 4em; background: #444444
#content    width: 150vw
/* Background grid to visualize scrolling parts */
#bar, #leftbar, #content 
 background-image: url(data:image/gif;base64,R0lGODlhEAAQAIABAO7u7v///yH5BAEKAAEA
 LAAAAAAQABAAAAIdhI9pwe0PnnRxzmphlniz7oGbmJGWeXmU2qAcyxQAOw==)

问题出在最顶部的菜单栏中,它应该像固定的标题一样始终保留在原位,但必须保持粘性。 100vw 宽度完美地工作,直到一直向右滚动。此时菜单栏的最左边部分滚动到视野之外,可能是由于垂直滚动条没有计入 100vw。

固定定位是不可能的,因为菜单栏的高度会发生变化,所以需要一堆非常丑陋的 JS 读取 offsetHeight 并在每次 DOM 更改后移动下面的元素 - 哎呀!

肯定有办法在纯 CSS 中或通过更改文档结构来解决这个小问题?

编辑: 我看到 JS 解决方案开始涌入,所以让我们用这 3 行可以轻松解决问题的 JS 来制止这种情况。我只是更喜欢 CSS/html 解决方案,以避免将来出现代码维护问题。

var f=function() document.getElementById('menu').style.width=document.body.clientWidth+'px';;
window.addEventListener('DOMContentLoaded',f);
window.addEventListener('resize',f);

【问题讨论】:

这很难。可能你可以在 body 上设置 overflow-y hidden 并在实际内容区域中将其设置回溢出。它可能看起来有点难看,但它会起作用。 很棒的例子,但是是的,这是一个棘手的例子。基本上它归结为 100vw 是屏幕宽度 + 滚动条宽度的事实。因此,如果您设置#menu width: calc(100vw - 17px) (17px 大约是 Windows 上的滚动条宽度)它会起作用,但它非常丑陋,而不是跨浏览器。我会努力寻找更好的解决方案 感谢您的想法。除了 Windows 之外,我还需要针对几个不同的 Linux 桌面,因此不可能依靠固定的滚动条宽度。也许我可以使用 JS 在加载/调整大小事件中设置一个精确的宽度,因为这样不会太难看。 【参考方案1】:

正如评论中提到的,问题在于 100vw 包括滚动条宽度。对此并没有真正的优雅解决方案,但一种解决方法是将您的 #menu 宽度减小到 99vw,然后用具有相同背景颜色的伪元素填充空白空间。

#menu 
  width: 99vw;
  background: #006600;

#menu:before 
  content: '';
  width: 5vw;
  left: 100%;
  top: 0;
  bottom: 0;
  position: absolute;
  background: inherit;

请注意,这仅在您的 #menu 上具有平坦背景颜色(无图像,无透明度)时才有效,因此我不确定它是否对您的情况有所帮助,但这是我能来的最佳跨浏览器解决方案跟上。

见:https://codepen.io/anon/pen/WWLedm

【讨论】:

我担心我会看到这个建议。不幸的是,例如,如果我使用此界面可视化 iframe 中的小数据集,它会中断(或需要宽度甚至小于 99vw)。在这种特殊情况下,颜色不是问题。 我明白了。如果您将#menu 宽度设置为 calc(100vw - 50px) 并将 :before 的宽度设置为 50px 会怎样?那会更准确。但我不知道它在 iframe 中的表现如何【参考方案2】:

更新

将滚动条的宽度设置为 15px 并将绿色菜单栏的宽度设置为 calc(100vw - 15px) 可能会这样做。

我不知道用纯 CSS 实现这一点的任何方法。但是,您可以使用 vanilla JS 实现您所需要的。

我在this SO question 中使用@lostsource 提供的函数来获取滚动条的宽度。

然后我创建了一个函数 - resizeMenuBar() - 它将菜单栏的宽度设置为 window.innerWidth 的宽度减去滚动条的宽度。

我添加了两个事件监听器——一个用于window.load,另一个用于window.resize。事件处理程序是resizeMenuBar 函数。

* 
  line-height: 2em;
  background-position: center center;


body 
  margin: 0;
  padding: 0;


body,
a 
  color: #fff;


ul,
li 
  margin: 0;
  padding: 0;
  list-style-type: none;


.flex 
  display: flex;


.flex>* 
  flex: 0 0 auto;


header 
  display: block;
  position: sticky;
  top: 0;
  left: 0;
  width: min-content;
  min-width: 100%;
  z-index: 1;
  background: #000066;


#menu 
  flex-wrap: wrap;
  position: sticky;
  left: 0;
  width: calc(100vw - 15px);
  background: #006600;


#button 
  position: sticky;
  left: 0;
  width: 4em;
  background: #000066;


#bar 
  width: 150vw;


main 
  min-height: 100vh;



/* Ensure scrollbar */

#leftbar 
  position: sticky;
  left: 0;
  width: 4em;
  background: #444444;


#content 
  width: 150vw;



/* Background grid to visualize scrolling parts */

#bar,
#leftbar,
#content 
  background-image: url(data:image/gif;base64,R0lGODlhEAAQAIABAO7u7v///yH5BAEKAAEALAAAAAAQABAAAAIdhI9pwe0PnnRxzmphlniz7oGbmJGWeXmU2qAcyxQAOw==);


body::-webkit-scrollbar 
  width: 15px;


body::-webkit-scrollbar-track 
  -webkit-box-shadow: inset 0 0 6px #ccc;


body::-webkit-scrollbar-thumb 
  background-color: grey;
  outline: 1px solid grey;
<header>
  <nav>
    <ul id=menu class=flex>
      <li><a href="#">Menu bar item</a></li>
    </ul>
  </nav>
  <div class=flex>
    <div id=button>Button</div>
    <div id=bar>Bar wider than the window</div>
  </div>
</header>
<main class=flex>
  <div id=leftbar></div>
  <div id=content></div>
</main>

【讨论】:

这看起来相当复杂,因为我已经在 3 行 JS 中解决了这个问题,但我仍然更喜欢 CSS 解决方案以避免代码维护问题。 我同意,如果有一个 CSS 解决方案会更好。当 100vw 或 100vh 应该包含滚动条的宽度时,我看不到任何用例。你介意分享你用来解决这个问题的 JS 吗? 另外,我同意在视口单元中包含滚动条很糟糕。我还没有设法找出规范背后的人在吸烟。 我已经放弃尝试弄清楚为什么事情在 CSS 中的工作方式与很久以前的工作方式相同。实际上,这里有一个想法。您是否尝试设置滚动条本身的样式? IE。给它一个宽度为 1vw 并将你的菜单栏设置为 99vw。我知道这是一个 hack,我不确定它是否有效,我也不确定跨浏览器的兼容性。 大多数 CSS 规范至少在某些方面是有意义的,但这一规范超出了我的理解。为滚动条设置样式听起来是个坏主意,因为我的界面将在应用程序中的许多不同环境、浏览器和浏览器引擎中使用,我不能依赖任何黑客攻击。【参考方案3】:

下面的 hack 似乎只能在 chrome 上正常工作

这是一个技巧,其想法是增加粘性元素的父元素的宽度,以便为粘性行为留出足够的空间,然后使用一些平移来纠正额外的宽度和位置:

* 
  line-height: 2em;
  background-position: center center;

body 
  margin: 0;
  padding: 0;

body,
a 
  color: #fff;

ul,
li 
  margin: 0;
  padding: 0;
  list-style-type: none;

.flex 
  display: flex;

.flex > * 
  flex: 0 0 auto;

header 
  display: block;
  position: sticky;
  top: 0;
  left: 0;
  width: min-content;
  min-width: 100%;
  z-index: 1;
  background: #000066;

/*addedd*/
nav 
  width: calc(100% + 60px);
  transform: translateX(-60px);

/**/
#menu 
  flex-wrap: wrap;
  position: sticky;
  left: 0;
  width: 100vw;
  background: #006600;
  transform: translateX(60px); /*added*/

#button 
  position: sticky;
  left: 0;
  width: 4em;
  background: #000066;

#bar 
  width: 150vw;

main 
  min-height: 100vh;
 /* Ensure scrollbar */
#leftbar 
  position: sticky;
  left: 0;
  width: 4em;
  background: #444444;

#content 
  width: 150vw;

/* Background grid to visualize scrolling parts */
#bar,
#leftbar,
#content 
  background-image: url(data:image/gif;base64,R0lGODlhEAAQAIABAO7u7v///yH5BAEKAAEALAAAAAAQABAAAAIdhI9pwe0PnnRxzmphlniz7oGbmJGWeXmU2qAcyxQAOw==);
<header>
 <nav>
  <ul id=menu class=flex>
   <li><a href="#">Menu bar item</a></li>
  </ul>
 </nav>
 <div class=flex>
  <div id=button>Button</div>
  <div id=bar>Bar wider than the window</div>
 </div>
</header>
<main class=flex>
 <div id=leftbar></div>
 <div id=content></div>
</main>

60px 是一个随机值,应该大于滚动宽度。

获得诀窍并非易事,但如果您删除翻译,您将获得以下内容:

* 
  line-height: 2em;
  background-position: center center;

body 
  margin: 0;
  padding: 0;

body,
a 
  color: #fff;

ul,
li 
  margin: 0;
  padding: 0;
  list-style-type: none;

.flex 
  display: flex;

.flex > * 
  flex: 0 0 auto;

header 
  display: block;
  position: sticky;
  top: 0;
  left: 0;
  width: min-content;
  min-width: 100%;
  z-index: 1;
  background: #000066;

/*addedd*/
nav 
  width: calc(100% + 60px);

/**/
#menu 
  flex-wrap: wrap;
  position: sticky;
  left: 0;
  width: 100vw;
  background: #006600;

#button 
  position: sticky;
  left: 0;
  width: 4em;
  background: #000066;

#bar 
  width: 150vw;

main 
  min-height: 100vh;
 /* Ensure scrollbar */
#leftbar 
  position: sticky;
  left: 0;
  width: 4em;
  background: #444444;

#content 
  width: 150vw;

/* Background grid to visualize scrolling parts */
#bar,
#leftbar,
#content 
  background-image: url(data:image/gif;base64,R0lGODlhEAAQAIABAO7u7v///yH5BAEKAAEALAAAAAAQABAAAAIdhI9pwe0PnnRxzmphlniz7oGbmJGWeXmU2qAcyxQAOw==);
<header>
 <nav>
  <ul id=menu class=flex>
   <li><a href="#">Menu bar item</a></li>
  </ul>
 </nav>
 <div class=flex>
  <div id=button>Button</div>
  <div id=bar>Bar wider than the window</div>
 </div>
</header>
<main class=flex>
 <div id=leftbar></div>
 <div id=content></div>
</main>

nav 溢出,这将在逻辑上增加滚动。现在想象一下,您只会滚动而不考虑额外的60px。这可以正常工作,但当然用户不会在60px 之前停止滚动,所以诀窍是使用 translate 将 oveflow 移动到左侧。

【讨论】:

有趣,但确实在其他浏览器中中断。

以上是关于如何防止窗口范围的粘性元素在更宽的块内滚动?的主要内容,如果未能解决你的问题,请参考以下文章

位置:粘性 - 与javascript高度调整结合使用时滚动弹跳

边框半径不适用于更宽的屏幕

如何在表格中正确放置粘性 td 元素

使用媒体查询使 WP 短代码元素居中

防止从元素到窗口的滚动冒泡

当DIV内出现滚动条,fixed实效怎么办?