SVG 圆形变形

SVG circle shape morphing

如何制作带有形状变形的圆形 SVG?

我正在尝试用有机运动做圆圈。

但是如您所见,像素似乎有问题。

关于如何让它更好看有什么想法吗?这甚至是我使用的正确方法吗?我不是 svg 专家,也不是形状变形专家。

class App extends React.Component {
  render() {
    return (
      <div>
       <svg width="200px" height="200px" viewBox="0 0 120 120">
    <defs>
        <filter id="distort">
            <feTurbulence baseFrequency=".02" type="fractalNoise" />
            <feColorMatrix type="hueRotate" values="0">
                <animate attributeName="values" from="0" to="360" dur="1s" repeatCount="indefinite" />
            </feColorMatrix>
            <feDisplacementMap in="SourceGraphic" xChannelSelector="R" yChannelSelector="B" scale="20">
                <animate attributeName="scale" values={Math.round(Math.random() * 20) + ';' + Math.round(Math.random() * 10) + ';' + Math.round(Math.random() * 10) + ';' + Math.round(Math.random() * 10) + ';'}  dur="5s" repeatCount="indefinite" />
            </feDisplacementMap>
        </filter>
    </defs>
    <circle filter="url(#distort)" cx="60" cy="60" r="30" />
</svg>
      </div>
    );
  }
}

// Render it
ReactDOM.render(
  <App />,
  document.getElementById("app")
);
svg {
    stroke-width: 1;
    stroke: #293133;
    stroke-linecap: round;
    fill: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Any ideas on how to make it better looking ?

可以通过更改补丁的 d 属性来实现圆形边界的变形

逐步实现变形:

  • 正在将圆加载到矢量编辑器中
  • 添加额外的锚点。以 SVG 格式保存文件。这会 成为变形的起始路径

  • 移动节点。我们保存文件。这将是最终路径 用于变形。

  • 我们写了一个动画命令来改变d属性。价值 此属性更改为

values="path Start; path Finish; path Start"

注意:StartFinish路径之间用分号隔开

完整代码如下。点击后动画开始

<svg id="svg1" width="200px" height="200px" viewBox="0 0 120 120"> 

<path
     style="fill:none;stroke:#000000;stroke-width:2"
     d="M 90,60 C 90,68.642693 86.345292,76.431784 80.497023,81.906123 75.133823,86.926409 67.925849,90 60,90 51.779957,90 44.332072,86.694001 38.913442,81.339102 33.410134,75.90052 30,68.3485 30,60 30,52.173162 32.99728,45.046376 37.906648,39.704832 43.389864,33.738925 51.258296,30 60,30 68.057492,30 75.373063,33.17654 80.762728,38.345633 86.455778,43.805696 90,51.488949 90,60 Z">
       <animate
        attributeName="d"
        begin="svg1.click"
        dur="1.5s"
        fill="freeze"
        repeatCount="indefinite"
        restart="whenNotActive"
        values="
            M 90,60 C 90,68.642693 86.345292,76.431784 80.497023,81.906123 75.133823,86.926409 67.925849,90 60,90 51.779957,90 44.332072,86.694001 38.913442,81.339102 33.410134,75.90052 30,68.3485 30,60 30,52.173162 32.99728,45.046376 37.906648,39.704832 43.389864,33.738925 51.258296,30 60,30 68.057492,30 75.373063,33.17654 80.762728,38.345633 86.455778,43.805696 90,51.488949 90,60 Z;
            
            m 92.542373,60 c 0,8.642693 -9.247929,11.982631 -15.096197,17.45697 -5.363201,5.020286 -9.647445,15.212522 -17.573295,15.212522 -8.220042,0 -9.947589,-10.678881 -15.366218,-16.03378 -5.503309,-5.438582 -17.176155,-8.541449 -17.176155,-16.889949 0,-7.826839 9.480331,-11.267184 14.389699,-16.608727 5.483217,-5.965908 10.046563,-16.187883 18.788268,-16.187883 8.057491,0 12.195097,9.786709 17.584761,14.955802 C 83.786287,47.365018 92.542373,51.488949 92.542373,60 Z;
            
            M 90,60 C 90,68.642693 86.345292,76.431784 80.497023,81.906123 75.133823,86.926409 67.925849,90 60,90 51.779957,90 44.332072,86.694001 38.913442,81.339102 33.410134,75.90052 30,68.3485 30,60 30,52.173162 32.99728,45.046376 37.906648,39.704832 43.389864,33.738925 51.258296,30 60,30 68.057492,30 75.373063,33.17654 80.762728,38.345633 86.455778,43.805696 90,51.488949 90,60 Z" />
                          
  </path> 

</svg>

另一个从圆形变形为三角形,然后变形为矩形再变回圆形的示例

所有形状的路径都在矢量编辑器中绘制。 要想动画流畅,不卡顿,创建路径时必须满足两个条件:

  • 所有形状的锚点数量必须相同
  • 从头开始位置相同的锚点类型, 所有形状必须相同

<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink" width="600" height="600" viewBox="0 0 400 400" >
     <rect width="100%" height="100%" fill="#2772C7" />
 <path  
     style="stroke:white; fill:none;stroke-width:18; stroke-linecap:round; stroke-dasharray:0,15"
     d="m 350,200 c 0,27.23032 -7.25588,52.76593 -19.93968,74.77889 C 304.15623,319.7359 255.6124,350 200,350 144.67416,350 96.344223,320.04698 70.34191,275.47268 57.409394,253.30319 50,227.51687 50,200 50,117.15729 117.15729,50 200,50 c 82.84271,0 150,67.15729 150,150 z">
            <!-- Animation of morphing circle into a triangle and a rectangle -->
      <animate
        attributeName="d"
        begin="svg1.click"
        dur="3s"
        fill="freeze"
        repeatCount="3"
        restart="whenNotActive"
        values="
            m 350,200 c 0,27.23032 -7.25588,52.76593 -19.93968,74.77889 C 304.15623,319.7359 255.6124,350 200,350 144.67416,350 96.344223,320.04698 70.34191,275.47268 57.409394,253.30319 50,227.51687 50,200 50,117.15729 117.15729,50 200,50 c 82.84271,0 150,67.15729 150,150 z;
            
            m 285,200 c 15,25 55,90 65,110 -45,0 -94.3876,0 -150,0 -55.32584,0 -85,0 -150,-0.0962 C 64.378221,285 99.019238,225 113.45299,200 153.86751,130 170,100 200,50 c 45,79.01924 48.45299,85 85,150 z;
            
            m 285,200 c 15,25 55,90 65,110 -45,0 -94.3876,0 -150,0 -55.32584,0 -85,0 -150,-0.0962 C 64.378221,285 99.019238,225 113.45299,200 153.86751,130 170,100 200,50 c 45,79.01924 48.45299,85 85,150 z;
            
            m 350,50 c 0,40 0,240 0,260 -45,0 -94.3876,0 -150,0 -55.32584,0 -85,0 -150,-0.0962 C 50,285 50,85 50,50 c 70,0 105,0 150,0 80,0 100,0 150,0 z;
            
            m 350,50 c 0,40 0,240 0,260 -45,0 -94.3876,0 -150,0 -55.32584,0 -85,0 -150,-0.0962 C 50,285 50,85 50,50 c 70,0 105,0 150,0 80,0 100,0 150,0 z;
            
            m 350,200 c 0,27.23032 -7.25588,52.76593 -19.93968,74.77889 C 304.15623,319.7359 255.6124,350 200,350 144.67416,350 96.344223,320.04698 70.34191,275.47268 57.409394,253.30319 50,227.51687 50,200 50,117.15729 117.15729,50 200,50 c 82.84271,0 150,67.15729 150,150 z;
            
            m 350,200 c 0,27.23032 -7.25588,52.76593 -19.93968,74.77889 C 304.15623,319.7359 255.6124,350 200,350 144.67416,350 96.344223,320.04698 70.34191,275.47268 57.409394,253.30319 50,227.51687 50,200 50,117.15729 117.15729,50 200,50 c 82.84271,0 150,67.15729 150,150z"    
             />
  </path>
</svg>    

我认为出现毛刺效应是因为 feTurbulence 滤镜在 R、G 和 B 通道中输出 8 位整数值。这种量化使得置换贴图的平滑变化变得困难。

无论如何,使用动画矢量曲线会获得更好的结果。下面是一个为具有 50 个顶点的多边形设置动画的示例。您可以通过使用二次或三次贝塞尔曲线而不是直线段来减少点数,但这至少应该让您开始:

window.onload = function() {
    var radius = 60, npoints = 50, nwaves = 8;
    var min_amp = 0.5, max_amp = 2.0;       // Spatial amplitude
    var min_freq = 0.1, max_freq = 0.2;     // Spatial frequency
    var min_speed = 0.05, max_speed = 0.1;  // Temporal speed
    var time_step = 0.2;
    
    // Create path data for initial circle
    var base_coords = [];
    for (var i=0; i<=npoints; i++) {
        var x = Math.sin(i * 2 * Math.PI / npoints) * radius;
        var y = Math.cos(i * 2 * Math.PI / npoints) * radius;
        base_coords.push([x,y]);
    }
    
    // Create wave data for distortion
    var wave_data = [];
    for (var i=0; i<nwaves; i++) {
        var amp = Math.random() * (max_amp - min_amp) + min_amp;
        var freq = Math.random() * (max_freq - min_freq) + min_freq;
        var speed = Math.random() * (max_speed - min_speed) + min_speed;
        var angle = Math.random() * 2 * Math.PI;
        wave_data.push([amp,freq,speed,angle]);
    }
    
    var ticks = 0;
    var update_blob = function() {
        ticks++;
        var blob_coords = [];
        for (var i=0; i<base_coords.length; i++) {
            // Fetch base coordinate
            var x = base_coords[i][0];
            var y = base_coords[i][1];
            // Distort using wave data
            for (var j=0; j<wave_data.length; j++) {
                // Rotate x & y to wave orientation
                var s = Math.sin(wave_data[j][3]);
                var c = Math.cos(wave_data[j][3]);
                var tx = x * c + y * s;
                var ty = x * -s + y * c;
                // Shift along x axis using wave parameters and x value
                tx += Math.sin(tx * wave_data[j][1] + ticks * wave_data[j][2]) * wave_data[j][0];
                // Rotate back to original orientation
                x = tx * c + ty * -s;
                y = tx * s + ty * c;
            }
            blob_coords.push([x,y]);
        }
        var d = "M";
        for (var i=0; i<blob_coords.length; i++) {
            d += " " + blob_coords[i][0].toFixed(2) + " " + blob_coords[i][1].toFixed(2);
        }
        d += "Z";
        // console.log(d);
        document.getElementById("blob").setAttribute("d", d);
        // if (ticks == 1) alert(d);
    }
    setInterval(update_blob, 20);
}
<svg width="150" height="150" viewBox="-75 -75 150 150">
<path id="blob" d="M0 0 0 0Z" stroke="#000" stroke-width="2" fill="none" />
</svg>

好吧,您可以尝试使用额外的过滤器基元来尝试使事情顺利进行 - 但这并不完美。这是一次勇敢的尝试。

额外的模糊+粘稠的颜色矩阵将使锯齿状的边缘变得平滑,但一些位移非常锯齿,导致部分圆圈消失。所以我增加了原始圆的笔划宽度,然后使用 feMorphology/erode 再次将其变细 - 如您所见,这并不完美。 (由于粘糊糊的 feColorMatrix 的工作方式,我还不得不去掉原来的白色填充。)

class App extends React.Component {
  render() {
    return (
      <div>
       <svg width="200px" height="200px" viewBox="0 0 120 120">
    <defs>
        <filter id="distort" width="130%" height="130%">
            <feTurbulence baseFrequency=".02" type="fractalNoise" />
            <feColorMatrix type="hueRotate" values="0">
                <animate attributeName="values" from="0" to="360" dur="1s" repeatCount="indefinite" />
            </feColorMatrix>
            <feDisplacementMap in="SourceGraphic" xChannelSelector="R" yChannelSelector="B" scale="15"/>
<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="1 0 0 0 0   0 1 0 0 0   0 0 1 0 0   0 0 0 10 -1"/>
<feMorphology operator="erode" radius="1"/>
        </filter>
    </defs>
    <circle filter="url(#distort)" cx="60" cy="60" r="30" />
</svg>
      </div>
    );
  }
}

// Render it
ReactDOM.render(
  <App />,
  document.getElementById("app")
);
svg {
    stroke-width: 3;
    stroke: #293133;
    stroke-linecap: round;
    fill: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>