这是一个想法!让我们在一些字母的形状内播放 HTML <video>
。 就像 “镂空文字” 一样,只是它后面不是图片,而是视频。一个实时演示会更清楚地解释
如果您在 Safari 中查看,您可能需要 参考此评论 以使一切正常运行。
这里的一个关键目标是响应式地开发它。其想法不仅是调整视频的大小以适应父容器(就像视频通常那样),而且还要调整文本的大小,并保持类型和底层视频之间的大小关系。
我们将通过使用 CSS clip-path
属性来剪辑视频,以剪辑针对在 SVG 路径 中定义的 元素。
首先,我们将介绍核心概念。然后,我们将通过添加一些更多功能来添加一些视觉效果,并通过几个额外的演示来演示它们。
在 HTML 中设置视频
让我们从视频标记开始。在 <video>
标签内,我们指定了 WebM 和 MP4 版本视频的 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
属性使视频永久播放autoplay
和muted
属性用于允许在所有设备上页面加载时自动播放视频,并确保它在没有音频的情况下播放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%; }
}

容器上设置的 outline
将帮助我们可视化视频在被剪辑时的位置和大小。说到这里……
在 CSS 中剪辑视频
借助 clip-path
CSS 属性,我们可以定义元素要显示的区域而不是整个元素——并用一行代码完成它
video {
clip-path: url(#clip-00);
}
使用 url()
函数,我们指定了定义剪辑文本的 SVG clipPath
元素的 ID。
请注意,尽管视频的可见几何形状被剪辑所改变,但视频元素对于浏览器来说仍然是一个矩形框,因此页面布局与未使用剪辑时相同。这是剪辑的优点之一,如果您不熟悉这个概念,那么一定要查看 Sarah 对它的解释。
现在,诀窍实际上是创建该剪辑路径!
clipPath
元素
创建 SVG 我将创建文本设计,进行编辑,然后在 Inkscape 中导出作品。类似的步骤可以通过其他矢量编辑器组合在一起。欢迎在评论中分享您的经验。
第一步是记下视频的纵横比,即其 width:height
比例。
接下来,创建一个与视频大小相同或仅具有相同纵横比的文档。因此,例如,如果您的视频宽度为 1000px
,高度为 500px
,那么您可以将其用作文档大小或任何 2:1 比例。这将充当视频的占位符,因此请确保文档边框可见。此外,删除程序创建的默认图层,因为这将简化 SVG 生成的代码,避免创建不必要的组元素。

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

现在,如果我们查看 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
的另一个可用值 objectBoundingBox
,clipPath
内容中指定的坐标值和长度被视为 [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.0005208333333333333
和 1 / 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,其中包含更多详细信息。数字表示浏览器在该版本及更高版本中支持该功能。
桌面
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
130 | 54 | 否 | 127 | TP |
移动/平板电脑
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
127 | 127 | 127 | 18.0 |
不幸的是,Microsoft浏览器是一个例外。Internet Explorer绝对不支持,而Edge上的功能正在开发中。请在Microsoft开发者反馈页面上为其投票!
当然,对于最后一个演示,需要支持CSS混合模式的浏览器。在这种情况下,支持范围更广,因为Edge甚至也实现了此功能。
此浏览器支持数据来自Caniuse,其中包含更多详细信息。数字表示浏览器在该版本及更高版本中支持该功能。
桌面
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
30 | 20 | 否 | 13 | 6.1 |
移动/平板电脑
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
127 | 127 | 4.4 | 7.0-7.1 |
参考文献和鸣谢
Eric Meyer撰写了关于为HTML元素缩放SVG剪裁路径的文章,使用objectBoundingBox
值作为clipPathUnits
属性,以及缩放转换。这对这篇文章的创作产生了巨大的启发。
感谢Coverr提供了这些演示中使用的视频。
太棒了!真的太酷了,Giulio!
所以……在最新版本的Chrome或Safari中,iOS 11.3上的所有CodePen都不起作用。:/
感谢您的反馈!
看来WebKit在使用SVG clipPath元素定义的路径裁剪html元素方面遇到了一些困难。我正在整理一些笔来展示这些问题。敬请关注!
最后两个在Chrome 64位Linux上没有显示视频。
WebKit(在iOS Safari 11.2.6上测试)在剪裁
<video>
方面遇到了一些问题,即使是最简单的<clipPath>
,也没有渲染任何内容。此外,https://lab.iamvdo.me/css-svg-masks/提供了几个有趣的测试(尽管没有使用视频)。
@jgarcianewemage
您能告诉我第三支笔(背景颜色)发生了什么吗?
哪个Chrome版本?
此外,您是否尝试过使用这些测试?
更新:如果将视频包装在DIV中并将
clip-path
应用于此容器,则基本笔在iOS Safari上有效:https://codepen.io/mgiulio/pen/bMLWWw?editors=0100但是,文本剪裁没有完美地转换,存在一点向上偏移,如不均匀的紫色填充所揭示的那样。使用图像而不是视频时也会发生同样的问题。