使用clip-path制作Web动效

发布于 大漠

clip-path是CSS属性之一,只不过很多同学都担心浏览器对他的兼容性,因此不怎么使用该属性。其实clip-path已经得到很好的支持,可以说现在主流浏览器对他的支持已经很好了。事实也是这样,就我自己而言,早在去年中开发的项目就已经有clip-path的身影了。在CSS的世界中,clip-path是一个很有意思的属性,他可以帮助我们绘制很多不同规则的图形(除了常见的圆形,椭圆形,矩形,三角形等),而且结合CSS的transitionanimation的话,clip-path能帮助我们实现一些很有意思的动画效果。接下来,就和大家聊聊使用clip-path制作Web动效的一些事情。

clip-path简介

CSS Masking Module Level 1主要涵盖了 CSS ClippingCSS 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-pathclipPath都是剪切,不同的是前者是CSS中的剪切,而<clipPath>是SVG中的剪切,但他们是可以相互合作的。正如上面的示例所示,虽然我们使用clip-pathpolygon()能描绘出所需的图形形状态,但计算总是蛮麻烦的。如果上图换到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-pathurl()函数引用才能起作用

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-pathpath()

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-pathtransitionanimation的结合

CSS中transitionanimation都可以给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()最多有五个可以动画化的属性。前四个表示形状的每条边,其行为类似于marginpadding。根据所需的形状,第一个属性是必须的,而后面三个属性是可选的。

@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-pathpath()来构建,很容易就能实现:

用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有更多的经验或独到的见解,欢迎在下面的评论中与我们一起共享。