DEV Community

leimapapa
leimapapa

Posted on • Originally published at Medium on

Let’s Make Fire from an SVG

fire sale
The “sale” refers to our savings on file size

Let’s make a realistic fire effect from an SVG!

Impossible you say? How dare you. You and I don’t even know each other and you’re coming at me all hostile like that? Super rude of you.

sobbing
It’s okay, I’m okay.

Anyways, here’s the effect we’re going for:

fire
Fiya!

Now I know what you’re thinking, that’s impossibly complex and learning how to make it probably causes brain cancer or something.

Wrong again bucko.

This specific fire effect is as simple as applying a turbulence effect to a shape and animating the shape downward while animating the surrounding area up at the same speed.

In fact, here’s the entire code for the fire above:

<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
 <defs>
  <!-- turbulence filter -->
  <filter id="turb" x="-100%" y="-100%" width="300%" height="300%">
   <feTurbulence type="turbulence" baseFrequency="0.06" numOctaves="2.5" result="turbulence" seed="69" />
   <feDisplacementMap in2="turbulence" in="SourceGraphic" scale="35" />
  </filter>
  <!-- fire gradient -->
  <radialGradient id="grad" cx="50%" cy="100%">
      <stop offset="0%" stop-color="blue" />
      <stop offset="20%" stop-color="gold" />
      <stop offset="60%" stop-color="gold" />
      <stop offset="100%" stop-color="red" />
 </radialGradient>
 </defs>
 <g>
  <!-- triangle shape with turbulence applied -->
  <path d="M70 200 h60 l-30 -90z" filter="url(#turb)" fill="url(#grad)">
   <!-- triangle shape moving down -->
   <animate attributeName="d" values="M70 200 h60 l-30 -90z; M70 7200 h60 l-30 -90z" dur="100s" begin="0s" repeatCount="indefinite" />
  </path>
  <!-- group container moving up -->
  <animateTransform attributeName="transform" attributeType="XML" type="translate" values="0 0; 0 -7000" dur="100s" begin="0s" repeatCount="indefinite" />
 </g>
</svg>
Enter fullscreen mode Exit fullscreen mode

Dead simple, right? Now you can probably figure out what’s going on from here, but allow me to explain it piece by piece for the SVG nerds among us.

dozens of us
SVG nerds assemble!

First off, the turbulence filter:

<!-- turbulence filter -->
  <filter id="turb" x="-100%" y="-100%" width="300%" height="300%">
   <feTurbulence type="turbulence" baseFrequency="0.06" numOctaves="2.5" result="turbulence" seed="69" />
   <feDisplacementMap in2="turbulence" in="SourceGraphic" scale="35" />
  </filter>
Enter fullscreen mode Exit fullscreen mode

This is where we set the waviness of the fire effect. I massaged the turbulence numbers to make it appear more fire-y, but you could mess with the baseFrequency, numOctaves, and displacement scale to see different styles.

Next, the color gradient of the fire:

<!-- fire gradient -->
  <radialGradient id="grad" cx="50%" cy="100%">
      <stop offset="0%" stop-color="blue" />
      <stop offset="20%" stop-color="gold" />
      <stop offset="60%" stop-color="gold" />
      <stop offset="100%" stop-color="red" />
 </radialGradient>
Enter fullscreen mode Exit fullscreen mode

I used a radial gradient because it seemed to mimic a fire a little better than a linear gradient.

I also added a little bit of blue at the bottom, but some might argue it looks better without it. Not me though, I like the blue.

blued
#backtheblue

Now the most complex part, the path animation:

<!-- group to animate downward -->
<g>
  <!-- triangle shape with turbulence applied and the gradient fill -->
  <path d="M70 200 h60 l-30 -90z" filter="url(#turb)" fill="url(#grad)">
   <!-- triangle shape moving down by animating its path value -->
   <animate attributeName="d" values="M70 200 h60 l-30 -90z; M70 7200 h60 l-30 -90z" dur="100s" begin="0s" repeatCount="indefinite" />
  </path>
Enter fullscreen mode Exit fullscreen mode

The path has a default value of M70 200 h60 l-30 -90z.

Broken down, this path starts at M70 200 (x position 70 and y position 200).

It then goes horizontally relative to its starting position by 60 with h60 (lowercase h is relative).

Lastly, it goes back left by 30 and up by 90 ( l-30 -90 ) before closing with a z.

triangle
Here’s the original path (just a simple triangle)

Now the animation part. The animation we apply to its d attribute will make use of the relative movements and therefore we only need to animate to a different starting position.

In my example, I chose 7000 units down. This is because I wanted mine not to visibly repeat itself anytime soon, so I have my repeat value set to repeat at 100 seconds.

And for the fire to appear to whip in a fire-y way, 7000 seemed right. A smaller number (over the course of the 100 seconds animation duration) would make the fire appear to whip faster.

We’re basically just animating the starting point from M70 200 to M70 7200 and leaving everything else the same:

<!-- triangle shape moving down by animating its path value -->
<animate attributeName="d" 
values="
M70 200 h60 l-30 -90z; 
M70 7200 h60 l-30 -90z" 
dur="100s" 
begin="0s" 
repeatCount="indefinite" />
Enter fullscreen mode Exit fullscreen mode

Then, inside the surrounding group ( component) we add a animateTransform to keep the fire inside of the viewBox (so the triangle doesn’t just run away off the screen).

<g>
  <!-- triangle shape moving down -->
  <path ...><animate .../></path>
  <!-- group container moving up -->
  <animateTransform attributeName="transform" 
    attributeType="XML" 
    type="translate" 
    values="0 0; 0 -7000" 
    dur="100s" 
    begin="0s" 
    repeatCount="indefinite" />
 </g>
Enter fullscreen mode Exit fullscreen mode

This one is a simple transform in the opposite direction by the same amount (7000) and over the same amount of time (100 seconds).

Now, I know what you’re thinking yet again. Can’t you just animate the with it’s own < animateTransform> instead of having to deal with all that complicated path nonsense? Something like this?:

<g>
  <!-- triangle shape moving down -->
  <path ...>
    <animateTransform attributeName="transform" 
    attributeType="XML" 
    type="translate" 
    values="0 0; 0 7000" 
    dur="100s" 
    begin="0s" 
    repeatCount="indefinite" />
  </path>
  <!-- group container moving up -->
  <animateTransform attributeName="transform" 
    attributeType="XML" 
    type="translate" 
    values="0 0; 0 -7000" 
    dur="100s" 
    begin="0s" 
    repeatCount="indefinite" />
 </g>
Enter fullscreen mode Exit fullscreen mode

That is an excellent question. Here’s the answer:

nope
no. already tried it, just no.

If you do it that way, the turbulence filter doesn’t change as the two < animateTransform> s cancel each other out and the turbulence of the fire appears fixed and not like an animated fire. Which was kinda the whole goal…

But here’s the good news, it doesn’t have to be a path triangle where you have to worry about the relative paths and animating between them. You can also use the y attribute of a component or the cy attribute of a or :

<!-- ellipse example -->
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
 <defs>
  <!-- same as before -->
 </defs>
 <g>
  <!-- ellipse shape with turbulence applied -->
  <ellipse cx="100" cy="100" rx="33" ry="50" filter="url(#turb)" fill="url(#grad)">
   <animate attributeName="cy" values="100; 7100" dur="100s" begin="0s" repeatCount="indefinite" />
  </ellipse>
  <animateTransform attributeName="transform" attributeType="XML" type="translate" values="0 0; 0 -7000" dur="100s" begin="0s" repeatCount="indefinite" />
 </g>
</svg>
Enter fullscreen mode Exit fullscreen mode

Which gives us this:

fiya

Huzzah for simplicity!

huzzah
and again I say huzzah!

Here is the SVG fire effect in a CodePen to play around with:

And that’s pretty much it. A computationally cheap and small file size SVG fire… so… um… yeah… See ya.

uh bye

Top comments (0)