Friday, 13 February 2026

Web Tutorial: Year of the Horse SVG Animation (Part 1/3)

It's that time of the year again! Chinese New Year has arrived! 2026 is the Year of the Horse, and I want to work on a nice SVG animation. It's so nice that I'm going to have to break up this web tutorial into more manageable components.

First, we want a galloping horse. No two ways about it. It's the Year of the Horse, after all. From the internet, I obtained this file and scaled it down, then cut out the individual horses to use as frames for the animation. No, I didn't pay for it. I'm also not profiting from this web tutorial, so...

The original image.



horse00.png



horse01.png



horse02.png



horse03.png


This part of the web tutorial shows you how to import external images into an SVG. Let's get the HTML in there. It's a 600 by 400 pixel animation. I set the background to green for visibility.
<!DOCTYPE html>
<html>
  <head>
    <title>Year of the Horse</title>
  </head>

  <body>
    <svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">

    </svg>
  </body>
</html>


We want to place horse00.png right here, around the bottom middle.
<!DOCTYPE html>
<html>
  <head>
    <title>Year of the Horse</title>
  </head>

  <body>
    <svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">
     <image href="horse00.png" x="250" y="300" width="100" height="55">

     </image>

    </svg>
  </body>
</html>


See this?


Now place the rest of the PNG files, all overlapping one another.
<!DOCTYPE html>
<html>
  <head>
    <title>Year of the Horse</title>
  </head>

  <body>
    <svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">
     <image href="horse00.png" x="250" y="300" width="100" height="55">

     </image>

     <image href="horse01.png" x="250" y="300" width="100" height="55">

     </image>

     <image href="horse02.png" x="250" y="300" width="100" height="55">

     </image>

     <image href="horse03.png" x="250" y="300" width="100" height="55">

     </image>

    </svg>
  </body>
</html>


It's going to look a right mess, until...


...you set opacity to 0! At that point, the horses will all disappear.
<!DOCTYPE html>
<html>
  <head>
    <title>Year of the Horse</title>
  </head>

  <body>
    <svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">
     <image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">

     </image>

     <image href="horse01.png" x="250" y="300" width="100" height="55" opacity="0">
  
     </image>

     <image href="horse02.png" x="250" y="300" width="100" height="55" opacity="0">
  
     </image>

     <image href="horse03.png" x="250" y="300" width="100" height="55" opacity="0">

     </image>
    </svg>
  </body>
</html>


We'll then animate the first image with this. This animation has the id horse0, which we'll need for referencing later. We set attributeName to opacity because that's what we'll be animating. It goes from 0.8 to 1, as you can see from the from and to attributes, with a very short duration of 0.1 seconds. It begins as soon as the SVG loads, so begin is set to 0 seconds. And lastly, we set repeatCount to 1. In theory, it should only execute once.
<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse0"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="0s"
   repeatCount="1"
  />

</image>


For the next one, we have horse1. It is identical to horse0, except that it begins only when horse0 ends.
<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse0"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="0s"
   repeatCount="1"
  />
</image>

<image href="horse01.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse1"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse0.end"
   repeatCount="1"
  />

</image>


And so on, and so for.
<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse0"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="0s"
   repeatCount="1"
  />
</image>

<image href="horse01.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse1"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse0.end"
   repeatCount="1"
  />
</image>

<image href="horse02.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse2"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse1.end"
   repeatCount="1"
  />

</image>

<image href="horse03.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse3"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse2.end"
   repeatCount="1"
  />

</image>


Of course, you'll want the reverse animation to make the previous frame disappear. These ones won't require an id. Now when you refresh your page, you'll see the horse animate... but only once.
<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse0"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="0s"
   repeatCount="1"
  />

  <animate
   attributeName="opacity"
   from="0.5" to="0"
   dur="0.1s"
   begin="horse0.end"
   repeatCount="1"
  />

</image>

<image href="horse01.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse1"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse0.end"
   repeatCount="1"
  />

  <animate
   attributeName="opacity"
   from="0.5" to="0"
   dur="0.1s"
   begin="horse1.end"
   repeatCount="1"
  />

</image>

<image href="horse02.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse2"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse1.end"
   repeatCount="1"
  />

  <animate
   attributeName="opacity"
   from="0.5" to="0"
   dur="0.1s"
   begin="horse2.end"
   repeatCount="1"
  />

</image>

<image href="horse03.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse3"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse2.end"
   repeatCount="1"
  />

  <animate
   attributeName="opacity"
   from="0.5" to="0"
   dur="0.1s"
   begin="horse3.end"
   repeatCount="1"
  />

</image>


In here, you'll need an additional trigger. horse0 should run once when the SVG loads... and also when horse3 ends. This will in turn trigger the rest of the animations in an infinite loop!
<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse0"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="0s;horse3.end"
   repeatCount="1"
  />

  <animate
   attributeName="opacity"
   from="0.5" to="0"
   dur="0.1s"
   begin="horse0.end"
   repeatCount="1"
  />

</image>


Because of the reverse animations we added, you can see a shadowy effect of the horse in the animation! Without it, the animation would look a lot jerkier.



Excellent! We have a galloping horse. What next?

We'll do some easy parts first. Let's have some text. It's a simple text tag where I have some Chinese New Year greeting in Chinese text, in orange fill and red outline. In English, it means "Teochew Thunder wishes all the spirit of dragons and horses!"
<svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">
  <text x="300" y="30" text-anchor="middle" font-size="30px" fill="rgb(250, 100, 0)" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2">潮州雷祝大家龙马精神!</text>

  <image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
    <animate
      id="horse0"
      attributeName="opacity"
      from="0.8" to="1"
      dur="0.1s"
      begin="0s;horse3.end"
      repeatCount="1"
    />

    <animate
      attributeName="opacity"
      from="0.5" to="0"
      dur="0.1s"
      begin="horse0.end"
      repeatCount="1"
    />
  </image>


Cool, right?


Then four red rectangles on each of the corners of the SVGs, leaving a 10 pixel border of space.
<text x="300" y="30" text-anchor="middle" font-size="30px" fill="rgb(250, 100, 0)" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2">潮州雷祝大家龙马精神!</text>

<rect fill="none" x="10" y="10" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="570" y="10" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="10" y="370" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="570" y="370" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />


<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">


See this?


And then lines to connect the rectangles.
<text x="300" y="30" text-anchor="middle" font-size="30px" fill="rgb(250, 100, 0)" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2">潮州雷祝大家龙马精神!</text>

<line x1="20" y1="20" x2="100" y2="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<line x1="500" y1="20" x2="580" y2="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<line x1="20" y1="20" x2="20" y2="380" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<line x1="580" y1="20" x2="580" y2="380" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<line x1="20" y1="380" x2="580" y2="380" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />

<rect fill="none" x="10" y="10" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="570" y="10" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="10" y="370" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="570" y="370" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />

<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">


It's actually pretty simple, but the effect is that of a horse galloping in a frame.


That's it for now!

This is actually a good stopping point if it's enough for you. You just need to change the ugly green background to something else. But we're going to seriously improve on this...

Next

Adding a changing sky background.

No comments:

Post a Comment