Avatar of Blake Lundquist
Blake Lundquist

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

这是一个向朋友展示的有趣的 CSS 技巧:一个大型标题,随着其背后的背景图像滚动到位,标题会从纯色切换到挖空文本。并且我们可以使用普通的 HTML 和 CSS 来实现它!

此效果是通过渲染两个包含固定 <h1> 元素的容器来创建的。第一个容器具有白色背景和挖空文本。第二个容器具有背景图像和白色文本。然后,使用一些巧妙的剪裁技巧,当用户滚动超出其边界时,我们隐藏第一个容器的文本,反之亦然。这创造了一种文本背景正在变化的错觉。

在开始之前,请注意,这在旧版本的 Internet Explorer 上不起作用。此外,在移动 WebKit 浏览器上,固定背景图像可能很麻烦。请务必考虑针对这些情况的回退行为。

设置 HTML

让我们从创建一般的 HTML 结构开始。在外部包装器内,我们创建了两个相同的容器,每个容器都包含一个 <h1> 元素,该元素包装在 .title_wrapper 中。

<header>

  <!-- First container -->
  <div class="container container_solid">
    <div class="title_wrapper">
      <h1>The Great Outdoors</h1>
    </div>
  </div>

  <!-- Second container -->
  <div class="container container_image">
    <div class="title_wrapper">
      <h1>The Great Outdoors</h1>
    </div>
  </div>

</header>

请注意,每个容器都具有全局 .container 类及其自己的标识符类——分别为 .container_solid.container_image。这样,我们就可以创建通用的基本样式,并使用 CSS 单独定位每个容器。

初始样式

现在,让我们向容器添加一些 CSS。我们希望每个容器都占据屏幕的完整高度。第一个容器需要一个纯白色的背景,我们可以在其 .container_solid 类上实现这一点。我们还希望向第二个容器添加一个固定的背景图像,我们可以在其 .container_image 类上实现这一点。

.container {
  height: 100vh;
}

/* First container */
.container_solid {
  background: white;
}

/* Second container */
.container_image {
  /* Grab a free image from unsplash */
  background-image: url(/path/to/img.jpg);
  background-size: 100vw auto;
  background-position: center;
  background-attachment: fixed;
}

接下来,我们可以稍微调整一下 <h1> 元素的样式。.container_image 内部的文本可以简单地设置为白色。但是,要为 .container_image 内部的 <h1> 元素获取挖空文本,我们需要应用背景图像,然后使用 text-fill-colorbackground-clip CSS 属性将背景应用于文本本身,而不是 <h1> 元素的边界。请注意,<h1> 背景的大小与我们的 .container_image 元素相同。这对于确保内容对齐非常重要。

.container_solid .title_wrapper h1 {
  /* The text background */
  background: url(https://images.unsplash.com/photo-1575058752200-a9d6c0f41945?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ);
  background-size: 100vw auto;
  background-position: center;
  
  /* Clip the text, if possible */
  /* Including -webkit` prefix for bester browser support */
  /* https://caniuse.cn/text-stroke */
  -webkit-text-fill-color: transparent;
  text-fill-color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  
  /* Fallback text color */
  color: black;
}

.container_image .title_wrapper h1 {
  color: white;
}

现在,我们希望文本固定在布局的中心。我们将向全局 .title_wrapper 类添加固定定位,并将其固定到窗口的垂直中心。然后我们使用 text-align 将我们的 <h1> 元素水平居中。

.header-text {
  display: block;
  position: fixed; 
  margin: auto;
  width: 100%;
  /* Center the text wrapper vertically */
  top: 50%;
  -webkit-transform: translateY(-50%);
      -ms-transform: translateY(-50%);
          transform: translateY(-50%);
}

.header-text h1 {
  text-align: center;
}

此时,每个容器中的 <h1> 应该彼此直接叠加,并在用户滚动时保持固定在窗口的中心。以下是包含一些阴影的完整、组织良好的代码,以便更好地查看文本定位。

剪裁文本和容器

这就是事情开始变得真正有趣的地方。我们只希望容器的 <h1> 在其当前滚动位置在其父容器的边界内时可见。通常,这可以使用父容器上的 overflow: hidden; 来解决。但是,由于我们的两个 <h1> 元素都使用固定定位,因此它们现在相对于浏览器窗口而不是父元素进行定位。在这种情况下,使用 overflow: hidden; 将不起作用。

为了使父容器隐藏固定的溢出内容,我们可以使用 CSS clip 属性和绝对定位。这告诉我们的浏览器隐藏元素边界之外的任何内容。让我们替换 .container 类的样式,以确保它们不会显示任何溢出的元素,即使这些元素使用固定定位。

.container {
  /* Hide fixed overflow contents */
  clip: rect(0, auto, auto, 0);

  /* Does not work if overflow = visible */
  overflow: hidden;

  /* Only works with absolute positioning */
  position: absolute;

  /* Make sure containers are full-width and height */
  height: 100vh;
  left: 0;
  width: 100%;
}

现在我们的容器使用绝对定位,它们从正常的 콘텐츠 流中移除。并且,因此,我们需要手动将它们相对于其各自的父元素进行定位。

.container_solid {
  /* ... */

  /* Position this container at the top of its parent element */
  top: 0;
}

.container_image {
  /* ... */

/* Position the second container below the first container */
  top: 100vh;
}

此时,效果应该开始成形了。您可以看到滚动会产生一种错觉,即挖空文本似乎正在更改背景。实际上,它只是我们的剪裁蒙版根据哪个父容器与屏幕中心重叠来显示不同的 <h1> 元素。

让 Safari 满意

如果您使用的是 Safari,您可能已经注意到,在滚动时,其渲染引擎没有正确刷新视图。将以下代码添加到 .container 类以强制其正确刷新。

.container {
  /* ... */

  /* Safari hack */
  -webkit-mask-image: -webkit-linear-gradient(top, #ffffff 0%,#ffffff 100%);
}

这是到目前为止的完整代码。

是时候清理了

让我们确保我们的 HTML 遵循无障碍最佳实践。不使用辅助技术的用户无法分辨文档中是否存在两个相同的 <h1> 元素,但使用屏幕阅读器的用户肯定可以分辨,因为这两个标题都会被宣布。让我们向第二个容器添加 aria-hidden,以告知屏幕阅读器它纯粹是装饰性的。

<!-- Second container -->
<div class="container container_image" aria-hidden="true">
  <div class="title_wrapper">
    <h1>The Great Outdoors</h1>
  </div>
</div>

现在,在样式方面,世界就是我们的牡蛎。我们可以随意修改字体和字体大小,使文本达到我们想要的效果。我们甚至可以更进一步,添加视差效果或用视频替换背景图像。但是,嘿,在那种情况下,请务必在无障碍方面多做一些工作,以便那些更喜欢较少运动的人获得正确的体验

这并不难,对吧?