”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 让粘性和全出血元素完美结合的技巧

让粘性和全出血元素完美结合的技巧

发布于2025-04-17
浏览:312

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解决方案。让我们把脏活留给浏览器吧!

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

最新教程 更多>
  • Python高效去除文本中HTML标签方法
    Python高效去除文本中HTML标签方法
    在Python中剥离HTML标签,以获取原始的文本表示 仅通过Python的MlStripper 来简化剥离过程,Python Standard库提供了一个专门的功能,MLSTREPERE,MLSTREPERIPLE,MLSTREPERE,MLSTREPERIPE,MLSTREPERCE,MLST...
    编程 发布于2025-05-20
  • Java的Map.Entry和SimpleEntry如何简化键值对管理?
    Java的Map.Entry和SimpleEntry如何简化键值对管理?
    A Comprehensive Collection for Value Pairs: Introducing Java's Map.Entry and SimpleEntryIn Java, when defining a collection where each element com...
    编程 发布于2025-05-20
  • 如何从PHP中的Unicode字符串中有效地产生对URL友好的sl。
    如何从PHP中的Unicode字符串中有效地产生对URL友好的sl。
    为有效的slug生成首先,该函数用指定的分隔符替换所有非字母或数字字符。此步骤可确保slug遵守URL惯例。随后,它采用ICONV函数将文本简化为us-ascii兼容格式,从而允许更广泛的字符集合兼容性。接下来,该函数使用正则表达式删除了不需要的字符,例如特殊字符和空格。此步骤可确保slug仅包含...
    编程 发布于2025-05-20
  • 如何在其容器中为DIV创建平滑的左右CSS动画?
    如何在其容器中为DIV创建平滑的左右CSS动画?
    通用CSS动画,用于左右运动 ,我们将探索创建一个通用的CSS动画,以向左和右移动DIV,从而到达其容器的边缘。该动画可以应用于具有绝对定位的任何div,无论其未知长度如何。问题:使用左直接导致瞬时消失 更加流畅的解决方案:混合转换和左 [并实现平稳的,线性的运动,我们介绍了线性的转换。这...
    编程 发布于2025-05-20
  • 如何使用Python的请求和假用户代理绕过网站块?
    如何使用Python的请求和假用户代理绕过网站块?
    如何使用Python的请求模拟浏览器行为,以及伪造的用户代理提供了一个用户 - 代理标头一个有效方法是提供有效的用户式header,以提供有效的用户 - 设置,该标题可以通过browser和Acterner Systems the equestersystermery和操作系统。通过模仿像Chro...
    编程 发布于2025-05-20
  • 如何在Chrome中居中选择框文本?
    如何在Chrome中居中选择框文本?
    选择框的文本对齐:局部chrome-inly-ly-ly-lyly solument 您可能希望将文本中心集中在选择框中,以获取优化的原因或提高可访问性。但是,在CSS中的选择元素中手动添加一个文本 - 对属性可能无法正常工作。初始尝试 state)</option> < op...
    编程 发布于2025-05-20
  • 如何在JavaScript对象中动态设置键?
    如何在JavaScript对象中动态设置键?
    在尝试为JavaScript对象创建动态键时,如何使用此Syntax jsObj['key' i] = 'example' 1;不工作。正确的方法采用方括号: jsobj ['key''i] ='example'1; 在JavaScript中,数组是一...
    编程 发布于2025-05-20
  • 如何使用Python理解有效地创建字典?
    如何使用Python理解有效地创建字典?
    在python中,词典综合提供了一种生成新词典的简洁方法。尽管它们与列表综合相似,但存在一些显着差异。与问题所暗示的不同,您无法为钥匙创建字典理解。您必须明确指定键和值。 For example:d = {n: n**2 for n in range(5)}This creates a dicti...
    编程 发布于2025-05-20
  • \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    答案: 在大多数现代编译器中,while(1)和(1)和(;;)之间没有性能差异。编译器: perl: 1 输入 - > 2 2 NextState(Main 2 -E:1)V-> 3 9 Leaveloop VK/2-> A 3 toterloop(next-> 8 last-> 9 ...
    编程 发布于2025-05-20
  • 为什么尽管有效代码,为什么在PHP中捕获输入?
    为什么尽管有效代码,为什么在PHP中捕获输入?
    在php ;?>" method="post">The intention is to capture the input from the text box and display it when the submit button is clicked.但是,输出...
    编程 发布于2025-05-20
  • 如何在鼠标单击时编程选择DIV中的所有文本?
    如何在鼠标单击时编程选择DIV中的所有文本?
    在鼠标上选择div文本单击带有文本内容,用户如何使用单个鼠标单击单击div中的整个文本?这允许用户轻松拖放所选的文本或直接复制它。 在单个鼠标上单击的div元素中选择文本,您可以使用以下Javascript函数: function selecttext(canduterid){ if(do...
    编程 发布于2025-05-20
  • 在JavaScript中如何并发运行异步操作并正确处理错误?
    在JavaScript中如何并发运行异步操作并正确处理错误?
    同意操作execution 在执行asynchronous操作时,相关的代码段落会遇到一个问题,当执行asynchronous操作:此实现在启动下一个操作之前依次等待每个操作的完成。要启用并发执行,需要进行修改的方法。 第一个解决方案试图通过获得每个操作的承诺来解决此问题,然后单独等待它们: co...
    编程 发布于2025-05-20
  • 如何使用不同数量列的联合数据库表?
    如何使用不同数量列的联合数据库表?
    合并列数不同的表 当尝试合并列数不同的数据库表时,可能会遇到挑战。一种直接的方法是在列数较少的表中,为缺失的列追加空值。 例如,考虑两个表,表 A 和表 B,其中表 A 的列数多于表 B。为了合并这些表,同时处理表 B 中缺失的列,请按照以下步骤操作: 确定表 B 中缺失的列,并将它们添加到表的末...
    编程 发布于2025-05-20
  • Java中假唤醒真的会发生吗?
    Java中假唤醒真的会发生吗?
    在Java中的浪费唤醒:真实性或神话?在Java同步中伪装唤醒的概念已经是讨论的主题。尽管存在这种行为的潜力,但问题仍然存在:它们实际上是在实践中发生的吗? Linux的唤醒机制根据Wikipedia关于伪造唤醒的文章,linux实现了pthread_cond_wait()功能的Linux实现,利用...
    编程 发布于2025-05-20

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3