Home

Animated source code svg

October 20, 2022

I had a pretty boring image on my Github profile readme for a long time. I wanted to create some kind of introduction there. When I looked for some inspiration, many people had so much information there that it looked very noisy and messy. I wanted to keep it simple.

I've been building this website for the past few weeks. That has included a lot of handwritten HTML. I got some inspiration from this great article <h1>This page is a truly naked, brutalist html quine</h1>. I wanted a brutalist HTML introduction.

The first version was simply a code block.

```html
<h1>Hi</h1>
<p>
  I'm Jonnie.
  I'm a software developer.
</p>
<a href="//jonniek.github.io">
  Read more...
</a>
```

Resulting in:

<h1>Hi</h1>
<p>
  I'm Jonnie.
  I'm a software developer.
</p>
<a href="//jonniek.github.io">
  Read more...
</a>

This is nice, but I wanted the link to be clickable. It seems possible to add some links to code blocks, but it's very limited and changes the look. In order to get a link I changed the approach from markdown syntax to HTML.

To make the HTML visible we need to use some entity codes.

So after converting the html tags, wrapping it in a pre and adding a link we have this.

<pre>
&lt;h1&gt;Hi&lt;/h1&gt;
&lt;p&gt;
  I'm Jonnie.
  I'm a software developer.
&lt;/p&gt;
&lt;a href="//jonniek.github.io"&gt;
  <a href="//jonniek.github.io">Read more...</a>
&lt;/a&gt;
</pre>

Resulting in:

<h1>Hi</h1> <p>   I'm Jonnie.   I'm a software developer. </p> <a href="//jonniek.github.io">  Read more...</a>

The link works exactly like I wanted, but the syntax highlighting disappeared. There is no easy way to add color to text, since Github strips style elements and attributes. The only way to color text in an arbitrary way is by using an SVG. A nice thing about SVG is that you can animate them quite easily. The downside is that it would probably not be possible to add a granular link. I think the color and some animation are cooler than a nice link.

So let's take a look at a simple animated SVG text.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="40px">
  <path id="example1">
    <animate
      attributeName="d"
      from="m0,25 h0"
      to="m0,25 h500"
      dur="6s"
      begin="0s"
      repeatCount="indefinite"
    />
  </path>
  <text font-size="16" font-family="monospace" fill='#333'>
    <textPath xlink:href="#example1">
      This text is animated in SVG
    </textPath>
  </text>
</svg>

Resulting in:

This text is animated in SVG

To not annoy you by endless animations, here on out the example animations will only play when you click them. I'm also removing the animation repeating, so just click them again to replay if you want to.

Let's add a background so you can see where to click. To create a background for the text let's use a <rect> element. The rx and ry can be used to make the corners rounded.

<rect fill="#eaeaea" rx="6" ry="6" />

Hello world <- Click me to run

To add some padding to the text we need to understand the from and to attribute paths of the animation. The first two parameters after m are the x and y coordinates. The h0 to h500 determine the width of the animation, it should be at least as wide as the text to avoid clipping. If it's longer than the width of the text, then the animation keeps on playing with no changes, this makes the duration visibly inaccurate.

Anyway let's change all the m0 to m16 instead to add some padding on the x axis.

Hello world

Next to add HTML syntax is quite straight forward. Same formatting as earlier.

<textPath>&lt;h1&gt;Hello world&lt;/h1&gt;</textPath>

<h1>Hello world</h1>

To add color for the syntax wrap the tokens in a <tspan> element with a fill attribute.

<textPath>
  &lt;<tspan fill="red">h1</tspan>&gt;Hello world&lt;/<tspan fill="red">h1</tspan>&gt;
</textPath>

<h1>Hello world</h1>

To create the effect of typing on multiple lines, we just need another animation path and text element. Remember to increase the y coordinate in the animation paths to make the second line below the first.

<h1>The first line</h1><h2>The second line</h2>

In order to run animations sequentially we have to do two things.

  1. Add fill="freeze" to the animate element
  2. Remove repeatCount="indefinite" from the animate element

This will give us full control over when the animation runs with the begin attribute. The freeze will also keep the text visible once the animation has completed.

We could manually calculate and adjust the starts to be sequential. However, imagine if we have 10 lines and want to change the duration of the first line. We would have to recalculate and change all timings. Luckily you can simply do begin="id1.end+0.1s". This will start the animation 0.1 seconds after the end of animation which has an element id of id1. You can also use ms instead of seconds if you wish.

<h1>Short line</h1><h2>The second line</h2>

The animation width also has to be adjusted to match the width of the actual line. This will help keep the speed constant. For example the above animation.

The result is the following animate elements if we want a speed of 0.1s per character.

<animate
  id="first"
  begin="0s"
  dur="1.9s"
  from="m15,25 h0"
  to="m15,25 h186"
/>

<animate
  id="second"
  begin="first.end+0.1s"
  dur="2.4s"
  from="m15,50 h0"
  to="m15,50 h235"
/>

To create an effect of erasing the text we can add another animate with the from and to attribute values flipped.

<animate id="line1" begin="0s" dur="1.25s"
  from="m15,25 h0" to="m15,25 h186" />
<animate id="line1reverse" begin="line2reverse.end" dur="0.25s"
  to="m15,25 h0" from="m15,25 h186" />
  
<animate id="line2" begin="line1.end+0.1s" dur="1.6s"
  from="m15,50 h0" to="m15,50 h235" />
<animate id="line2reverse" begin="line2.end+0.5s" dur="0.32s"
  to="m15,50 h0" from="m15,50 h235" />
  

<h1>Short line</h1><h2>The second line</h2>

If you want to loop animations, there is a better way than the repeatCount attribute. You can simply use two begin values for the first animate like this begin="0s;lastid.end". Then just make sure your animations make a closed loop with referencing each other in begin attributes.

In preparation of adding more lines let's add some common styles in a <style> element. I really wanted to use var(--css-variables) as the colors from Github. Unfortunately Github adds SVGs in image tags, which do not have access to the css variables of the parent document. This makes it impossible to always match 1 to 1 with the theme Github is using. We can still try to respect the users choice of a theme by adding a media query.

<style>
  rect { fill: #161b22 }
  text { fill: #c9d1d9 }
  .el { fill: #7ee787 }
  .attr { fill: #79c0ff }
  .val { fill: #a5d6ff }
  @media (prefers-color-scheme:light) {
    rect { fill: #f6f8fa }
    text { fill: #24292f }
    .el { fill: #116329 }
    .attr { fill: #0550ae }
    .val { fill: #0a3069 }
  }
</style>

Combining all the things we have learned so far with some tweaking we might end up with something like this.

Looks pretty good!

Let's put it to Github. You can create the SVG wrapped in a link in markdown like this.

[![](https://example.com/animation.svg)](https://jonniek.github.io/)

You can also use HTML syntax. This way the height and width attributes can be set, which can prove quite handy in controlling the result.

<a href="https://jonniek.github.io/">
  <img height="190px" width="100%" src="https://example.com/animation.svg">
</a>

I want it to look as close to possible to a real code block in Github. To see what it should look liked we can edit a README in Github, add the target HTML in a code block and preview it.

I was having a lot of trouble trying to make this render exactly like above in Github. But the reason was that I am using Firefox. On Firefox it seems impossible to satisfy all the above requirements. I tried every combination of viewBox, preserveAspectRatio and nested SVGs I could think of. Always one of the following happened.

If you open the SVG itself on Firefox it will render correctly, but somehow when used in an img tag it renders in a different way. I think it might be some bug in Firefox SVG rendering.

I really like Firefox. But I don't like it so much that I would give up my quest of imitating a Github code block pixel perfectly. So for now the Firefox rendering will be broken and show text sizes for ants.

If you want to try fixing it for Firefox go ahead and fork my repository. Serve the directory with python3 -m http.server and open the index on Firefox. The limitations are that you can only set the width, height and src attributes from the HTML. Everything else has to be done from the SVG file. Wanted behaviour on all major browsers would be the following.

If you figure it out, please open a pull request!

Here is a picture of the final result in my profile.

I hope you found some inspiration from this. Here is some ideas to take this further.