Animating SVG fill linearly along length of path

2019-05-18 00:31发布

问题:

I have an SVG file that represents a thin shape. I would like to craft an animation such that the entire shape appears be being drawn out.

Example of what I am talking about with the 'S' logo from the Samsung Galaxy S brand: https://codepen.io/anon/pen/MGawzy

Animation code (because StackOverflow forces me to include it):

@keyframes test {
  0% {
    clip-path: inset(0px 0px 300px 0px);
  }
  80% {
    clip-path: inset(0px 0px 0px 0px);
  }
  100% {
    clip-path: inset(0px 0px 0px 0px);
  }
}

svg {
  animation: test;
  animation-duration: 2s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

A simple SVG in the example above is easy to animate, I can just slowly un-crop the SVG from top-down. But if I have a very complex shape which cannot be animated in this manner (maybe the red line in NASA's logo: https://upload.wikimedia.org/wikipedia/commons/e/e5/NASA_logo.svg), I need a better solution.

Just to clarify again, I do not want to animate the stroke. I want to be able to animate the fill, like it is begin drawn out.

Is there any general solution to this? If there is no general solution, how would I go about key-framing this on my own in a reasonable amount of time?

EDIT: To give some insight, I'm trying to animate the treble clef: https://upload.wikimedia.org/wikipedia/commons/e/e8/G-clef.svg

回答1:

You could do this (easier to describe than to do, there's a lot of fiddly work involved):

  1. Draw a smooth path on top of the shape that follows what you would consider the "path" and select such a stroke-width that the path covers the shape everywhere.
  2. I would leave the final dot completely out of this, as it is so much thicker than the rest.
  3. Divide the path in such a way that it has no overlapping parts. Order the parts in drawing order, make sure every partial path is drawn in the right direction.
  4. Now divide the shape in the same way, making sure each part is exactly beneath the path on top.
  5. Associate each of the shapes with one of the paths on top, defining the path as a mask for the shape. The path must have stroke:white. Preserve order.
  6. Now you can animate the paths that define the masks with a stroke-dashoffset animation.
  7. I would simply hide the final dot until the pseudo-line animation finishes and then reveal it at once.

Edit: I've definitely got too much time on my hands today, here's the working result:

.clef {
    fill: black;
    stroke: black;
    stroke-width: 0.1;
}
mask path {
    fill: none;
    stroke: white;
    stroke-width: 6;
}
#mask1 path {
    stroke-dasharray: 100.8186 100.8186;
    stroke-dashoffset: 100.8186;
    animation: draw1 1s linear forwards;
}
@keyframes draw1 {
    from { stroke-dashoffset: 100.8186; }
    to { stroke-dashoffset: 0; }
}
#mask2 path {
    stroke-dasharray: 83.6713 83.6713;
    stroke-dashoffset: 83.6713;
    animation: draw2 1s 1s linear forwards;
}
@keyframes draw2 {
    from { stroke-dashoffset: 83.6713; }
    to { stroke-dashoffset: 0; }
}
.dot {
    opacity: 0;
    animation: reveal 0s 2.5s forwards;
}
@keyframes reveal {
    from { opacity: 0; }
    to { opacity: 1; }
}
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="200" viewBox="0 0 44 75">
  <defs>
  <mask id="mask1" maskUnits="userSpaceOnUse">
      <path d="M 24.3018,49.658 C 15.0191,46.9092 18.5393,38.1126 25.6256,38.2163 35.5458,38.3614 34.8431,54.3874 22.6943,54.1023 12.0123,53.8516 7.34095,40.0402 18.4391,30.1787 29.5373,20.3173 29.9235,12.5622 27.8005,9.28112" />
  </mask>
  <mask id="mask2" maskUnits="userSpaceOnUse">
      <path d="M 27.8005,9.28112 C 25.1382,5.16638 17.6602,8.86888 20.5194,22.1412 L 28.1788,57.6956 C 31.6264,73.699 16.4903,72.3627 15.035,62.329" />
  </mask>
  </defs>
  <path class="clef" mask="url(#mask1)" d="M 26.8522,9.90048 C 26.912,9.95039 26.9649,10.0085 27.0101,10.075 27.4815,10.7683 28.6214,14.0098 25.3767,19.8157 22.846,24.3437 11.0718,30.2815 10.2077,40.9075 9.45969,50.1477 19.1325,56.9723 27.4811,54.2894 33.0239,52.5081 35.8812,44.0959 32.4504,39.7568 23.3964,28.3057 8.87616,45.8309 22.9422,50.6319 21.4126,49.4286 20.37,48.4968 20.1759,47.3578 18.286,36.2692 34.9591,39.1968 30.4666,49.7165 28.6194,54.0421 21.1577,54.879 16.9085,51.0198 13.3489,47.787 11.7693,41.5593 15.7305,37.0885 21.0956,31.0332 27.4302,25.5974 29.1125,17.3081 29.7841,13.9988 29.4887,10.9357 28.6445,8.70078 Z" />
  <path class="clef" mask="url(#mask2)" d="M 15.7311,63.3465 C 15.3353,65.46 17.5402,69.8491 21.9764,69.9924 27.3392,70.1658 30.7655,66.0634 29.1692,59.3682 L 21.164,22.4229 C 20.2111,18.0249 20.9262,15.6394 21.4351,14.2178 22.7185,10.6326 25.8192,9.03863 26.8522,9.90048 L 28.6445,8.70078 C 26.9883,4.31578 23.2199,3.11893 20.4997,9.50576 19.1217,12.7412 18.6085,15.989 19.9279,22.2128 L 27.9268,59.9444 C 28.4995,62.6457 28.1161,66.3629 25.595,68.0714 24.3461,68.9177 19.9267,69.5001 18.8455,67.48" />
  <path class="clef dot" d="M 15.6702,63.6634 A 3.77139,3.8362 1.075 0 1 19.5129,59.8986 3.77139,3.8362 1.075 0 1 23.2116,63.8049 3.77139,3.8362 1.075 0 1 19.3689,67.5697 3.77139,3.8362 1.075 0 1 15.6702,63.6634 Z" />
</svg>



回答2:

SVG solution

In this example, solutions from the @ccprog response are used, but the animation is moved from the CSS rules directly to theSVG animation commands

The commands that implement the animation are the same for all patches. The difference in the numeric value of the stroke-dasharray and stroke-dashoffset attributes for each patch:

<animate attributeName="stroke-dashoffset" dur="1s" values="100.8186;0" fill="freeze" />

The animation command for the appearance of a dot on the end of a treble clef:

<animate attributeName="opacity" dur="0.05s" values="0;1" begin="an2.end-0.05s" fill="freeze" />

<svg xmlns="http://www.w3.org/2000/svg" width="100" height="200" viewBox="0 0 44 75">
  <defs>
    <mask id="mask1" maskUnits="userSpaceOnUse">
      <path fill="none" stroke="white" stroke-width="6" stroke-dasharray="100.8186" stroke-dashoffset="100.8186" d="M 24.3018,49.658 C 15.0191,46.9092 18.5393,38.1126 25.6256,38.2163 35.5458,38.3614 34.8431,54.3874 22.6943,54.1023 12.0123,53.8516 7.34095,40.0402 18.4391,30.1787 29.5373,20.3173 29.9235,12.5622 27.8005,9.28112">
        <animate attributeName="stroke-dashoffset" dur="1s" values="100.8186;0" fill="freeze"/>
      </path>
    </mask>
    <mask id="mask2" maskUnits="userSpaceOnUse">
      <path fill="none" stroke="white" stroke-width="6" stroke-dasharray="83.6713" stroke-dashoffset="83.6713" d="M 27.8005,9.28112 C 25.1382,5.16638 17.6602,8.86888 20.5194,22.1412 L 28.1788,57.6956 C 31.6264,73.699 16.4903,72.3627 15.035,62.329" >
        <animate id="an2" attributeName="stroke-dashoffset" dur="1s" values="83.6713;0" begin="+1s" fill="freeze"/>
      </path>
    </mask>
  </defs>
  <path class="clef" fill="black" stroke="black" stroke-width="0.1" mask="url(#mask1)" d="M 26.8522,9.90048 C 26.912,9.95039 26.9649,10.0085 27.0101,10.075 27.4815,10.7683 28.6214,14.0098 25.3767,19.8157 22.846,24.3437 11.0718,30.2815 10.2077,40.9075 9.45969,50.1477 19.1325,56.9723 27.4811,54.2894 33.0239,52.5081 35.8812,44.0959 32.4504,39.7568 23.3964,28.3057 8.87616,45.8309 22.9422,50.6319 21.4126,49.4286 20.37,48.4968 20.1759,47.3578 18.286,36.2692 34.9591,39.1968 30.4666,49.7165 28.6194,54.0421 21.1577,54.879 16.9085,51.0198 13.3489,47.787 11.7693,41.5593 15.7305,37.0885 21.0956,31.0332 27.4302,25.5974 29.1125,17.3081 29.7841,13.9988 29.4887,10.9357 28.6445,8.70078 Z"/>
  <path class="clef" fill="black" stroke="black" stroke-width="0.1" mask="url(#mask2)" d="M 15.7311,63.3465 C 15.3353,65.46 17.5402,69.8491 21.9764,69.9924 27.3392,70.1658 30.7655,66.0634 29.1692,59.3682 L 21.164,22.4229 C 20.2111,18.0249 20.9262,15.6394 21.4351,14.2178 22.7185,10.6326 25.8192,9.03863 26.8522,9.90048 L 28.6445,8.70078 C 26.9883,4.31578 23.2199,3.11893 20.4997,9.50576 19.1217,12.7412 18.6085,15.989 19.9279,22.2128 L 27.9268,59.9444 C 28.4995,62.6457 28.1161,66.3629 25.595,68.0714 24.3461,68.9177 19.9267,69.5001 18.8455,67.48"/>
  <path class="clef dot" opacity="0" d="M 15.6702,63.6634 A 3.77139,3.8362 1.075 0 1 19.5129,59.8986 3.77139,3.8362 1.075 0 1 23.2116,63.8049 3.77139,3.8362 1.075 0 1 19.3689,67.5697 3.77139,3.8362 1.075 0 1 15.6702,63.6634 Z">
    <animate attributeName="opacity" dur="0.05s" values="0;1" begin="an2.end-0.05s" fill="freeze"/>
  </path>
</svg>

Update SMIL SVG does not work in IE/Edge - caniuse