"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Dicas para combinar perfeitamente elementos de sangramento pegajoso e total

Dicas para combinar perfeitamente elementos de sangramento pegajoso e total

Postado em 2025-04-17
Navegar:521

How to Get Sticky and Full-Bleed Elements to Play Well Together

最近我遇到一个独特的布局需求:在页面包含全屏元素的同时,保持一个元素始终固定在顶部。这实现起来相当棘手,因此我将记录下我的解决方案,以备不时之需。尤其是在小屏幕上的逻辑定位处理上,更是增加了难度。

效果难以用文字描述,所以我录制了屏幕视频来说明我的意思。请特别注意主要的号召性用语部分,也就是带有“立即体验Domino”标题的部分。

我们的目标是在较大视窗中,将主要的号召性用语显示在右侧,而用户向下滚动时,其他部分会从其下方经过。在较小视窗中,号召性用语元素必须显示在带有“开始试用”标题的主要英雄区之后。

这里主要有两个挑战:

  • 创建不与粘性元素冲突的全屏元素
  • 避免重复HTML代码

在深入探讨几种可能的解决方案(及其局限性)之前,让我们先搭建语义化的HTML结构。

HTML结构

构建此类布局时,可能会倾向于创建重复的号召性用语部分:一个用于桌面版本,另一个用于移动版本,然后在适当的时候切换它们的可见性。这避免了在HTML中寻找完美位置以及应用处理两种布局需求的CSS的麻烦。我必须承认,我有时也会这样做。但这一次,我想避免重复我的HTML代码。

另一个需要考虑的是,我们对.box--sticky元素使用了粘性定位,这意味着它需要与其他元素(包括全屏元素)同级,才能正常工作。

以下是标记:

英雄区
粘性区
全屏区
全屏区

创建粘性元素

在CSS网格布局中创建粘性元素非常简单。我们向.box--sticky元素添加position: stickytop: 0偏移量,指示其开始粘贴的位置。注意,我们只在视窗宽度大于768px时才使元素粘性化。

@media screen and (min-width: 768px) {
  .box--sticky {
    position: sticky;
    top: 0;
  }
}

需要注意的是,当position: stickyoverflow: auto一起使用时,Safari中存在已知的粘性定位问题。Caniuse网站的已知问题部分对此进行了说明:

设置为overflow: auto的父元素会阻止Safari中position: sticky的工作。

不错,这很容易。接下来让我们解决全屏元素的挑战。

方案一:伪元素

第一个方案是我经常使用的方法:绝对定位的伪元素,可以从一侧延伸到另一侧。这里的技巧是使用负偏移量。

如果我们讨论的是居中内容,那么计算就相当简单:

.box--bleed {
  max-width: 600px;
  margin-right: auto;
  margin-left: auto;
  padding: 20px;
  position: relative; 
}

.box--bleed::before {
  content: "";
  background-color: dodgerblue; 
  position: absolute;
  top: 0;
  bottom: 0;
  right: calc((100vw - 100%) / -2);
  left: calc((100vw - 100%) / -2);
}

简而言之,负偏移量是视窗宽度(100vw)减去元素宽度(100%),然后除以-2,因为我们需要两个负偏移量。

需要注意的是,使用100vw时存在一个已知的bug,Caniuse网站也对此进行了说明:

目前除Firefox之外的所有浏览器都错误地认为100vw是整个页面宽度,包括垂直滚动条,当设置overflow: auto时,这可能会导致水平滚动条。

现在让我们在内容居中的情况下创建全屏元素。如果你再次观看视频,你会注意到粘性元素下方没有内容。我们不希望粘性元素与内容重叠,这就是为什么在这个特定的布局中没有居中内容的原因。

首先,我们将创建网格:

.grid {
  display: grid;
  grid-gap: var(--gap);
  grid-template-columns: var(--cols);
  max-width: var(--max-width);
  margin-left: auto;
  margin-right: auto;
}

我们使用自定义属性,允许我们重新定义最大宽度、间隙和网格列,而无需重新声明属性。换句话说,我们重新声明变量值,而不是重新声明grid-gapgrid-template-columnsmax-width属性:

:root {
  --gap: 20px;
  --cols: 1fr;
  --max-width: calc(100% - 2 * var(--gap));
}

@media screen and (min-width: 768px) {
  :root {
    --max-width: 600px;
    --aside-width: 200px;
    --cols: 1fr var(--aside-width);
  }
}

@media screen and (min-width: 980px) {
  :root {
    --max-width: 900px;
    --aside-width: 300px;
  }
}

在宽度为768px及以上的视窗上,我们定义了两列:一列具有固定宽度(--aside-width),另一列填充剩余空间(1fr),以及网格容器的最大宽度(--max-width)。

在小于768px的视窗上,我们定义了一列和间隙。网格容器的最大宽度是视窗的100%,减去两侧的间隙。

现在是精彩的部分。内容在较大的视窗上没有居中,因此计算不像你想象的那么简单。以下是它的样子:

.box--bleed {
  position: relative;
  z-index: 0;
}

.box--bleed::before {
  content: "";
  display: block;
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc((100vw - (100%   var(--gap)   var(--aside-width))) / -2);
  right: calc(((100vw - (100% - var(--gap)   var(--aside-width))) / -2) - (var(--aside-width)));
  z-index: -1;
}

我们没有使用父元素宽度的100%,而是考虑了间隙和粘性元素的宽度。这意味着全屏元素中的内容宽度不会超过英雄元素的边界。这样,我们确保粘性元素不会与任何重要信息重叠。

左侧偏移量比较简单,因为我们只需要从视窗宽度(100vw)中减去元素宽度(100%)、间隙(--gap)和粘性元素(--aside-width)。

left: (100vw - (100%   var(--gap)   var(--aside-width))) / -2);

右侧偏移量比较复杂,因为我们必须将粘性元素的宽度添加到之前的计算中,--aside-width,以及间隙,--gap

right: ((100vw - (100%   var(--gap)   var(--aside-width))) / -2) - (var(--aside-width)   var(--gap));

现在我们可以确保粘性元素不会与全屏元素中的任何内容重叠。

这是一个包含水平bug的解决方案:

这是一个包含水平bug修复的解决方案:

修复方法是隐藏body的x轴溢出,这通常也是一个好主意:

body {
  max-width: 100%;
  overflow-x: hidden;
}

这是一个完全可行的解决方案,我们可以在此结束。但这有什么乐趣呢?通常不止一种方法可以完成某件事,所以让我们看看另一种方法。

方案二:填充计算

我们可以通过配置网格来实现相同的效果,而不是使用居中的网格容器和伪元素。让我们从定义网格开始,就像我们上次做的那样:

.grid {
  display: grid;
  grid-gap: var(--gap);
  grid-template-columns: var(--cols);
}

同样,我们使用自定义属性来定义间隙和模板列:

:root {
  --gap: 20px;
  --gutter: 1px;
  --cols: var(--gutter) 1fr var(--gutter);
}

我们在小于768px的视窗上显示三列。中间列占据尽可能多的空间,而另外两列仅用于强制水平间隙。

@media screen and (max-width: 767px) {
  .box {
    grid-column: 2 / -2;
  }
}

请注意,所有网格元素都放置在中间列中。

在大于768px的视窗上,我们定义了一个--max-width变量,它限制了内部列的宽度。我们还定义了--aside-width,即粘性元素的宽度。同样,这样可以确保粘性元素不会定位在全屏元素内的任何内容之上。

:root {
  --gap: 20px;
}

@media screen and (min-width: 768px) {
  :root {
    --max-width: 600px;
    --aside-width: 200px;
    --gutter: calc((100% - (var(--max-width))) / 2 - var(--gap));
    --cols: var(--gutter) 1fr var(--aside-width) var(--gutter);
  }
}

@media screen and (min-width: 980px) {
  :root {
    --max-width: 900px;
    --aside-width: 300px;
  }
}

接下来,我们将计算边距宽度。计算公式是:

--gutter: calc((100% - (var(--max-width))) / 2 - var(--gap));

…其中100%是视窗宽度。首先,我们从视窗宽度中减去内部列的最大宽度。然后,我们将结果除以2以创建边距。最后,我们减去网格间隙以获得边距列的正确宽度。

现在让我们将.box--hero元素推到一边,以便它从网格的第一列开始:

@media screen and (min-width: 768px) {
  .box--hero {
    grid-column-start: 2;
  }
}

这会自动推动粘性框,使其紧跟在英雄元素之后。我们也可以明确地定义粘性框的位置,如下所示:

.box--sticky {
  grid-column: 3 / span 1;
}

最后,让我们通过将grid-column设置为1 / -1来创建全屏元素。这告诉元素从第一个网格项开始内容,并跨越到最后一个网格项。

@media screen and (min-width: 768px) {  
  .box--bleed {
    grid-column: 1 / -1;
  }
}

为了居中内容,我们将计算左侧和右侧填充。左侧填充等于边距列的大小加上网格间隙。右侧填充等于左侧填充的大小,再加上另一个网格间隙以及粘性元素的宽度。

@media screen and (min-width: 768px) {
  .box--bleed {  
    padding-left: calc(var(--gutter)   var(--gap));
    padding-right: calc(var(--gutter)   var(--gap)   var(--gap)   var(--aside-width));
  }
}

这是最终的解决方案:

我更喜欢这个解决方案,因为它没有使用有问题的视窗单位。

我喜欢CSS计算。使用数学运算并不总是直截了当的,尤其是在组合不同的单位时,例如100%。弄清楚100%的含义是工作的一半。

我也喜欢使用CSS解决简单但复杂的布局,就像这个一样。现代CSS具有原生解决方案——例如网格、粘性定位和计算——消除了复杂且相当繁重的JavaScript解决方案。让我们把脏活留给浏览器吧!

你对此有更好的解决方案或不同的方法吗?我很乐意听到你的想法。

Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3