Skip to content

[转载] position: sticky is Amazing

约 2065 字大约 7 分钟

csssticky转载

2024-09-11

原文:https://mastery.games/post/position-sticky/

CSS 又迎来了一次小小的升级。position: sticky 在 Chrome 56 版本中首次亮相。粘性定位(sticky positioning)让我们可以用很少的代码构建一些非常酷的交互效果。它适用于任何你希望用户滚动时UI元素能够保持可见的情况,但并不是立即变为粘性状态,而是在元素到达视口滚动边缘的具体距离时才变为粘性状态。它就像一个“卧底特工”式的固定定位(position: fixed)元素。它表现得就像一个普通的相对定位(position: relative)元素——甚至欺骗了自己的父级和兄弟元素——直到秘密的距离被达到,激活其作为“卧底”的固定定位行为。

我们能用它做什么?

粘性定位非常适合像iOS风格的列表标题。滚动内容,当标题到达距离顶部边缘0像素时,观察它们如何变得粘性。

.heading{
  background: #ccc;
  height: 50px;
  line-height: 50px;
  margin-top: 10px;
  font-size: 30px;
  padding-left: 10px;
  position: -webkit-sticky;
  position: sticky;
  top: 0px;
}

或者假设你正在开发一个 Trello 的替代品,现在他们已经被 Atlassian(Jira)收购了(<— 心里有点难过)。你希望当用户向下滚动时,列表头部能够保持可见。你也希望当他们向上滚动时,“添加条目”的页脚也能保持可见。试试看!上下滚动,观察这两个元素如何一旦到达视口边缘就变得粘性:

header{
  background: #ccc;
  font-size: 20px;
  color: #282a37;
  padding: 10px;
  position: -webkit-sticky;
  position: sticky;
  top: 0;
}

footer{
  background: #ccc;
  padding: 10px;
  color: #ae81fe;
  position: relative;
  position: -webkit-sticky;
  position: sticky;
}

footer{
  bottom: 0;
}

list{
  border: 1px solid #ccc;
  border-radius: 5px;
  width: 200px;
  margin-left: 20px;
  background: #282a37;
}

item{
  padding: 10px;
  color: #fff;
  display: block;
}

body{
  padding-top: 20px;
  display: flex;
  align-items: flex-start;
}

.abs{
  position: absolute;
  right: 0;
  top: 10px;
}

你也可以将项目粘贴到左边或右边边缘。这里是一个水平滚动的图像查看器,带有旋转的图像描述文本。水平滚动它,观察描述如何停靠在左侧,直到新的描述将其推离视野。

div[description]{
  max-width: 40px;
  height: 300px;
  position: -webkit-sticky;
  position: sticky;
  left: 0; /* become sticky once touching left edge */
}

sidescroller{
  display: flex;
  align-items: center;
  overflow-x: auto;
  overflow-y: hidden;
  background: #000;
}

div[wrapper]{
  flex: 0 0 40px;
  max-width: 40px;
  height: 300px;
  position: -webkit-sticky;
  position: sticky;
  left: 0;
  white-space: nowrap;
  color: #fff;
}

div[item]{
  display: flex;
}

div[description] span{
  display: inline-block;
  background: rgba(0,0,0,.5);
  width: 300px;
  height: 40px;
  transform: rotate(-90deg) translateX(-300px);
  transform-origin: left top 0;
  padding-top: 11px;
  text-align: center;
  text-transform: uppercase;
  color: #fff;
  font-size: 14px;
}

img{
  max-height: 300px;
}

edit on codepen

你甚至可以在想让元素在部分或全部被滚动出视野时变成粘性的情况下指定负数。例如,在侧边栏菜单在被滚动出视野时变为粘性,留下一个小按钮,当点击时可以跳回到侧边栏:

.sidebar{
  background: purple;
  width: 200px;
  height: 300px;
  padding: 20px;
  flex-shrink: 0;
  overflow: visible;
  position: -webkit-sticky;
  position: sticky;
  left: -200px;
}

.sidebar .handle{
  height: 30px;
  width: 30px;
  position: absolute;
  right: -30px;
  top: 0;
  background: purple;
  color: #fff;
  font-weight: bold;
  font-size: 20px;
  padding-left: 8px;
  cursor: pointer;
} 

p{
  padding: 20px;
}

.site{
  display: flex;
}

edit on codepen

Give Me This Power

很整洁吧?并且使用它很简单。

  1. 使用 position:sticky 将元素声明为粘性(加上所需的任何浏览器前缀,如position: -webkit-sticky
  2. 指定要“粘贴”的项目的边缘(上|右|下|左)。
  3. 输入距所述边缘的距离,到达该距离后将激活粘性。

例如,假设您希望标题在距离滚动区域顶部 20px 时变得粘性:

.header {
  position: -webkit-sticky;
  position: sticky;
  top: 20px;
}

或者,一旦滚动到视图之外,菜单就会粘在左边缘,如上面的示例所示:

.menu {
  width: 200px;
  position: -webkit-sticky;
  position: sticky;
  left: -200px;
}

Some Gotchas

position:sticky 有一些需要注意的问题。

Siblings

如果将同级(相邻)元素设置为 position: sticky ,它们的行为将与嵌套项目内的元素略有不同。粘性同级元素不会为新元素让路。相反,它们会在适当的位置重叠:

有时您可能想要这种行为,但如果您确实设置了背景颜色,否则用户会立即看到所有项目都挤在同一个小空间中,看起来会很混乱。

另一方面,如果您将粘性元素嵌套父元素中,就像我们在 sidescroller 示例中所做的那样,那么一旦另一个粘性元素开始接触粘性元素,粘性元素就会开始移开。这是一个很好的做法,在我看来,效果有点经典:

Overflow

不要尝试在 position:sticky 元素的父元素上使用 overflow: auto|scroll|hidden 彻底打破了粘性。 overflow: visible 就好。

Absolute Positioning

如果你想在粘性元素内部的元素上使用position:absolute你必须小心。如果您的应用程序在不支持position:sticky旧版浏览器中运行,则该粘性元素将不会像相对定位元素一样起作用。因此绝对定位元素将跳过它并查找 DOM 树,直到找到下一个非静态元素(绝对 | 相对 | 固定位置),如果没有找到,则默认为html元素。换句话说,您的绝对定位元素在屏幕上的位置将与您预期的不同。如果您正在为较旧的浏览器构建某些内容,人们可能会认为解决方案是仅设置相对定位和粘性定位:

/* WARNING don't do this */
.footerWithAbsolutePositionedChildren {
  position: relative; /* <-- all browsers will set this */
  position: sticky; /* <-- new browsers will use this, old ones will ignore it */
  bottom: 20px;
}

很好吧?不,这是个坏主意,因为粘性数字非零。如果浏览器不支持position:sticky ,您的页脚将保留在您首先指定的position: relative位置。因此,现在您为粘性指定的边缘值将被视为相对值。在这种情况下,这意味着将页脚向上推 20px,这根本不是我们想要的。

更好的解决方案是使用CSS support at-rule来检测当前浏览器是否支持粘性定位,如果支持设置边缘值:

.footerWithAbsolutePositionedChildren {
  position: relative; /* don't forget this */
}

/* NOTE: @supports has to be at the root, not nested */
@supports (position: sticky) {
  .footerWithAbsolutePositionedChildren {
    position: sticky;
    bottom: 20px; /* now this won't mess with positioning in non-sticky browsers */
  }
}

Why Not Use JavaScript?

你绝对可以用 JS 来实现。但这将涉及scroll事件侦听器,将其添加到应用程序中仍然是非常昂贵的事情。滚动是用户最常执行的操作之一,在这些事件期间执行 JavaScript 会导致很难保持稳定的 60 FPS(每秒帧数)滚动。 UI 与用户的鼠标/手指/手写笔不同步。这称为滚动卡顿。有一种特殊的事件侦听器,称为被动事件侦听器,它可以让浏览器知道您的事件不会停止滚动,因此浏览器可以进一步优化这些事件。但 IE 或 Edge 尚不支持它们,无论如何你都需要 JS 后备方法。

此外,使用position:sticky您在滚动期间不会写入DOM,因此不会导致任何强制布局和布局重新计算。因此,浏览器能够将此操作移至 GPU,即使在使用粘性元素时,您也可以获得非常平滑的滚动。在移动版 Sarari 中尤其流畅。

另外,编写两行声明性 CSS 比 JS 替代方案更容易。

Can I Use This Now?

所有常青浏览器都支持position:sticky 。 IE 此时并不重要,除非您在 Enterprise Town 中有合同义务(感谢销售人员)。如果您确实需要这种行为,那么有很多 Polyfill,但它们都使用 JavaScript,因此您将遭受上面提到的性能损失。更好的选择是设计您的应用程序,使粘性位置成为一个巧妙的补充,但应用程序在没有它的情况下仍然可以运行。所以我竖起大拇指。

我可以使用吗?有关主要浏览器对此功能的支持的数据来自 caniuse.com。