使用clip-path制作Web动效
clip-path
是CSS属性之一,只不过很多同学都担心浏览器对他的兼容性,因此不怎么使用该属性。其实clip-path
已经得到很好的支持,可以说现在主流浏览器对他的支持已经很好了。事实也是这样,就我自己而言,早在去年中开发的项目就已经有clip-path
的身影了。在CSS的世界中,clip-path
是一个很有意思的属性,他可以帮助我们绘制很多不同规则的图形(除了常见的圆形,椭圆形,矩形,三角形等),而且结合CSS的transition
或animation
的话,clip-path
能帮助我们实现一些很有意思的动画效果。接下来,就和大家聊聊使用clip-path
制作Web动效的一些事情。
clip-path
简介
CSS Masking Module Level 1主要涵盖了 CSS Clipping 和 CSS Masking 两个部分的内容,其中clip-path
就是 CSS Clipping中的。至于在实际使用的时候,什么时候时候使用Clipping,什么时候使用Masking,可以阅读《Clipping和Masking 何时使用》一文,这里就不做过多阐述了。而今天我们要聊的是clip-path
怎么制作Web动效。
在深入聊如何使用clip-path
制作Web动效之前,简单的了解一下clip-path
。如果用一句话来概述的话:
clip-path
可以让我们绘制规则图形,也可以绘制不规则的图形。
简单地说,clip-path
属性值可以是一些CSS函数,比如circle()
、inset()
、ellipse()
绘制像圆形,矩形和椭圆形等;对于一些其他图形,比如三角形,多边形等,可以使用polygon()
函数,甚至还可以使用url()
函数和SVG的路径结合起来。
社区中 Clippy 工具给我们展示了clip-path
如何绘制图形。
简单地说,Clippy展示了clip-path
属性如何使用 polygon()
函数绘制图形。这个函数其实非常的实用,我们可以用它来绘制很多不同的图形,比如下图这样的不规则图形:
就拿上图为例,我们来看clip-path
实现的整个过程:
对应的代码很简单:
.box {
width: 220px;
height: 64px;
margin: 0 10px;
}
.box:nth-child(1) {
background-color: #FF79B7;
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 8.18% 100%, 0% 71.875%);
}
.box:nth-child(2) {
background-color: #71C2DF;
clip-path: polygon(0% 0%, 100% 0%, 100% 71.875%, 91.818% 100%, 0% 100%);
}
效果如下:
内部白色区域,我们可以使用同样的方式来完成:
.box {
width: 220px;
height: 64px;
margin: 0 10px;
position: relative;
}
.box::after {
content: "";
position:absolute;
top: 8px;
right: 8px;
bottom: 8px;
left: 8px;
background-color: #fff;
}
.box:nth-child(1) {
background-color: #FF79B7;
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 8.18% 100%, 0% 71.875%);
}
.box:nth-child(2) {
background-color: #71C2DF;
clip-path: polygon(0% 0%, 100% 0%, 100% 71.875%, 91.818% 100%, 0% 100%);
}
.box:nth-child(1)::after {
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 6.862% 100%, 0% 70.8333%);
}
.box:nth-child(2)::after {
clip-path: polygon(0% 0%, 100% 0%, 100% 70.8333%, 93.137% 100%, 0% 100%);
}
我们可以在上例基础上稍作调整,可以有一个clip-path
实现的动画效果:
你将鼠标移动图形上,你会看到像下图这样的一个动效:
注意,上面示例中坐标的计算运用到了CSS百分比相关的知识,如果你想深入了解CSS中百分比是如何计算的,可以阅读《CSS中百分比单位计算方式》一文。
clip-path
和<clipPath>
clip-path
和clipPath
都是剪切,不同的是前者是CSS中的剪切,而<clipPath>
是SVG中的剪切,但他们是可以相互合作的。正如上面的示例所示,虽然我们使用clip-path
的polygon()
能描绘出所需的图形形状态,但计算总是蛮麻烦的。如果上图换到SVG的世界中来,那要简单的多,可以使用SVG的<path>
快速绘制出上图的效果:
将上图中的四条路径导出来,对应的SVG效果如下所示:
<path d="M1,1 C1,1 221,1 221,1 C221,1 221,65 221,65 C221,65 19.5,65 19.5,65 C19.5,65 1,45.5 1,45.5 C1,45.5 1,1 1,1 Z" id="path-1" stroke="#979797" fill="none" />
<path d="M1,1 C1,1 205,1 205,1 C205,1 205,49 205,49 C205,49 15,49 15,49 C15,49 1,34 1,34 C1,34 1,1 1,1 Z" id="path-2" stroke="#D0021B" fill="none" />
<path d="M1,1 C1,1 221,1 221,1 C221,1 221,45.5 221,45.5 C221,45.5 202.5,65 202.5,65 C202.5,65 1,65 1,65 C1,65 1,1 1,1 Z" id="path-3" stroke="#979797" fill="none" />
<path d="M1,1 C1,1 203,1 203,1 C203,1 203,32.5 203,32.5 C203,32.5 190,46.5 190,46.5 C190,46.5 1,47 1,47 C1,47 1,1 1,1 Z" id="path-4" stroke="#D0021B" fill="none" />
这样一来,我们可以把上面的四个<path>
绘制的路径当作剪切路径。
在SVG中,我们可以把上面几个<path>
放置到<clipPath>
元素中,因为<clipPath>
才是真正的剪切路径。SVG的<clipPath>
有一个特性,它是不会呈现在用户面前的,而且它唯一的用途是 作为剪切路径,即 只有被clip-path
的url()
函数引用才能起作用。
SVG的<clipPath>
一般像下面这样使用:
<svg width="222px" height="66px" viewBox="0 0 222 66" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<clipPath id="path-1">
<path d="M1,1 C1,1 221,1 221,1 C221,1 221,65 221,65 C221,65 19.5,65 19.5,65 C19.5,65 1,45.5 1,45.5 C1,45.5 1,1 1,1 Z" stroke="#979797" fill="none"></path>
</clipPath>
<clipPath id="path-2">
<path d="M1,1 C1,1 205,1 205,1 C205,1 205,49 205,49 C205,49 15,49 15,49 C15,49 1,34 1,34 C1,34 1,1 1,1 Z" stroke="#D0021B" fill="none"></path>
</clipPath>
<clipPath id="path-3">
<path d="M1,1 C1,1 221,1 221,1 C221,1 221,45.5 221,45.5 C221,45.5 202.5,65 202.5,65 C202.5,65 1,65 1,65 C1,65 1,1 1,1 Z" stroke="#979797" fill="none"></path>
</clipPath>
<clipPath id="path-4">
<path d="M1,1 C1,1 203,1 203,1 C203,1 203,32.5 203,32.5 C203,32.5 190,46.5 190,46.5 C190,46.5 1,47 1,47 C1,47 1,1 1,1 Z" stroke="#D0021B" fill="none"></path>
</clipPath>
</defs>
</svg>
上面的SVG并不会渲染出任何图形,只有和clip-path
结合使用才能生效。另外,为了能让clip-path
引用指定的剪切路径,我们在每个<clipPath>
定义了一个id
值。CSS的clip-path
引用指定的SVG的<clipPath>
很简单:
.box {
width: 220px;
height: 64px;
margin: 5px;
background-color: #ff79b7;
clip-path: url(#path-1);
position: relative;
}
.box::after {
content: "";
position: absolute;
top: 8px;
right: 8px;
bottom: 8px;
left: 8px;
background-color: #fff;
clip-path: url(#path-2)
}
.box__2 {
background-color: #71c2df;
clip-path: url(#path-3);
}
.box__2::after {
clip-path: url(#path-4);
}
这个时候你看到的效果如下:
同样的,上面的示例中稍作调整就可以实现下面示例的动画效果:
你可以尝试把鼠标移到图形上,会看到下图这样的效果:
你可能发现了,上面这个示例的效果没有第一个示例那么好。
熟悉clip-path
的同学应该知道,polygon()
只能绘制多边形,无法绘制平滑的路径图形。而SVG的<clipPath>
可以,比如下面这个SVG代码:
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" width="200" id="blobSvg">
<path id="blob" d="M413.5,344Q359,438,257,426Q155,414,88.5,332Q22,250,78,149.5Q134,49,247,54Q360,59,414,154.5Q468,250,413.5,344Z" fill="#d1d8e0"></path>
</svg>
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" width="100%" id="blobSvg">
<path id="blob" d="M332,291.5Q202,333,211.5,266.5Q221,200,341.5,225Q462,250,332,291.5Z" fill="#d1d8e0"></path>
</svg>
上面的代码是使用SVG绘制的 Blobs 效果:
特别声明,上面的SVG代码是由 Blobs App 工具生成的。
如果我们把上面的两个<path>
当作<clipPath>
,那么可以实现像下面这个示例的效果:
clip-path
和path()
在clip-path
中还可以使用path()
函数绘制图形。这里的path()
是一个类似于url()
的函数。上面的示例,我们已经看到了SVG中有一个<path>
属性,这个属性可以绘制路径,比如:
<path d="M1,1 C1,1 221,1 221,1 C221,1 221,65 221,65 C221,65 19.5,65 19.5,65 C19.5,65 1,45.5 1,45.5 C1,45.5 1,1 1,1 Z" stroke="#979797" fill="none"></path>
基中d
的值就是一个SVG路径。在介绍<clipPath>
时,里面的<path>
就是我们需要指定的剪切路径。事实上,<path>
中d
的值也可以是path()
函数的值值,比如:
clip-path: path(
"M1,1 C1,1 221,1 221,1 C221,1 221,65 221,65 C221,65 19.5,65 19.5,65 C19.5,65 1,45.5 1,45.5 C1,45.5 1,1 1,1 Z"
);
按照这个思路,上面示例可以更换成下面这样:
.box {
width: 220px;
height: 64px;
position: relative;
background-color: #ff79b7;
margin: 5px;
clip-path: path(
"M1,1 C1,1 221,1 221,1 C221,1 221,65 221,65 C221,65 19.5,65 19.5,65 C19.5,65 1,45.5 1,45.5 C1,45.5 1,1 1,1 Z"
);
}
.box::after {
content: "";
position: absolute;
top: 8px;
right: 8px;
bottom: 8px;
left: 8px;
background-color: #fff;
clip-path: path(
"M1,1 C1,1 205,1 205,1 C205,1 205,49 205,49 C205,49 15,49 15,49 C15,49 1,34 1,34 C1,34 1,1 1,1 Z"
);
}
.box__2 {
background-color: #71c2df;
clip-path: path(
"M1,1 C1,1 221,1 221,1 C221,1 221,45.5 221,45.5 C221,45.5 202.5,65 202.5,65 C202.5,65 1,65 1,65 C1,65 1,1 1,1 Z"
);
}
.box__2::after {
clip-path: path(
"M1,1 C1,1 203,1 203,1 C203,1 203,32.5 203,32.5 C203,32.5 190,46.5 190,46.5 C190,46.5 1,47 1,47 C1,47 1,1 1,1 Z"
);
}
你可能没有看到path()
带来的效果,那是因为,到目前为止仅在Firefox 71+的浏览器中得到支持。如果你使用Firefox 71+查看上面的Demo的话,你会看到像下图这样的效果:
同样可以实现动画效果:
使用Firefox 71+浏览器,你能看到像下图这样的动画效果:
clip-path
和transition
和animation
的结合
CSS中transition
和animation
都可以给Web元素带来动画效果。同样的,他们也可以和clip-path
结合在一起使用,也就是说,在不同的状态改变clip-path
的值,就会产生一个变换效果。比如说,一个圆过渡到一个矩形就可以像下面这样做:
.box {
clip-path: circle(50%);
transition: clip-path 1s;
}
.box:hover {
clip-path: circle(75%);
}
在下面的示例中将鼠标悬浮动圆上,可以看到效果:
换句话说,元素只要在A
状态和B
状态的clip-path
值不同,就能看到类似像上例的一个简单过渡动效。
.box {
clip-path: polygon(
0% 20%,
60% 20%,
60% 0%,
100% 50%,
60% 100%,
60% 80%,
0% 80%
);
transition: clip-path 2s linear;
}
.box:hover {
clip-path: polygon(
40% 0%,
40% 20%,
100% 20%,
100% 80%,
40% 80%,
40% 100%,
0% 50%
);
}
同样的,我们可以在animation
的不同帧改变clip-path
,实现一个多状态的图形变化效果,比如下面这个示例:
@keyframes clip__path {
0% {
clip-path: circle(50% at 50% 50%);
}
10% {
clip-path: inset(5% 20% 15% 10%);
}
/* ... */
100% {
clip-path: polygon(100% 0%, 75% 50%, 100% 100%, 25% 100%, 0% 50%, 25% 0%);
}
}
你可能已经发现了,就上面的动画效果,有的会一闪就跳到另外一种形状,有的会慢慢过渡到另一种形状。接下来我们简单地来说一下其中的原因。
首先们我来制作一个简单的clip-path
动效,而且是由circle()
到inset()
:
@keyframes clipAni {
from {
clip-path: circle(70%);
}
to {
clip-path: inset(5% 20% 15% 10%);
}
}
圆形和矩形并不能平滑过渡,而是一闪而过:
如果我们把clip-path
从圆过渡到圆,只是改变circle()
的参数,比如半径,圆心位置:
@keyframes clipAni {
from {
clip-path: circle(20%);
}
to {
clip-path: circle(75%);
}
}
效果如下:
如果改变circle()
的圆心位置:
@keyframes clipAni {
from {
clip-path: circle(20% at 10% 10%);
}
to {
clip-path: circle(75% at 100% 100%);
}
}
接下来简单对clip-path
中每个形状做分解。
circle()
clip-path: circle(<length|percentage> at <position>);
circle()
接受两个可动画化的属性:
- 圆的半径
- 圆心的位置:可以是沿着
x
轴和y
轴位置
例如:
@keyframes circle {
0% {
clip-path: circle(75%);
}
100% {
clip-path: circle(0%);
}
}
ellipse()
clip-path: ellipse(<length|percentage>{2} at <position>);
ellipse()
可以接受三个可动画化的属性:
- 椭圆水平方向(
x
轴)的半径 - 椭圆垂直方向(
y
轴)的半径 - 椭圆圆心位置:可以是沿着
x
轴和y
轴的位置
inset()
clip-path: inset(<length|percentage>{1,4} round <border-radius>{1,4});
inset()
最多有五个可以动画化的属性。前四个表示形状的每条边,其行为类似于margin
或padding
。根据所需的形状,第一个属性是必须的,而后面三个属性是可选的。
@keyframes inset {
0% {
clip-path: inset(0% round 0%);
}
100% {
clip-path: inset(50% round 50%);
}
}
再来看一个效果:
@keyframes inset {
0% {
clip-path: inset(0 0 0 100%);
}
100% {
clip-path: inset(0 0 0 0);
}
}
用这个方式,我们可以轻易的实现舞台幕布拉开合并的动画效果:
polygon()
clip-path: polygon(<length|percentage>);
polygon()
相对来说是一种特殊的情况。使用polygon()
绘制多边形至少需要三个顶点,而每个顶点都是可动画化的。我们来分两种情形来看。先来看顶点数不一致的情况。比如从一个三角形(三个顶点)到五角形(五个顶点)的效果:
@keyframes polygon {
0% {
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}
100% {
clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
}
}
你看到的效果是三角形一闪就到了五角形:
接着再来看另一种情形,就是顶点数相同的情况:
@keyframes polygon {
0% {
clip-path: polygon(50% 0%, 90% 13%, 98% 35%, 91% 66%, 79% 91%, 52% 88%, 21% 91%, 7% 68%, 2% 35%, 23% 12%);
}
100% {
clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
}
}
效果如下:
从上面的效果我们可得知,使用polygon()
的顶点数数量相同时,我们可以构建出一个平滑的图形变化的动画效果。这个效果有点类似于 Morphing Animations。
polygon()
不仅仅是顶点数的集合。它还有另一个关键属性,就是拼图的规则。到目前为止,我们一直使用的默认值nonzero
,它还有另一个值evenodd
。下面这个示例演示了两者之间的差异:
path()
如果path()
可以得到更多浏览器支持的话,那么我们就可以将SVG中的path
的值当作path()
的值,而且我们在关键帧中设置不同的值,可以得到一个很好的图形过渡动效,比如下面这两条path
:
<path d="M103.13 100C103 32.96 135.29 -0.37 200 0L0 0C0.35 66.42 34.73 99.75 103.13 100Z
M199.35 200C199.83 133.21 167.75 99.88 103.13 100C102.94 165.93 68.72 199.26 0.46 200L199.35 200Z
M103.13 100C167.46 99.75 199.54 133.09 199.35 200L200 0C135.15 -0.86 102.86 32.47 103.13 100Z
M0 200C68.63 200 103 166.67 103.13 100C34.36 100.12 -0.02 66.79 0 0L0 200Z" stroke="#f36" fill="none" stroke-width="2"></path>
<path d="M60.85 2.56C108.17 -44.93 154.57 -45.66 200.06 0.35L58.64 -141.07C11.93 -93.85 12.67 -45.97 60.85 2.56Z
M139.87 340.05C187.44 293.16 188.33 246.91 142.54 201.29C95.79 247.78 48.02 247.15 -0.77 199.41L139.87 340.05Z
M201.68 61.75C247.35 107.07 246.46 153.32 199.01 200.5L340.89 59.54C295.65 13.07 249.25 13.81 201.68 61.75Z
M-140.61 141.25C-92.08 189.78 -44.21 190.51 3.02 143.46C-45.69 94.92 -46.43 47.05 0.81 -0.17L-140.61 141.25Z" stroke="#0f9" fill="none" stroke-width="2">
上面两条path
对应的效果如下:
将它们放到@keyframes
中:
@keyframes iris {
0% {
clip-path: path(
"
M103.13 100C103 32.96 135.29 -0.37 200 0L0 0C0.35 66.42 34.73 99.75 103.13 100Z
M199.35 200C199.83 133.21 167.75 99.88 103.13 100C102.94 165.93 68.72 199.26 0.46 200L199.35 200Z
M103.13 100C167.46 99.75 199.54 133.09 199.35 200L200 0C135.15 -0.86 102.86 32.47 103.13 100Z
M0 200C68.63 200 103 166.67 103.13 100C34.36 100.12 -0.02 66.79 0 0L0 200Z
"
);
}
100% {
clip-path: path(
"
M60.85 2.56C108.17 -44.93 154.57 -45.66 200.06 0.35L58.64 -141.07C11.93 -93.85 12.67 -45.97 60.85 2.56Z
M139.87 340.05C187.44 293.16 188.33 246.91 142.54 201.29C95.79 247.78 48.02 247.15 -0.77 199.41L139.87 340.05Z
M201.68 61.75C247.35 107.07 246.46 153.32 199.01 200.5L340.89 59.54C295.65 13.07 249.25 13.81 201.68 61.75Z
M-140.61 141.25C-92.08 189.78 -44.21 190.51 3.02 143.46C-45.69 94.92 -46.43 47.05 0.81 -0.17L-140.61 141.25Z
"
);
}
}
你将会得到像下图这样的动画效果:
换句话说,基于这种方式,我们可以很容易的实现 SVG Morphing Animations 效果。比如下图中有六个不同的形状的东西,然后我们从左到右,再从右到左进行形变。
我们使用clip-path
的path()
来构建,很容易就能实现:
用Firefox 71+浏览器查看上面的Demo,你将看到效果如下所示:
只可惜
clip-path:path()
得到支持的浏览器很少,但你要是对SVG熟悉的话,我们可以使用SVG来实现上面示例的效果。有关于这方面更多的介绍可以阅读@Ana Tudor的教程《Unfortunately,clip-path: path()
is Still a No-Go 》。
当然,如果你设计得好,使用polygon()
也可以实现形变(Morphing Animations)效果,比如@Mikael Ainalem写的一个Demo:
如果你想知道上例是如何实现现的,可以阅读@Mikael Ainalem的《Creating Morphing Animations with CSS clip-path》教程。
在CSS中,使用clip-path
可以帮助我们构建很多Web动效,比如@Travis Almand教程《Animating with Clip-Path》中提到的效果。
使用clip-path
制作动效技术手段并不复杂,难的是你要有一定的想象力和相关的创意。比如 @chokcoco在他的教程《clip-path
实现图片的故障艺术风格动画
》中就向我们展示了如何使用clip-path
实现故障艺术风格动效:
小结
最后希望本文已经让你对如何使用clip-path
创建动画效果有了一定的了解,甚至你可以通过本文的学习掌握了怎么用clip-path
制作有意思的动效,甚至在你的项目中可以立马使用到这种技术。事实上,clip-path
除了构建动画效果之外,在很多特殊的场景还可以帮助我们快速还原UI效果,甚至影响一些交互效果。
如果你在使用clip-path
有更多的经验或独到的见解,欢迎在下面的评论中与我们一起共享。