如何创建变形动效

发布于 大漠

变开动效他有一个专业名词,叫作 Morphing Animation,简单地说从一种图形平滑过渡到另一张图形,也就是把两个(或多个)图形在变化时间上作线性的内插。而这种动画效果在Web上的使用也越来越频繁,比如说,两个状态下图标变形有一个动画效果。既然变形动效越来越受青眯,那么今天就来和大家聊聊如何创建变形动效的效果。

就目前创建变形动效主要有CSS、SVG和JavaScript等方法,在这里我们主要来看CSS和SVG两种方式,因为这两种方式实现成本相对而言要更低一些,难度和复杂度也要更低一些。先来看CSS如何实现

使用CSS创建变形动效

使用CSS创建变形动效主要采用的是CSS的clip-path属性,如果你阅读过《使用clip-path制作Web动效》一文的话,我想你已经知道如何使用clip-path创建变形动效了。这里简单地再向大家阐述一下。

使用clip-path制作变形动效主要依赖于该属性的polygon()函数和path()函数:

  • 使用polygon()函数制作变形动效,有一个必要条件,那就是polygon()中使用的顶点数必须是相同的
  • 使用path()函数制作变形动效,可以将SVG的path路径来做为其值,但目前仅Firefox 71+版本支持

先来看polygon()函数实现的变形动效。比如:

上图中对应的polygon的值是:

clip-path: polygon(50% 48%, 34% 69%, 11% 44%, 0 56%, 36% 93%, 49% 77%, 63% 59%, 76% 44%, 89% 30%, 100% 18%, 86% 5%, 70% 23%);

clip-path: polygon(20% 0%, 0% 20%, 30% 50%, 0% 80%, 20% 100%, 50% 70%, 80% 100%, 100% 80%, 70% 50%, 100% 20%, 80% 0%, 50% 30%);

他们图形效果不同,但polygon()用的顶点数是相同的。基于这两个图形,我们来制作一个简单的变形动效:

.button::before {
    content: "";
    display: block;
    background-color: #fff;
    margin-right: 5px;
    filter: drop-shadow(1px 1px 0 rgba(0, 0, 0, 0.5));
    width: 36px;
    height: 36px;
    clip-path: polygon(
        50% 48%,
        34% 69%,
        11% 44%,
        0 56%,
        36% 93%,
        49% 77%,
        63% 59%,
        76% 44%,
        89% 30%,
        100% 18%,
        86% 5%,
        70% 23%
    );
    transition: clip-path 0.28s linear;
}

.button.active::before {
    clip-path: polygon(
        20% 0%,
        0% 20%,
        30% 50%,
        0% 80%,
        20% 100%,
        50% 70%,
        80% 100%,
        100% 80%,
        70% 50%,
        100% 20%,
        80% 0%,
        50% 30%
    );
    transition: clip-path 0.28s linear;
}

效果如下:

点击上面示例中的按钮,可以看到按钮上的图标会在两个不同形状之间进行平滑的过渡:

使用polygon()虽然能实现部分图形之间的形变,但他还是有自身的局限性的,比如说绘制的图形只能是多边形,如果要像下面这样的矢量图形之间的形变,那他就显得束手无策了:

其实上面展示的形状是SVG的<path>

<path d="M183.40000015375514,25.900000312099994C183.40000015375514,25.900000312099994,222,104.10000000076495,222,104.10000000076495C226.6,113.30000000076495,235.4,119.70000000076494,245.5,121.20000000076494C245.5,121.20000000076494,333.60011850847275,134.00001720360777,333.60011850847275,134.00001720360777C340.1000699036153,134.90001010402185,342.70005051628453,142.99995057672447,338.0000857411531,147.59991644409982C338.0000857411531,147.59991644409982,273.6,210.40000027382592,273.6,210.40000027382592C266.6,217.20000027382594,263.40000000000003,227.00000027382592,265.1,236.6000002738259C265.1,236.6000002738259,280.5945414942853,326.9683214460172,280.5945414942853,326.9683214460172C281.59678338056096,332.78128539708973,275.4831371162741,337.1911302020395,270.27153779163035,334.4849893831104C270.27153779163035,334.4849893831104,186.20000000000002,290.29990280050964,186.20000000000002,290.29990280050964C179.4,286.6999028005096,171.3,286.6999028005096,164.50000000000003,290.29990280050964C164.50000000000003,290.29990280050964,69.24097013032006,340.3326423632227,69.24097013032006,340.3326423632227C68.68029747402416,340.6544955077781,67.87052391928385,340.1429237499265,67.93881951174058,339.416183828001C67.93881951174058,339.416183828001,86.12767787760481,233.20000000000073,86.12767787760481,233.20000000000073C87.42767787760489,225.70000000000024,84.92767787760474,218.09999999999977,79.52767787760439,212.8999999999994C79.52767787760439,212.8999999999994,43.12767787702422,177.39999999943421,43.12767787702422,177.39999999943421C25.92767787726208,160.59999999966567,35.42767787713136,131.40000000006853,59.2276778768035,128.0000000001178C59.2276778768035,128.0000000001178,105.22767787760709,121.29999999999995,105.22767787760709,121.29999999999995C115.4276778776071,119.79999999999995,124.22767787760709,113.39999999999995,128.7276778776071,104.19999999999995C128.7276778776071,104.19999999999995,167.32767772385193,26.000000311334986,167.32767772385193,26.000000311334986C170.79999990897085,19.300000184353184,180.20000009102915,19.300000184353184,183.40000015375514,25.900000312099994C183.40000015375514,25.900000312099994,183.40000015375514,25.900000312099994,183.40000015375514,25.900000312099994" />

<path d="M203.4993632058084,66.69871341581697C203.4993632058084,66.69871341581697,222.0001646134645,104.20033789079555,222.0001646134645,104.20033789079555C226.60006497899914,113.40012562606502,235.39986137813517,119.79998267226689,245.49962745373824,121.2999480168007C245.49962745373824,121.2999480168007,289.6182331273968,127.71482482139365,289.6182331273968,127.71482482139365C314.156080795958,131.2501805559095,323.9508984476141,161.44106182624444,306.17894101155974,178.70980510461553C306.17894101155974,178.70980510461553,273.7092770292975,210.29393279645245,273.7092770292975,210.29393279645245C266.6417797179491,217.15982302895927,263.4096381411165,227.05624775945702,265.1257089295328,236.75106541111316C265.1257089295328,236.75106541111316,266.6999956680667,246,266.6999956680667,246C273.3999956680667,284.9,232.49999566806673,314.6,197.49999566806673,296.2C197.49999566806673,296.2,197.49999566806673,296.2,197.49999566806673,296.2C183.59999566806673,288.9,166.99999566806673,288.9,153.09999566806673,296.2C153.09999566806673,296.2,153.09999566806673,296.2,153.09999566806673,296.2C118.09999566806671,314.59999999999997,77.29999566806671,284.9,83.89999566806671,246C83.89999566806671,246,83.89999566806671,246,83.89999566806671,246C86.59999566806671,230.5,81.39999566806671,214.8,70.1999956680667,203.8C70.1999956680667,203.8,70.1999956680667,203.8,70.1999956680667,203.8C41.899995668066715,176.20000000000002,57.49999566806671,128.20000000000002,96.59999566806671,122.50000000000001C96.59999566806671,122.50000000000001,105.1003638823952,121.2999480168007,105.1003638823952,121.2999480168007C115.30013428993156,119.7999826722669,124.09993502100085,113.40012562606502,128.59982672266895,104.20033789079557C128.59982672266895,104.20033789079557,147.10062813032505,66.69871341581698,147.10062813032505,66.69871341581698C158.90037687819503,43.399237579743385,192.09962312180497,43.399237579743385,203.4993632058084,66.69871341581697C203.4993632058084,66.69871341581697,203.4993632058084,66.69871341581697,203.4993632058084,66.69871341581697" />

其中<path>d的值可以作为clip-pathpath()函数的值。也就是说,我们在不同状态下改变path()的值也能达到形变的效果。比如下面这个示例:

请使用Firefox 71+ 浏览器查看上面的Demo,你看到的效果会像下面这样:

今天我发现path()存在的另一个现象,就是当他的点顶点不同时,变形效果就不会平滑,比如下面这两个图形:

这两个图形只是做了一个简单的切换:

.button::before {
    clip-path: path(
        "M35.0730399,6.59855769 C34.5155325,5.30464127 33.716716,4.14802145 32.7057138,3.15366124 C31.6947115,2.16346154 30.5214497,1.38960799 29.2108913,0.844582101 C27.8587278,0.282914201 26.4275148,0 24.9505362,0 C22.8994083,0 20.8982064,0.561667899 19.1591161,1.62259615 C18.7430658,1.87638683 18.347818,2.15514053 17.9733728,2.45885725 C17.5989275,2.15514053 17.2036797,1.87638683 16.7876294,1.62259615 C15.0485392,0.561667899 13.0473373,0 10.9962093,0 C9.51923077,0 8.08801775,0.282914201 6.73585429,0.844582101 C5.42945636,1.38544749 4.25203402,2.16346154 3.2410318,3.15366124 C2.22586908,4.14802145 1.43121302,5.30464127 0.873705621,6.59855769 C0.29539571,7.94240015 0,9.36945266 0,10.8381102 C0,12.2235577 0.282914201,13.6672522 0.844582101,15.1359098 C1.31471893,16.3632581 1.98872041,17.636372 2.84994453,18.9219675 C4.2145895,20.9564534 6.09097633,23.0783099 8.42085799,25.2292899 C12.2818047,28.794841 16.105307,31.2578587 16.2675666,31.3577108 L17.2536058,31.9901072 C17.6904586,32.2688609 18.2521265,32.2688609 18.6889793,31.9901072 L19.6750185,31.3577108 C19.8372781,31.2536982 23.6566198,28.794841 27.5217271,25.2292899 C29.8516087,23.0783099 31.7279956,20.9564534 33.0926405,18.9219675 C33.9538646,17.636372 34.6320266,16.3632581 35.098003,15.1359098 C35.6596709,13.6672522 35.9426284,12.2235577 35.9426284,10.8381102 C35.9467456,9.36945266 35.6513499,7.94240015 35.0730399,6.59855769 Z M17.9733728,28.6991494 C17.9733728,28.6991494 3.16198225,19.2090422 3.16198225,10.8381102 C3.16198225,6.59855769 6.66928624,3.16198225 10.9962093,3.16198225 C14.037537,3.16198225 16.6752959,4.85946746 17.9733728,7.33912722 C19.2714497,4.85946746 21.9092086,3.16198225 24.9505362,3.16198225 C29.2774593,3.16198225 32.7847633,6.59855769 32.7847633,10.8381102 C32.7847633,19.2090422 17.9733728,28.6991494 17.9733728,28.6991494 Z"
    );
    transition: clip-path 0.28s linear;
}

.button.active::before {
    clip-path: path(
        "M34.7369164,12.0759601 L23.9824925,10.5129895 L19.1749812,0.766660392 C19.0436747,0.499811747 18.8276544,0.283791416 18.5608057,0.15248494 C17.8915663,-0.177899096 17.0783133,0.0974209337 16.7436935,0.766660392 L11.9361822,10.5129895 L1.18175828,12.0759601 C0.885259789,12.118317 0.614175452,12.2580949 0.406626506,12.4698795 C-0.114363705,13.0078125 -0.105892319,13.8634224 0.432040663,14.3886483 L8.21300828,21.9747741 L6.37471762,32.6868411 C6.32388931,32.9791039 6.37048193,33.2840738 6.51025979,33.5466867 C6.8575866,34.2074548 7.67931099,34.4658321 8.34007907,34.1142696 L17.9593373,29.0568524 L27.5785956,34.1142696 C27.8412086,34.2540474 28.1461785,34.3006401 28.4384413,34.2498117 C29.1754518,34.122741 29.6710279,33.4238517 29.5439571,32.6868411 L27.7056664,21.9747741 L35.486634,14.3886483 C35.6984187,14.1810994 35.8381965,13.9100151 35.8805535,13.6135166 C35.9949172,12.8722703 35.4781627,12.1860881 34.7369164,12.0759601 Z M24.4314759,20.9073795 L25.960561,29.8150414 L17.9593373,25.6132342 L9.9581137,29.8192771 L11.4871988,20.9116152 L5.01506024,14.600433 L13.9608434,13.3000753 L17.9593373,5.19719503 L21.9578313,13.3000753 L30.9036145,14.600433 L24.4314759,20.9073795 Z"
    );
    transition: clip-path 0.28s linear;
}

为了验证这个,我把上面的图形简化了,每个形状只留下四个点:

对应的path为:

<path d="M26.8946059,11.0126393 C21.2892118,5.02527866 9,21.5 9,18.5 C9,15.5 19.1556529,0.0806130573 10.0778264,0.0401677435 C1,-6.09443052e-14 -1,35 1.21039013,36.9476513 C3.42078025,38.8953025 32.5,17 26.8946059,11.0126393 Z" />

<path d="M1.5,1 C-1,3 5,40.5 10.5,38 C16,35.5 5.5,14.5 10.5,15.5 C15.5,16.5 25.5,32.5 28,26.5 C30.5,20.5 4,-1 1.5,1 Z" />

来看一个Demo:

你将看到的效果如下:

使用SVG制作形变动效

使用SVG制作形变动效指的是我们在SVG元素上构建形变动效,该效果也被称为 SVG形变动效。在SVG元素上制作形变有两个点非常的重要:

SVG的<path>需要超过2个且具有相同数量的点或顶点,不要删除或添加一个新节点

接下来看怎么在SVG中构建形变动效。

首先在Sketch设计软件中,使用路径工具绘制一个图形,比如像下图这样的形状:

这个图形11个点构成:

导出来的SVG,并且进行优化后,你可以看到像下面这样的一段SVG代码:

<svg xmlns="http://www.w3.org/2000/svg" width="225" height="167" viewBox="0 0 225 167">
    <defs>
        <linearGradient id="path1-a" x1="50%" x2="50%" y1="0%" y2="100%">
        <stop offset="0%" stop-color="#F5515F"/>
        <stop offset="100%" stop-color="#9F041B"/>
        </linearGradient>
    </defs>
    <path fill="url(#path1-a)" fill-rule="evenodd" d="M202.5,5.5 C272.242316,19.3208858 151.76021,14.551942 176.5,50 C201.23979,85.448058 185.029863,56.2999188 156.5,83.5 C126.029863,112.549919 182.620539,155.708198 120,166 C74.6205388,173.458198 117.31825,121.960229 90,110 C67.0682497,99.9602287 39.9536115,107.435387 35,80 C26.9536115,35.4353868 -5.51923978,70.9721609 2,33 C4.48076022,20.4721609 25.6847866,15.0542119 39.5,21 C65.1847866,32.0542119 67.5960408,62.5320136 81,67 C94.4039592,71.4679864 117.396285,57.4613025 115.5,45 C113.603715,32.5386975 64.8930314,35.3322785 74,21 C83.1069686,6.66772153 132.757684,-8.32088583 202.5,5.5 Z"/>
</svg>

我们在上图上可以调整每个点的位置,得到另外一个图形:

注意,在原图上不能新增或删除节点。调整之后(你可以按上面的方式调整成你想要的任何图形)图形效果如下:

导出优化之后,可以得到另一段SVG代码:

<svg xmlns="http://www.w3.org/2000/svg" width="270" height="156" viewBox="0 0 270 156">
    <defs>
        <linearGradient id="path2-a" x1="50%" x2="50%" y1="0%" y2="100%">
        <stop offset="0%" stop-color="#F5515F"/>
        <stop offset="100%" stop-color="#9F041B"/>
        </linearGradient>
    </defs>
    <path fill="url(#path2-a)" fill-rule="evenodd" d="M152,17.5 C221.742316,31.3208858 234.26021,-22.948058 259,12.5 C283.73979,47.948058 262.529863,40.2999188 234,67.5 C203.529863,96.5499188 264.120539,144.708198 201.5,155 C156.120539,162.458198 195.31825,119.460229 168,107.5 C145.06825,97.4602287 65.9536115,128.435387 61,101 C52.9536115,56.4353868 30,118 6,122 C8.48076022,109.472161 -6.5,29 6,8.5 C31.6847866,19.5542119 75,80 99.5,67.5 C124,55 159.396285,57.9613025 157.5,45.5 C155.603715,33.0386975 78.8930314,43.3322785 88,29 C97.1069686,14.6677215 82.2576838,3.67911417 152,17.5 Z"/>
</svg>

我们分别把这两个SVG文件称这path1.svgpath2.svg

接下来,就要使用SVG自带的Animation相关的特性了,比如<animate><animateTransform><animateMotion>等。不过我们这里不会用到过于复杂的东西,这里只会使用<animate>。简单地说,就是在<path>元素里添加一个<animate>元素。而对于<animate>元素来说,重要的属性有:

  • attributeName:变动的属性的属性名
  • from:变动的初始值
  • to:变动的终值
  • dur:变动的持续时间
  • repeatCount:动画重复的次数

如果你想彻底了解SVG中的动效,那么可以阅读:

回到我们的示例中来。首先按照<animate>的要求,我们对第一个SVG文件中的SVG代码稍作改变,即在<path>元素中添加<animate>

<svg xmlns="http://www.w3.org/2000/svg" width="225" height="167" viewBox="0 0 225 167">
    <defs>
        <linearGradient id="path1-a" x1="50%" x2="50%" y1="0%" y2="100%">
        <stop offset="0%" stop-color="#F5515F"/>
        <stop offset="100%" stop-color="#9F041B"/>
        </linearGradient>
    </defs>
    <path fill="url(#path1-a)" fill-rule="evenodd" d="M202.5,5.5 C272.242316,19.3208858 151.76021,14.551942 176.5,50 C201.23979,85.448058 185.029863,56.2999188 156.5,83.5 C126.029863,112.549919 182.620539,155.708198 120,166 C74.6205388,173.458198 117.31825,121.960229 90,110 C67.0682497,99.9602287 39.9536115,107.435387 35,80 C26.9536115,35.4353868 -5.51923978,70.9721609 2,33 C4.48076022,20.4721609 25.6847866,15.0542119 39.5,21 C65.1847866,32.0542119 67.5960408,62.5320136 81,67 C94.4039592,71.4679864 117.396285,57.4613025 115.5,45 C113.603715,32.5386975 64.8930314,35.3322785 74,21 C83.1069686,6.66772153 132.757684,-8.32088583 202.5,5.5 Z">
        <animate />
    </path>
</svg>

这个时候路径并不会动起来,为了让其动起来,给<animate>元素添加一些属性:

<path>
    <animate 
        attributeName="d"
        dur="3s"
        repeatCount="indefinite"
        fill="freeze"
        />
</path>

我们要的效果是从图形一形变到图形二,也就是从path1.svg中的path过渡到path2.svg中的path。我们可以把path1.svg中的<path>d值赋值给<animate>from,同时把path2.svg中的<path>的值赋值给<animate>to

<svg xmlns="http://www.w3.org/2000/svg" width="800" viewBox="0 0 300 300">
    <defs>
        <linearGradient id="path1-a" x1="50%" x2="50%" y1="0%" y2="100%">
        <stop offset="0%" stop-color="#F5515F" />
        <stop offset="100%" stop-color="#9F041B" />
        </linearGradient>
    </defs>
    <path fill="url(#path1-a)" fill-rule="evenodd">
        <animate attributeName="d" dur="3s" repeatCount="indefinite" fill="freeze" from="M202.5,5.5 C272.242316,19.3208858 151.76021,14.551942 176.5,50 C201.23979,85.448058 185.029863,56.2999188 156.5,83.5 C126.029863,112.549919 182.620539,155.708198 120,166 C74.6205388,173.458198 117.31825,121.960229 90,110 C67.0682497,99.9602287 39.9536115,107.435387 35,80 C26.9536115,35.4353868 -5.51923978,70.9721609 2,33 C4.48076022,20.4721609 25.6847866,15.0542119 39.5,21 C65.1847866,32.0542119 67.5960408,62.5320136 81,67 C94.4039592,71.4679864 117.396285,57.4613025 115.5,45 C113.603715,32.5386975 64.8930314,35.3322785 74,21 C83.1069686,6.66772153 132.757684,-8.32088583 202.5,5.5 Z" to="M152,17.5 C221.742316,31.3208858 234.26021,-22.948058 259,12.5 C283.73979,47.948058 262.529863,40.2999188 234,67.5 C203.529863,96.5499188 264.120539,144.708198 201.5,155 C156.120539,162.458198 195.31825,119.460229 168,107.5 C145.06825,97.4602287 65.9536115,128.435387 61,101 C52.9536115,56.4353868 30,118 6,122 C8.48076022,109.472161 -6.5,29 6,8.5 C31.6847866,19.5542119 75,80 99.5,67.5 C124,55 159.396285,57.9613025 157.5,45.5 C155.603715,33.0386975 78.8930314,43.3322785 88,29 C97.1069686,14.6677215 82.2576838,3.67911417 152,17.5 Z" />
    </path>
</svg>

你看到的效果会像下面这样:

如果你想超过两个图形的形变,那么fromto就有一定的局限性,这个时候可以使用<animate>中的values属性,而且用;来分隔。按照上面的方式,我们可以再改变导出图形所需的路径:

<!-- path3.svg -->
<path fill="url(#path3-a)" fill-rule="evenodd" d="M189.5,16 C259.242316,29.8208858 231.26021,-21.948058 256,13.5 C280.73979,48.948058 197.029863,43.2999188 168.5,70.5 C138.029863,99.5499188 275.120539,136.208198 212.5,146.5 C167.120539,153.958198 146.31825,113.960229 119,102 C96.0682497,91.9602287 74.9536115,156.935387 70,129.5 C61.9536115,84.9353868 61,58 37,62 C39.4807602,49.4721609 -9.5,30 3,9.5 C28.6847866,20.5542119 104,84.5 128.5,72 C153,59.5 166.896285,52.4613025 165,40 C163.103715,27.5386975 64.3930314,25.8322785 73.5,11.5 C82.6069686,-2.83227847 119.757684,2.17911417 189.5,16 Z"/>

<!-- path4.svg -->
<path fill="url(#path4-a)" fill-rule="evenodd" d="M218.5,8.5 C288.242316,22.3208858 266,63 249,60 C232,57 228.529863,55.7999188 200,83 C169.529863,112.049919 235.620539,105.708198 173,116 C127.620539,123.458198 136.81825,131.460229 109.5,119.5 C86.5682497,109.460229 77.9536115,165.935387 73,138.5 C64.9536115,93.9353868 -11.5,114.5 6,79.5 C8.48076022,66.9721609 -6.5,39 6,18.5 C31.6847866,29.5542119 26.5,27 51,14.5 C75.5,2 100.396285,20.9613025 98.5,8.5 C96.6037148,-3.96130245 126.5,-1.5 151.5,8.5 C176.5,18.5 148.757684,-5.32088583 218.5,8.5 Z"/>

把上面的示例改造一下:

<path fill="url(#path1-a)" fill-rule="evenodd">
    <animate attributeName="d" dur="3s" repeatCount="indefinite" fill="freeze" 
        values="
            M202.5,5.5 C272.242316,19.3208858 151.76021,14.551942 176.5,50 C201.23979,85.448058 185.029863,56.2999188 156.5,83.5 C126.029863,112.549919 182.620539,155.708198 120,166 C74.6205388,173.458198 117.31825,121.960229 90,110 C67.0682497,99.9602287 39.9536115,107.435387 35,80 C26.9536115,35.4353868 -5.51923978,70.9721609 2,33 C4.48076022,20.4721609 25.6847866,15.0542119 39.5,21 C65.1847866,32.0542119 67.5960408,62.5320136 81,67 C94.4039592,71.4679864 117.396285,57.4613025 115.5,45 C113.603715,32.5386975 64.8930314,35.3322785 74,21 C83.1069686,6.66772153 132.757684,-8.32088583 202.5,5.5 Z; 
                
            M152,17.5 C221.742316,31.3208858 234.26021,-22.948058 259,12.5 C283.73979,47.948058 262.529863,40.2999188 234,67.5 C203.529863,96.5499188 264.120539,144.708198 201.5,155 C156.120539,162.458198 195.31825,119.460229 168,107.5 C145.06825,97.4602287 65.9536115,128.435387 61,101 C52.9536115,56.4353868 30,118 6,122 C8.48076022,109.472161 -6.5,29 6,8.5 C31.6847866,19.5542119 75,80 99.5,67.5 C124,55 159.396285,57.9613025 157.5,45.5 C155.603715,33.0386975 78.8930314,43.3322785 88,29 C97.1069686,14.6677215 82.2576838,3.67911417 152,17.5 Z;
                
            M189.5,16 C259.242316,29.8208858 231.26021,-21.948058 256,13.5 C280.73979,48.948058 197.029863,43.2999188 168.5,70.5 C138.029863,99.5499188 275.120539,136.208198 212.5,146.5 C167.120539,153.958198 146.31825,113.960229 119,102 C96.0682497,91.9602287 74.9536115,156.935387 70,129.5 C61.9536115,84.9353868 61,58 37,62 C39.4807602,49.4721609 -9.5,30 3,9.5 C28.6847866,20.5542119 104,84.5 128.5,72 C153,59.5 166.896285,52.4613025 165,40 C163.103715,27.5386975 64.3930314,25.8322785 73.5,11.5 C82.6069686,-2.83227847 119.757684,2.17911417 189.5,16 Z;
                
            M218.5,8.5 C288.242316,22.3208858 266,63 249,60 C232,57 228.529863,55.7999188 200,83 C169.529863,112.049919 235.620539,105.708198 173,116 C127.620539,123.458198 136.81825,131.460229 109.5,119.5 C86.5682497,109.460229 77.9536115,165.935387 73,138.5 C64.9536115,93.9353868 -11.5,114.5 6,79.5 C8.48076022,66.9721609 -6.5,39 6,18.5 C31.6847866,29.5542119 26.5,27 51,14.5 C75.5,2 100.396285,20.9613025 98.5,8.5 C96.6037148,-3.96130245 126.5,-1.5 151.5,8.5 C176.5,18.5 148.757684,-5.32088583 218.5,8.5 Z
    "  />
</path>

将看到的效果如下:

按照这个思路,再结合 Blobs生成工具 就可以实现Blobs相关的形变动效:

也可以将这个效果运用于实际项目中,比如支付宝的“2020宝贝青年” 项目中的展示金额那个UI效果:

简单地模拟了一下:

CSS改变SVG的path创建形变动效

接下来更有意思的是,CSS可以改变SVG的<path>d的值来实现形变的效果。在@Ana Tudor的《Unfortunately, clip-path: path() is Still a No-Go》教程中向我们阐述和展示了其基本原理:

简单地说,就是在CSS中改变SVG中<path>d的值。那么在CSS中改变<path>d的值主要有两种方法,一种是通过状态的切换(比如:hover:active:focus);一种是在@keyframes中来切换。

假设我们有两个SVG绘制的杯子:

上面两个杯子对应<path>d的值为:

<path id="simple-cup" d="M0.385262674,5.94224634 C0.385262674,6.34224634 -0.28516339,17.7539308 4.32901017,31.1532061 C5.32901017,32.3532061 6.09230991,37.8915223 7.49230991,42.9915223 C10.3923099,53.5915223 9.99336758,58.192 9.99336758,58.192 C9.99336758,58.192 9.99336758,59.1351801 9.99336758,62.192 L9.99336758,63.3762725 L14.9773381,65.692 C20.0773381,67.792 34,67.592 38.6,65.692 L44.0054662,63.992 L44.0054662,62.192 C44.0054662,60.3070603 44.0054662,61.2485521 44.0054662,59.5616903 C44.0054662,59.6227531 44.0054662,58.192 44.0054662,58.192 C44.0054662,58.192 44.0054662,56.0357788 44.0054662,56.0368563 C44.0054662,56.1443976 44.1584889,52.1913962 44.1513903,52.2148862 C44.18043,52.2064969 44.4505642,49.3217527 44.4611476,49.3039416 C44.0054662,46.9651447 46.7678097,39.091534 47.5678097,38.791534 C48.3678097,38.591534 52.8,37.6017328 55.4,37.0017328 C62.3288017,36.7364795 68.4691682,30.4573789 68.4691682,26.851942 C68.4691682,25.0527911 68.4691682,20.2992257 66.1171925,17.7539308 C63.4665746,14.8148385 60.3572838,14.0704885 56.6937477,14.2033611 C54.2179046,14.2931572 54.2497766,12.7733674 54.0497766,12.5733674 C53.8497766,12.4733674 54.0497766,12.5733674 54.0497766,10.8407045 L53.3480457,6.66187369 L51.3120843,4.64051206 C49.8120843,3.64051206 47.4776665,2.98869271 44.6776665,2.18869271 C38.2776665,0.488692712 20.1046423,0.0299756608 12.7046423,1.42997566 C7.00464234,2.42997566 0.385262674,4.74224634 0.385262674,5.94224634 Z M64.7915417,22.8695184 C64.7915417,25.8695184 65.5001114,27.9159065 60.6340924,31.7563748 C59.5900982,32.5803392 64.003575,30.2804794 62.903575,30.7804794 C60.903575,31.8804794 53.6,32.676872 52.3920534,31.7563748 C51.5537443,31.1057778 51.0837569,29.8430422 50.9820915,27.9681678 C51.2625331,24.5690682 52.1351692,22.0569251 53.6,20.4317385 C57.4,16.0317385 64.7915417,19.1695184 64.7915417,22.8695184 Z" fill-rule="nonzero" />

<path id="fancy-cup" d="M0,6.792 C0,7.192 0.9,8.492 1.9,9.592 C2.9,10.792 4.9,15.892 6.3,20.992 C9.2,31.592 12.4,36.492 17.5,38.192 C21.6,39.592 21.7,39.792 18.8,42.192 L16.5,43.992 L19.5,45.292 C24.6,47.392 37,47.592 41.6,45.692 L45.5,44.092 L43.1,42.092 C41,40.392 40.9,39.892 42.1,39.092 C42.8,38.592 44.2,38.192 45.1,38.192 C47,38.192 52.4,33.492 53.2,31.192 C53.6,29.892 53.9,29.892 55.3,30.992 C57.5,32.892 59.1,31.592 58.4,28.592 C58,26.992 58.3,26.092 59.1,25.792 C59.9,25.592 62.6,24.792 65.2,24.192 C70.7,22.692 75,18.992 75,15.492 C75,13.692 74.1,12.692 71.3,11.592 C68,10.192 67.3,10.192 63.7,11.592 C61.4,12.492 59.5,12.992 59.3,12.792 C59.1,12.692 59.9,11.292 61,9.892 L63.1,7.292 L60.5,5.592 C59,4.592 55.5,3.192 52.7,2.392 C46.3,0.692 21.5,-0.108 14.1,1.292 C8.4,2.292 0,5.592 0,6.792 Z M70,15.592 C70,18.592 65.7,22.192 62.3,22.192 C61,22.192 59,22.692 57.9,23.192 C55.9,24.292 55.5,23.592 56.6,20.792 C56.9,19.892 57.6,19.492 58.1,19.792 C58.6,20.092 60.5,18.692 62.2,16.792 C66,12.392 70,11.892 70,15.592 Z" fill-rule="nonzero" />

刚才提到了,在CSS中可以使用状态伪类选择器中改变d的值,也可以在@keyframes中改变d的值,都可以达到形变的效果。那么我们先来看在状态伪类选择器中改变d的值。

假设在HTML中,我们有一个<svg>元素,并在<path>d属性有一段路径的值(就称杯子一吧):

<svg id="cups" width="100" height="130" viewBox="0 0 75 47" xmlns="http://www.w3.org/2000/svg">
    <defs>
        <linearGradient id="PSgrad_0" x1="50%" x2="50%" y1="0%" y2="100%">
        <stop offset="0%" stop-color="#F5515F" />
        <stop offset="100%" stop-color="#9F041B" />
        </linearGradient>
    </defs>
    <path id="simple-cup" d="M0.385262674,5.94224634 C0.385262674,6.34224634 -0.28516339,17.7539308 4.32901017,31.1532061 C5.32901017,32.3532061 6.09230991,37.8915223 7.49230991,42.9915223 C10.3923099,53.5915223 9.99336758,58.192 9.99336758,58.192 C9.99336758,58.192 9.99336758,59.1351801 9.99336758,62.192 L9.99336758,63.3762725 L14.9773381,65.692 C20.0773381,67.792 34,67.592 38.6,65.692 L44.0054662,63.992 L44.0054662,62.192 C44.0054662,60.3070603 44.0054662,61.2485521 44.0054662,59.5616903 C44.0054662,59.6227531 44.0054662,58.192 44.0054662,58.192 C44.0054662,58.192 44.0054662,56.0357788 44.0054662,56.0368563 C44.0054662,56.1443976 44.1584889,52.1913962 44.1513903,52.2148862 C44.18043,52.2064969 44.4505642,49.3217527 44.4611476,49.3039416 C44.0054662,46.9651447 46.7678097,39.091534 47.5678097,38.791534 C48.3678097,38.591534 52.8,37.6017328 55.4,37.0017328 C62.3288017,36.7364795 68.4691682,30.4573789 68.4691682,26.851942 C68.4691682,25.0527911 68.4691682,20.2992257 66.1171925,17.7539308 C63.4665746,14.8148385 60.3572838,14.0704885 56.6937477,14.2033611 C54.2179046,14.2931572 54.2497766,12.7733674 54.0497766,12.5733674 C53.8497766,12.4733674 54.0497766,12.5733674 54.0497766,10.8407045 L53.3480457,6.66187369 L51.3120843,4.64051206 C49.8120843,3.64051206 47.4776665,2.98869271 44.6776665,2.18869271 C38.2776665,0.488692712 20.1046423,0.0299756608 12.7046423,1.42997566 C7.00464234,2.42997566 0.385262674,4.74224634 0.385262674,5.94224634 Z M64.7915417,22.8695184 C64.7915417,25.8695184 65.5001114,27.9159065 60.6340924,31.7563748 C59.5900982,32.5803392 64.003575,30.2804794 62.903575,30.7804794 C60.903575,31.8804794 53.6,32.676872 52.3920534,31.7563748 C51.5537443,31.1057778 51.0837569,29.8430422 50.9820915,27.9681678 C51.2625331,24.5690682 52.1351692,22.0569251 53.6,20.4317385 C57.4,16.0317385 64.7915417,19.1695184 64.7915417,22.8695184 Z" fill-rule="nonzero" fill="url(#PSgrad_0)" />
</svg>

示例在svg:hover状态将<path>d值做改变(杯子二),同时在transition中设置d值改变时相关的动效参数:

svg path {
    transition: d 0.3s linear;
}
svg:hover path {
    d: path(
        "M0,6.792 C0,7.192 0.9,8.492 1.9,9.592 C2.9,10.792 4.9,15.892 6.3,20.992 C9.2,31.592 12.4,36.492 17.5,38.192 C21.6,39.592 21.7,39.792 18.8,42.192 L16.5,43.992 L19.5,45.292 C24.6,47.392 37,47.592 41.6,45.692 L45.5,44.092 L43.1,42.092 C41,40.392 40.9,39.892 42.1,39.092 C42.8,38.592 44.2,38.192 45.1,38.192 C47,38.192 52.4,33.492 53.2,31.192 C53.6,29.892 53.9,29.892 55.3,30.992 C57.5,32.892 59.1,31.592 58.4,28.592 C58,26.992 58.3,26.092 59.1,25.792 C59.9,25.592 62.6,24.792 65.2,24.192 C70.7,22.692 75,18.992 75,15.492 C75,13.692 74.1,12.692 71.3,11.592 C68,10.192 67.3,10.192 63.7,11.592 C61.4,12.492 59.5,12.992 59.3,12.792 C59.1,12.692 59.9,11.292 61,9.892 L63.1,7.292 L60.5,5.592 C59,4.592 55.5,3.192 52.7,2.392 C46.3,0.692 21.5,-0.108 14.1,1.292 C8.4,2.292 0,5.592 0,6.792 Z M70,15.592 C70,18.592 65.7,22.192 62.3,22.192 C61,22.192 59,22.692 57.9,23.192 C55.9,24.292 55.5,23.592 56.6,20.792 C56.9,19.892 57.6,19.492 58.1,19.792 C58.6,20.092 60.5,18.692 62.2,16.792 C66,12.392 70,11.892 70,15.592 Z"
    );
}

你将看到的效果如下:

在上例中你把鼠标悬浮到杯子图形上,可以看到下图这样的形变效果:

在CSS中改变<path>d值时,完全可以不给<svg>中的<path>d属性设置任何值,但需要在CSS中初始状态中给d设置值,像下面这样:

<!-- HTML -->
<svg  xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 288 288">
    <linearGradient id="PSgrad_0" x1="70.711%" x2="0%" y1="70.711%" y2="0%">
        <stop offset="0%" stop-color="rgb(95,54,152)" stop-opacity="1" />
        <stop offset="100%" stop-color="rgb(247,109,138)" stop-opacity="1" />
    </linearGradient>
    <path fill="url(#PSgrad_0)"/>
</svg>

/* CSS */
svg path{
    d:path('M37.5,186c-12.1-10.5-11.8-32.3-7.2-46.7c4.8-15,13.1-17.8,30.1-36.7C91,68.8,83.5,56.7,103.4,45	c22.2-13.1,51.1-9.5,69.6-1.6c18.1,7.8,15.7,15.3,43.3,33.2c28.8,18.8,37.2,14.3,46.7,27.9c15.6,22.3,6.4,53.3,4.4,60.2	c-3.3,11.2-7.1,23.9-18.5,32c-16.3,11.5-29.5,0.7-48.6,11c-16.2,8.7-12.6,19.7-28.2,33.2c-22.7,19.7-63.8,25.7-79.9,9.7	c-15.2-15.1,0.3-41.7-16.6-54.9C63,186,49.7,196.7,37.5,186z');
animation: morph 5s infinite;
}

svg:hover path{
    d:path:hover('M51,171.3c-6.1-17.7-15.3-17.2-20.7-32c-8-21.9,0.7-54.6,20.7-67.1c19.5-12.3,32.8,5.5,67.7-3.4C145.2,62,145,49.9,173,43.4 c12-2.8,41.4-9.6,60.2,6.6c19,16.4,16.7,47.5,16,57.7c-1.7,22.8-10.3,25.5-9.4,46.4c1,22.5,11.2,25.8,9.1,42.6	c-2.2,17.6-16.3,37.5-33.5,40.8c-22,4.1-29.4-22.4-54.9-22.6c-31-0.2-40.8,39-68.3,35.7c-17.3-2-32.2-19.8-37.3-34.8	C48.9,198.6,57.8,191,51,171.3z');
}

效果如下:

另外,我们也可以在@keyframes中不同的帧改变d的值。比如前面示例中SVG的<animate>中的values中的值换到不同的帧中也可以实现形变动效。比如下面这个示例:

#blobSvg {
    width: 30%;
    height: auto;
}

#blobSvg path {
    animation: bolb 5s cubic-bezier(1, 0.02, 0, 0.96) infinite alternate;
}

@keyframes bolb {
    0% {
        d: path(
        "M413.5,342Q356,434,260.5,416Q165,398,115,324Q65,250,117.5,180.5Q170,111,257,99.5Q344,88,407.5,169Q471,250,413.5,342Z"
        );
    }

    20% {
        d: path(
        "M413.5,342Q356,434,260.5,416Q165,398,115,324Q65,250,117.5,180.5Q170,111,257,99.5Q344,88,407.5,169Q471,250,413.5,342Z"
        );
    }

    40% {
        d: path(
        "M391.5,334.5Q348,419,241,435.5Q134,452,110.5,351Q87,250,113,154Q139,58,237.5,80Q336,102,385.5,176Q435,250,391.5,334.5Z"
        );
    }

    60% {
        d: path(
        "M379,329Q341,408,257,395.5Q173,383,114.5,316.5Q56,250,100,158Q144,66,255,57.5Q366,49,391.5,149.5Q417,250,379,329Z"
        );
    }

    80% {
        d: path(
        "M421,343Q357,436,257,423.5Q157,411,94,330.5Q31,250,89,160.5Q147,71,253,66Q359,61,422,155.5Q485,250,421,343Z"
        );
    }

    100% {
        d: path(
        "M372.5,330Q342,410,240.5,426.5Q139,443,110.5,346.5Q82,250,110.5,154Q139,58,254,51Q369,44,386,147Q403,250,372.5,330Z"
        );
    }
}

你将看到的效果如下:

制作形变动效的工具

在Web上可以用多种不同的方式来实现同一效果,对于形变动效也是如此。在Web上除了上述提到的CSS,SVG等方法之外,还可以使用一些优势的库来实现。正如@Chris Coyier 的《The Many Tools for Shape Morphing》文章中整理的:

不过我更喜欢的是SVG MorphSVGPlugin

MorphSVGPlugin 是Greensock GSAP 中的一款插件。Greensock GSAP是一个非常优秀的制作动效的库,如果你从未接触过GSAP的话,可以点击这里进行了解

简单的了解MorphSVGPlugin如何制作形变动效

简单地说,MorphSVGPlugin制作形变动效也是动态改变<path>d的值,但使用MorphSVGPlugin制作SVG图形形变动效会变得更为容易。MorphSVGPlugin插件主要可以做:

  • 即使SVG的<path>的开始形状和结束形状之间的顶点数完全不同,也可以实现形变 (前面的内容中多次提到过,如果<path>的顶点数不同,无法实现平滑有形变动效效果)
  • <polyline><polygon>形变设置不同的顶点数
  • 有一个实用函数MorphSVGPlugin.convertToPath(),它可以将原始形状(比如<circle><rect><ellipse><polygon><polyline><line>)直接转换为等效的<path>,它看起来与原来完全相同,并且可以直接插入到DOM中
  • 通过渲染函数或设置一个MorphSVGPlugin.defaultRender将结果形状以<canvas>形式渲染
  • 使用线性插值(默认)来控制点如何被映射,这将影响在动画期间中间状态的样子
  • 无需将原始路径数据作为文本传递,只需输入选择器文本或元素,插件就会从中获取所需的数据,使工作流更容易

接下来来看MorphSVGPlugin如何实现一个形变动效。

在《初探GSAP》一文中介绍如何使用GSAP以及如何在CodePen上使用GSAP制作Demo。因为我们接下来的示例是要使用MorphSVGPlugin插件制作形变效果,所以在选择对应的.js时,除了要选择GSAP的核心库之外,还需要在插件列中选择“MorphSVG

将对应的.js文件放置到Codepen中JS设置项目中:

比如我们要从一个“”(<path>)变成一个“河马”(<path>

<!-- 圆的形状 -->
<path id="circle" class="st1" d="M490.1,280.649c0,44.459-36.041,80.5-80.5,80.5s-80.5-36.041-80.5-80.5s36.041-80.5,80.5-80.5 S490.1,236.19,490.1,280.649z" fill="url(#circle__fill)" />

<!-- 河马的形状 -->
<path id="hippo" class="st1" fill="url(#hippo__fill)" d="M148.802,244.876c2.737-36.735,16.107-69.079,40.099-97.061 c27.038-31.596,60.924-47.386,101.629-47.386c15.481,0,38.483,2.447,69.024,7.287c30.541,4.886,53.533,7.278,69.033,7.278 c23.693,0,57.868,8.847,102.526,26.477c7.914,3.009,17.471,11.239,28.701,24.59c6.381,7.886,16.256,19.769,29.616,35.568 c3.036,2.139,6.998,5.316,11.865,9.595c4.859,4.223,8.194,6.063,9.997,5.456c0.616-1.84,2.149-4.4,4.578-7.735 c1.214-1.225,1.962-1.832,2.261-1.832c0.935,0.607,1.831,1.215,2.747,1.832c0.906,0.616,1.205,2.419,0.906,5.456 c-0.616,5.474-0.906,7.138-0.906,4.998c-0.327,3.056-0.757,5.008-1.373,5.952c-3.952,6.671-5.485,11.847-4.55,15.472 c0.916,3.325,3.765,8.669,8.642,15.958c4.868,7.287,7.586,12.761,8.193,16.405c-0.299,2.728-0.43,7.119-0.43,13.211l-4.568,11.379 c0,8.512,9.865,23.114,29.616,43.78c9.436,4.223,14.117,18.826,14.117,43.714c0,19.47-16.089,29.167-48.273,29.167 c-4.26,0-8.81-0.13-13.678-0.467c-3.335-1.196-8.203-2.56-14.575-4.074c-7.586-0.934-12.761-3.494-15.48-7.773
c-4.877-6.95-12.781-13.509-23.711-19.581c-1.823-0.878-4.485-4.223-7.979-10.016c-3.503-5.774-6.615-9.418-9.333-10.949 c-2.719-1.495-6.68-1.813-11.856-0.878c-8.81,1.494-13.677,2.261-14.574,2.261c-2.139,0-5.25-0.598-9.343-1.831
c-4.11-1.215-7.054-1.831-8.893-1.831c-2.112,9.735-2.589,19.152-1.364,28.252c0.298,2.448,1.831,4.428,4.559,5.923
c4.27,3.046,6.531,4.709,6.849,5.045c2.718,2.111,5.615,5.605,8.642,10.445c0.616,1.849-0.523,4.952-3.419,9.342
c-2.887,4.41-5.223,7.008-7.063,7.736c-1.813,0.785-5.765,1.178-11.847,1.178c-8.82,0-12.295,0.131-10.464,0.43
c-12.145-1.831-18.984-2.878-20.516-3.158c-7.587-1.532-14.126-3.943-19.582-7.305c-2.756-1.813-5.913-10.333-9.557-25.524
c-3.681-16.406-6.717-26.272-9.137-29.635c-0.598-0.896-1.355-1.326-2.261-1.326c-1.533,0-4.045,1.494-7.53,4.559
c-3.494,2.99-5.858,4.652-7.054,5.008c-4.242,17.9-6.4,26.402-6.4,25.468c0,7.007,1.972,12.892,5.924,17.77
c3.943,4.858,8.063,9.567,12.323,14.107c5.157,5.774,7.736,10.782,7.736,15.042c0,2.41-0.748,4.521-2.28,6.372
c-6.381,7.885-17.022,11.847-31.905,11.847c-16.713,0-27.644-2.28-32.792-6.839c-6.699-5.774-10.949-11.865-12.762-18.199
c-0.298-1.533-1.055-6.091-2.28-13.678c-0.607-4.578-1.98-7.287-4.082-8.184c-6.101-0.916-13.687-2.578-22.778-5.007
c-1.841-1.215-3.811-4.26-5.942-9.118c-3.933-9.399-6.83-15.789-8.661-19.134c-9.128-4.56-23.702-9.698-43.761-15.453
c-0.916,1.831-1.345,4.373-1.345,7.718c3.335,4.26,8.343,10.8,15.032,19.581c5.466,7.288,8.203,14.295,8.203,20.965
c0,12.781-8.203,19.134-24.609,19.134c-12.453,0-20.955-0.878-25.523-2.709c-6.671-2.728-12.295-9.136-16.854-19.134
c-7.596-16.742-11.847-26.159-12.762-28.27c-4.868-11.231-8.204-21.133-10.006-29.653c-1.233-6.055-3.064-15.35-5.485-27.804
c-2.121-10.296-5.456-18.358-10.015-24.132C155.332,279.36,147.578,260.665,148.802,244.876z" />

上面两个<path>的顶点数是完全不同的,按照CSS和SVG(不采用任何插件或库)是无法实现平滑有形变动效的,但是MorphSVGPlugin插件没有这样的束缚。而且它的使用也非常的简单:

var tl = gsap.timeline({
    defaults: {
        duration: 1
    }
});

var circle = document.getElementById("circle");

tl.to("#circle", {
    duration: 1,
    morphSVG: "#hippo",
    fill: "url(#hippo__fill)"
});

效果如下:

尝试着拖动示例中的进度条,你将看到下面这样的效果:

是不是很有意思!

使用MorphSVGPlugin可以轻易地将任意图形做形变,比如下面这个示例:

var tl = gsap.timeline({
    defaults: {
        duration: 1
    }
});

var hippo = document.getElementById("hippo");

tl.to(hippo, {morphSVG: "#elephant", duration: 1}, "+=1")
    .to(hippo, {morphSVG: "#cock", duration: 1}, "+=1")
    .to(hippo, {morphSVG: "#rabbit", duration: 1}, "+=1")
    .to(hippo, {morphSVG: hippo, duration: 1}, "+=1");

效果如下:

如果你对MorphSVGPlugin制作的示例感兴趣的话,可以在Codepen上查阅,上面有很多优秀的案例

MorphSVGPlugin 更详细的介绍可以查阅官网文档

小结

这篇文章主要和大家一起探讨了在Web中如何实现一些形变的动画效果。虽然CSS的clip-path可能帮助我们实现形变的效果,但其局限性较大。随着SVG在Web中运用越来越广泛,因此Web上看到的形变效果大多数都是SVG的形变效果,而且可以将CSS的transitionanimation结合起来改变SVG的<path>d属性值,从而实现形变动画效果。

不管是CSS还是SVG,他们都有一个共性,对于形变图形的顶点数有要求,那就是他们顶点数必须相同,而且不能新增或删除。不过,现在有很多优秀的库可以帮助我们更轻易的实现更好的形变效果,比如说GSAP的MorphSVGPlugin插件就是业内非常有口碑的一款制作形变动效的工具。当然,如果你是基于React库开发项目的话,还可以使用像react-spring库来制作形变动效的效果。

如果你对形变动效的实现有更好的建议或经验,欢迎在下面的评论中与我们一起共享。