如何防止窗口范围的粘性元素在更宽的块内滚动?
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 移动到左侧。
【讨论】:
有趣,但确实在其他浏览器中中断。以上是关于如何防止窗口范围的粘性元素在更宽的块内滚动?的主要内容,如果未能解决你的问题,请参考以下文章