响应式镂空文字与循环视频

Avatar of Giulio Mainardi
Giulio Mainardi 发布

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

这是一个想法!让我们在一些字母的形状内播放 HTML <video>。 就像 “镂空文字” 一样,只是它后面不是图片,而是视频。一个实时演示会更清楚地解释

如果您在 Safari 中查看,您可能需要 参考此评论 以使一切正常运行。

这里的一个关键目标是响应式地开发它。其想法不仅是调整视频的大小以适应父容器(就像视频通常那样),而且还要调整文本的大小,并保持类型和底层视频之间的大小关系。

我们将通过使用 CSS clip-path 属性来剪辑视频,以剪辑针对在 SVG 路径 中定义的 元素。

首先,我们将介绍核心概念。然后,我们将通过添加一些更多功能来添加一些视觉效果,并通过几个额外的演示来演示它们。

在 HTML 中设置视频

让我们从视频标记开始。在 <video> 标签内,我们指定了 WebMMP4 版本视频的 URL,以支持广泛的浏览器

<video>
  <source src="/video/mt-baker.webm" type="video/webm">
  <source src="/video/mt-baker.mp4" type="video/mp4">
</video>

然后,我们添加一些属性来自定义视频播放行为

<video loop autoplay muted playsinline>
  <source src="/video/mt-baker.webm" type="video/webm">
  <source src="/video/mt-baker.mp4" type="video/mp4">
</video>
  • loop 属性使视频永久播放
  • autoplaymuted 属性用于允许在所有设备上页面加载时自动播放视频,并确保它在没有音频的情况下播放
  • playsinline 属性尝试在某些移动设备上播放视频时禁用视频的全屏扩展

然后,一个容器围绕 <video> 元素进行包装。当我们用一些视觉效果来增强基本演示时,这将派上用场。

这是完整的标记

<div class="video-container">
  <video loop autoplay muted playsinline>
    <source src="/video/mt-baker.webm" type="video/webm">
    <source src="/video/mt-baker.mp4" type="video/mp4">
  </video>
</div>

用于定位和缩放视频的基本 CSS

在这里,主要任务是使视频响应式。它必须放大和缩小,跨越其父容器的宽度并保持其固有的纵横比

.video-container video {
  display: block;
  width: 100%;
}

在将视频格式化为块级元素(使用 display: block)后,为其宽度分配 100% 的值使其与容器一样大。而这正是我们所需要的,因为height 属性的初始 auto 值和视频固有的纵横比 使其均匀缩放,不会失真。

在原始演示中,我重复了几次容器中的视频以测试这种响应式行为。这是我用于测试不同宽度下每个视频的响应能力的 CSS

.video-container {
  margin: 40px auto;
  outline: 1px solid #dadada;
  &:nth-of-type(1) { width: 25%; } /* first video instance, and so on */
  &:nth-of-type(2) { width: 50%; }
  &:nth-of-type(3) { width: 75%; }
  &:nth-of-type(4) { width: 100%; }
}
Four screenshots of the same video of a snowcapped mountain at different sizes to demonstrate different widths.

容器上设置的 outline 将帮助我们可视化视频在被剪辑时的位置和大小。说到这里……

在 CSS 中剪辑视频

借助 clip-path CSS 属性,我们可以定义元素要显示的区域而不是整个元素——并用一行代码完成它

video {
  clip-path: url(#clip-00);
}

使用 url() 函数,我们指定了定义剪辑文本的 SVG clipPath 元素的 ID。

请注意,尽管视频的可见几何形状被剪辑所改变,但视频元素对于浏览器来说仍然是一个矩形框,因此页面布局与未使用剪辑时相同。这是剪辑的优点之一,如果您不熟悉这个概念,那么一定要查看 Sarah 对它的解释

现在,诀窍实际上是创建该剪辑路径!

创建 SVG clipPath 元素

我将创建文本设计,进行编辑,然后在 Inkscape 中导出作品。类似的步骤可以通过其他矢量编辑器组合在一起。欢迎在评论中分享您的经验。

第一步是记下视频的纵横比,即其 width:height 比例。

接下来,创建一个与视频大小相同或仅具有相同纵横比的文档。因此,例如,如果您的视频宽度为 1000px,高度为 500px,那么您可以将其用作文档大小或任何 2:1 比例。这将充当视频的占位符,因此请确保文档边框可见。此外,删除程序创建的默认图层,因为这将简化 SVG 生成的代码,避免创建不必要的组元素。

A screenshot of a blank canvas in the Inkscape editor.

选择文本工具,选择一个漂亮的黑体粗体字体,输入文本并根据需要将其拉伸以覆盖文档矩形。

A screenshot of the word Text in all caps printed on the Inkscape canvas.

现在,如果我们查看 Inkscape 生成的代码,我们会看到如下内容

<text
  xml:space="preserve"
  style="font-style:normal;font-weight:normal;font-size:1147.68029785px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
  x="23.404421"
  y="939.04187"
  id="text3336"
  sodipodi:linespacing="125%"
  transform="scale(0.8988993,1.1124717)"
>
  <tspan
    sodipodi:role="line"
    id="tspan3338"
    x="23.404421"
    y="939.04187"
    style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:condensed;font-family:Impact;-inkscape-font-specification:'Impact Condensed'"
  >
    TEXT
  </tspan>
</text>

这有点笨拙,所以让我们将 SVG 元素转换为路径

<g
  transform="scale(0.8988993,1.1124717)"
  style="font-style:normal;font-weight:normal;font-size:1147.68029785px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
  id="text3336"
>
  <path 
    d="m 545.68862,31.769213 0,181.566607 -140.09769,0 0,725.70605 -235.92452,0 0,-725.70605 -139.5373,0 0,-181.566607 515.55951,0 z"
    style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:condensed;font-family:Impact;-inkscape-font-specification:'Impact Condensed'"
    id="path3348" />
  <path
    d="m 599.48616,31.769213 393.39432,0 0,181.566607 -157.46981,0 0,172.03997 147.38277,0 0,172.60036 -147.38277,0 0,199.49911 173.16073,0 0,181.56661 -409.08524,0 0,-907.272657 z"
    style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:condensed;font-family:Impact;-inkscape-font-specification:'Impact Condensed'"
    id="path3350" />
<path
  d="M 1554.9524,31.769213 1472.575,433.009 l 124.4067,506.03287 -218.5524,0 q -39.2273,-135.61457 -71.73,-330.07016 -8.9662,85.73978 -24.6572,182.127 l -22.4156,147.94316 -229.7602,0 85.1794,-506.03287 -85.1794,-401.239787 228.079,0 q 6.1643,37.546181 24.6572,124.967137 14.5702,66.68651 24.0968,122.16519 l 50.4352,-247.132327 197.8179,0 z"
  style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:condensed;font-family:Impact;-inkscape-font-specification:'Impact Condensed'"
  id="path3352" />
<path
  d="m 2105.8165,31.769213 0,181.566607 -140.0976,0 0,725.70605 -235.9246,0 0,-725.70605 -139.5373,0 0,-181.566607 515.5595,0 z"
  style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:condensed;font-family:Impact;-inkscape-font-specification:'Impact Condensed'"
  id="path3354" />
</g>

现在,我们有一个组,其中包含文本每个字母的路径元素。取消组合它会将这组路径保留在根级别。除了删除不需要的组之外,此步骤还会修改路径元素的坐标以考虑应用于已删除父组的转换。因此,在此步骤之后,SVG 中没有转换属性

<path
  inkscape:connector-curvature="0"
  id="path3348"
  style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:condensed;font-size:medium;line-height:125%;font-family:Impact;-inkscape-font-specification:'Impact Condensed';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
  d="m 490.51912,35.34235 0,201.98771 -125.93372,0 0,807.32744 -212.07238,0 0,-807.32744 -125.429984,0 0,-201.98771 463.436084,0 z" />
<path
  inkscape:connector-curvature="0"
  id="path3350"
  style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:condensed;font-size:medium;line-height:125%;font-family:Impact;-inkscape-font-specification:'Impact Condensed';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
  d="m 538.87769,35.34235 353.62188,0 0,201.98771 -141.5495,0 0,191.3896 132.48227,0 0,192.01302 -132.48227,0 0,221.93711 155.65406,0 0,201.98771 -367.72644,0 0,-1009.31515 z" />
<path
  inkscape:connector-curvature="0"
  id="path3352"
  style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:condensed;font-size:medium;line-height:125%;font-family:Impact;-inkscape-font-specification:'Impact Condensed';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
  d="m 1397.7456,35.34235 -74.049,446.36791 111.8291,562.94724 -196.4566,0 q -35.2614,-150.86737 -64.478,-367.19371 -8.0597,95.38308 -22.1644,202.61114 l -20.1493,164.58257 -206.5313,0 76.5677,-562.94724 -76.5677,-446.36791 205.02,0 q 5.5411,41.769064 22.1644,139.0224 13.0971,74.18686 21.6606,135.90532 l 45.3362,-274.92772 177.8183,0 z" />
<path
  inkscape:connector-curvature="0"
  id="path3354"
  style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:condensed;font-size:medium;line-height:125%;font-family:Impact;-inkscape-font-specification:'Impact Condensed';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
  d="m 1892.917,35.34235 0,201.98771 -125.9337,0 0,807.32744 -212.0724,0 0,-807.32744 -125.43,0 0,-201.98771 463.4361,0 z" />

那些难看的样式属性仍然存在,但这没关系,因为我们不会使用它们。

我们已经完成了 Inkscape 的操作,因此我们可以保存 SVG 文档并在我们最喜欢的代码编辑器中打开它。

从插图应用程序保存 SVG 文件本身就是一门艺术。以下是一些关于 Adobe Illustrator 的提示和常见设置 以及 来自 Illustrator、Sketch 和 Figma 的 SVG 导出比较 的常见 提示和常见设置

在播放视频的 HTML 页面中,插入一个内联 SVG 元素。这将承载剪辑路径

<svg class="clipping-paths">
  <defs>
  </defs>
</svg>

我们不希望此容器元素影响页面布局,因此

.clipping-paths {
  width: 0;
  height: 0;
  position: absolute;
}

向容器 SVG 添加一个新的 clipPath 元素,并使用适当的 ID 在 clip-path 属性中识别它,如前所述

<svg class="clipping-paths">
  <defs>
    <clipPath id="clip-00"></clipPath>
    <clipPath id="clip-01"></clipPath>
     ...
  </defs>
</svg>

如上所示代码片段,如果需要,可以添加多个剪辑路径。

现在,我们必须将 objectBoundingBox 值分配给 clipPath 的属性 clipPathUnits

<clipPath id="clip-00" clipPathUnits="objectBoundingBox"></clipPath>

这是实现响应式剪切的必要操作之一。我们告诉浏览器为将附加到 clipPath 元素的路径的 d 属性中列出的数值使用哪个坐标系。

clipPathUnits 属性的默认值为 userSpaceOnUse根据规范,这意味着

clipPath 元素的内容表示在引用(clipPath)元素时(即,通过 clip-path 属性引用 clipPath 元素的元素的用户坐标系)当前用户坐标系中的值。

相反,通过使用 clipPathUnits 的另一个可用值 objectBoundingBoxclipPath 内容中指定的坐标值和长度被视为 [0,1] 范围内的分数值,相对于被剪辑元素的边界框。例如,如果 clipPath 中的图形基元引用了坐标 (0,0)、(1,0)、(1,1)、(0,1) 和 (0.5,0.5),则无论视频在屏幕上的实际大小如何,这些点分别表示视频的左上角、右上角、右下角、左下角和中心。正是这种行为使文本剪切能够与底层视频以相同的方式缩放。

在从 Inkscape 中删除 SVG 文件中的任何转换后,路径元素的所有数值现在都在文档范围内。要将其映射到 objectBoundingBox 所需的 [0,1] 区间,只需将这些数值除以文档大小即可。x 坐标除以文档宽度,y 坐标除以文档高度。

为了完成此除法,可以使用 SVG transform 属性进行缩放

<clipPath id="clip-00" clipPathUnits="objectBoundingBox" transform="scale(0.0005208333333333333, 0.000925925925925926)">

水平缩放必须是文档宽度的倒数,而垂直缩放必须是文档高度的倒数。因此,在我们的例子中,假设文档与视频具有完全相同的大小,即 1920 X 1080,我们得到两个因子 1 / 1920 = 0.00052083333333333331 / 1080 = 0.000925925925925926

此工作流程中的最后一步是从 Inkscape 生成的文件中复制 元素到 clipPath 元素中,得到如下内容

<clipPath id="clip-00" clipPathUnits="objectBoundingBox" transform="scale(0.0005208333333333333, 0.000925925925925926)">
  <path d="m 341.4087,58.040727 0,951.927273 -112.60986,0 -66.8335,-432.74767 0,432.74767 -107.42188,0 0,-951.927273 107.42188,0 72.02149,428.631863 0,-428.631863 107.42187,0 z" />
  ...
  <path d="m 1642.6782,58.040727 214.2334,0 0,190.503053 -85.7544,0 0,180.50751 80.2613,0 0,181.09549 -80.2613,0 0,209.31816 94.2994,0 0,190.50306 -222.7784,0 0,-951.927273 z" />
</clipPath>

重申一下,我们只复制了路径的 d 属性,丢弃了 Inkscape 生成的所有其他内容。

整合所有内容

就这样,将以上代码片段与更多代码粘合在一起(有关详细信息,请参阅笔的源代码),我们得到了第一个基本演示。

其他示例

我们可以重用上述工作流程来创建更多剪裁,例如此处第二个演示中的剪裁。

最后一个示例值得关注,因为它与第一个演示中使用的文本形成关系;也就是说,路径分隔的内部和外部区域已交换。因此,我们看到的白色文本是一个孔,页面主体背景色可以通过该孔看到。此负剪裁是从原始剪裁中使用Inkscape中的差值路径操作派生的。

添加背景颜色

基于基本演示,我们可以通过为视频容器分配background-color来为文本剪裁添加颜色,因为此包装器元素在视频被剪裁出的所有位置都可见。

.video-container {
  // ...
  &:nth-of-type(1) { background: #c6c7c5; }
  &:nth-of-type(2) { background: #dcf3ff; }
  &:nth-of-type(3) { background: #a2d2df; }
  &:nth-of-type(4) { background: #406e8d; }
  &:nth-of-type(5) { background: linear-gradient(180deg, #bdc3c2, #2d5d89); }
}

结果可以在第三个演示中看到。

添加色调

如何为视频着色?我们可以使用伪元素和CSS混合模式来实现。

.video-container {
  /* ... */ 
  position: relative;
}

.video-container::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  clip-path: url(#clip-00);
}
        
.video-container:nth-of-type(1) {
  background: #406e8d;
}

.video-container:nth-of-type(1)::after {
  background-color: #3f51b5;
  mix-blend-mode: screen;
}

视频包装器的::after伪元素绝对定位在视频顶部,完全覆盖它,然后使用与视频相同的路径对其进行剪裁。这样,可以为背景和色调使用不同的颜色。

最后,在为伪元素分配所需的色调颜色后,可以使用mix-blend-mode属性选择可用的混合模式之一以获得所需的效果。

浏览器支持

在撰写本文时,几乎所有最新版本的桌面和移动浏览器都支持CSS clip-path属性。前缀版本-webkit-clip-path必须用于基于WebKit的用户代理,例如Safari和Samsung Internet。

此浏览器支持数据来自Caniuse,其中包含更多详细信息。数字表示浏览器在该版本及更高版本中支持该功能。

桌面

ChromeFirefoxIEEdgeSafari
13054127TP

移动/平板电脑

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712718.0

不幸的是,Microsoft浏览器是一个例外。Internet Explorer绝对不支持,而Edge上的功能正在开发中。请在Microsoft开发者反馈页面上为其投票!

当然,对于最后一个演示,需要支持CSS混合模式的浏览器。在这种情况下,支持范围更广,因为Edge甚至也实现了此功能。

此浏览器支持数据来自Caniuse,其中包含更多详细信息。数字表示浏览器在该版本及更高版本中支持该功能。

桌面

ChromeFirefoxIEEdgeSafari
3020136.1

移动/平板电脑

Android ChromeAndroid FirefoxAndroidiOS Safari
1271274.47.0-7.1

参考文献和鸣谢

Eric Meyer撰写了关于为HTML元素缩放SVG剪裁路径的文章,使用objectBoundingBox值作为clipPathUnits属性,以及缩放转换。这对这篇文章的创作产生了巨大的启发。

感谢Coverr提供了这些演示中使用的视频。