Preface

Goal: Animating SVG using Keyframe in Stylesheet.

The idea is, we can rewrite, the d attribute as ‘css property`.

Preview

SVG Bezier: Keyframe Animation: Javascript Automation


Single Bezier Area

Consider start from HTML document. Then stylesheet is all we need. No javascript required at all.

HTML: Head

<head>
  <title>Bezier Curves</title>
  <link rel="stylesheet"
        href="24-bezier.css"/>
  <link rel="stylesheet"
        href="24-style.css"/>
  <link rel="stylesheet"
        href="31-animate.css"/>
</head>

HTML: Body

Also prepare the HTML body. Only for single bezier, let’s say, the fifth bezier.

<body>
  <div id="svg_container">
    <svg xmlns="http://www.w3.org/2000/svg"
         viewbox="0 0 200 160">
      <rect x="0" y="0"     class="box"/>

      <!-- material blue 05 -->
      <path id="svg_area_5" class="area"/>
      <path id="svg_line_5" class="line green"/>

    </svg>
  </div>

  <div id="sitedesc_image"></div>
  <p style="text-align: center;">
    Paragraph Test on SVG Background</p>
</body>

Preview

SVG Bezier: Keyframe Animation: Single Bezier

Stylesheet: Skeleton

For single animation, this should be simple.

First the keyframes, for both area and guideline.

@keyframes area_5 {
  from {...}
  50%  {...}
  to   {...}
}
@keyframes line_5 {
  from {...}
  50%  {...}
  to   {...}
}

Then the element, for both area and guideline.

path#svg_area_5 {...}
path#svg_line_5 {...}

Stylesheet: Element Area

I keep the long version, just for reminder.

path#svg_area_5 {
  fill: #2196F3;

  animation-duration: 5s;
  animation-name: area_5;
  animation-iteration-count: 3;
  animation-timing-function: ease;
  animation-direction: normal;
}

While the definition of area_5 shown below.

Stylesheet: Element Line

Although I can write the short form.

path#svg_line_5 {
  animation: line_5 4s ease 4 normal;
}

While the definition of line_5 shown below.

Stylesheet: Keyframe Area

Here is the definition of area_5:

@keyframes area_5 {
  from {
    d: path("M 0 70 C 50 60 70 60 120 80 S 170 110 200 100 L 200 0 L 0 0 z");
  }
  50% {
    d: path("M 0 50 C 30 40 50 40 100 60 S 150 90 200 80 L 200 0 L 0 0 z");
  }
  to {
    d: path("M 0 70 C 50 60 70 60 120 80 S 170 110 200 100 L 200 0 L 0 0 z");
  }
}

In my case, the d from and d to, is exactly the same. So i can have back and forth effect.

The d 50% comes from area_6. Because I’m simply too lazy to create another bezier.

Stylesheet: Keyframe Line

@keyframes line_5 {
  from {
    d: path("M 0 70 C 50 60 50 60 100 80 S 150 100 200 90");
  }
  50% {
    d: path("M 0 50 C 30 40 20 40 70 60 S 100 80 200 70");
  }
  to {
    d: path("M 0 70 C 50 60 50 60 100 80 S 150 100 200 90");
  }
}

The same process as the area_5 above. The d from and d to, is exactly the same. The d 50% comes from area_6.

Preview

The result is similar as below:


Single Bezier Area using Javascript

Why Automation?

Again, writing down all d property, for use with keyframe, from ten bezier area is tedious.

We need to automate the stylesheet. Luckily, this can be done easily with javascript.

HTML: Head

Instead of stylesheet, all keyframe generated by javascript.

<head>
  <title>Bezier Curves</title>
  <link rel="stylesheet"
        href="24-bezier.css"/>
  <link rel="stylesheet"
        href="24-style.css"/>
  <script src="32-bezier.js"></script>
</head>

HTML: Body

Remain intact.

Preview

The output is slighly different, because I made modification in d 50%, to make it looks like flip flop, back and forth.

SVG Bezier: Keyframe Animation: Single Bezier Flip

Javascript: Skeleton

This is simply just writing the stylesheet, in javascript.

document.addEventListener(
  "DOMContentLoaded", function(event) {
    const styles_kf_a5 = `@keyframes area_5 {...}`
    const styles_kf_l5 = `@keyframes line_5 {...}`
    const styles_id_a5 = `path#svg_area_5 {...}`
    const styles_id_l5 = `path#svg_line_5 {...}`

    const styles = styles_kf_a5 + styles_kf_l5
                 + styles_id_a5 + styles_id_l5;

    // Create style document
    ...
});

And then write the style into head tag.

    // Create style document
    const css = document.createElement('style');
    css.type = 'text/css';
    css.appendChild(document.createTextNode(styles));
    document.getElementsByTagName("head")[0].appendChild(css);

Notice the variant argument. And frame function inside modifyPaths.

Javascript: Keyframe Area

    const styles_kf_a5 = `
    @keyframes area_5 {
      from { d: path("M 0 70 C 50  60 70  60 120 80 S 170 110 200 100 L 200 0 L 0 0 z"); }
      50%  { d: path("M 0 90 C 30 100 50 100 100 80 S 150  50 200  60 L 200 0 L 0 0 z"); }
      to   { d: path("M 0 70 C 50  60 70  60 120 80 S 170 110 200 100 L 200 0 L 0 0 z"); }
    }
`;

Javascript: Keyframe Line

    const styles_kf_l5 = `
    @keyframes line_5 {
      from { d: path("M 0 70 C 50 60 50 60 100 80 S 150 100 200 90"); }
      50%  { d: path("M 0 50 C 30 40 20 40 70 60 S 100 80 200 70"); }
      to   { d: path("M 0 70 C 50 60 50 60 100 80 S 150 100 200 90"); }
    }
`;

Javascript: Element Area

    const styles_id_a5 = `
    path#svg_area_5 {
      fill: #2196F3;
      animation: area_5 5s ease infinite normal;
    }
`;

Javascript: Element Line

    const styles_id_l5 = `
    path#svg_line_5 {
      animation: line_5 4s ease infinite normal;
    }
`;

Flip Effect

I made flip-flop effect by using absolute value.

For example the base d from line is 50. And d 50% line is 70.

Then I change the d 50% line into30.

Preview

The result is similar as below:


Multiple Bezier Areas using Javascript

Consider start from HTML document. Then stylesheet is all we need. No javascript required at all.

HTML: Head

Just switch to another javascript file.

<head>
  <title>Bezier Curves Animation</title>
  <link rel="stylesheet"
        href="24-bezier.css"/>
  <link rel="stylesheet"
        href="24-style.css"/>
  <script src="33-bezier.js"></script>
</head>

HTML: Body

We must prepare all the path tags for all bezier areas.

<body>
  <div id="svg_container">
    <svg xmlns="http://www.w3.org/2000/svg"
         viewbox="0 0 200 300">
      <path id="svg_area_0" class="area"/>
      <path id="svg_area_1" class="area"/>
      <path id="svg_area_2" class="area"/>
      <path id="svg_area_3" class="area"/>
      <path id="svg_area_4" class="area"/>
      <path id="svg_area_5" class="area"/>
      <path id="svg_area_6" class="area"/>
      <path id="svg_area_7" class="area"/>
      <path id="svg_area_8" class="area"/>
      <path id="svg_area_9" class="area"/>
    </svg>
  </div>

  <div id="sitedesc_image"></div>
  <p style="text-align: center;">
    Paragraph Test on SVG Background</p>
</body>

Notice the height of viewbox, to avoid truncated reactangle.

Preview

SVG Bezier: Keyframe Animation: Multiple Bezier

Javascript: Skeleton

It is easier to break down into functions.

const blueScale = [...];

function getBezierParams(index) {...}
function getDPath(index) {...}
function getDPathVariant(index) {...}
function getBezierStyle(index) {...}

document.addEventListener(
  "DOMContentLoaded", function(event) {...});

Javascript: Material Color

The color array choice is your decision. You are free to modify.

const blueScale = [     '#E3F2FD',
  '#BBDEFB', '#90CAF9', '#64B5F6',
  '#42A5F5', '#2196F3', '#1E88E5',
  '#1976D2', '#1565C0', '#0D47A1'];

Javascript: Get Bezier Params

The function is similar to previous code. Except it return array instead of dictionary. Because we need more flexibility later.

function getBezierParams(index) {
  const i = index + 1;

  // Initialize base points
  // with destructuring style
  let [mx, my] = [0, 190];
  let [c1x, c2x, c3x] = [-10, 70, 120];
  let c1y, c2y, c3y;
  let [s1x, s1y, s2x, s2y] = [170, 230, 200, 220];

  // Defining variant points
  my  -= 20*i;
  c1x += 10*i;
  [c1y, c2y, c3y] = [my-10, my-10, my+10]
  s1y -= 20*i;
  s2y -= 20*i;

  return [
    mx, my,
    c1x, c1y, c2x, c2y, c3x, c3y,
    s1x, s1y, s2x, s2y];
}

Javascript: Get D Path

This is just getting the complete d property string, for use with both d from and d to.

function getDPath(index) {
  const [
    mx, my,
    c1x, c1y, c2x, c2y, c3x, c3y,
    s1x, s1y, s2x, s2y
  ] = getBezierParams(index);

  return `M ${mx} ${my}`
      + ` C ${c1x} ${c1y} ${c2x} ${c2y} ${c3x} ${c3y}`
      + ` S ${s1x} ${s1y} ${s2x} ${s2y}`
      + ` L 200 0 L 0 0`
      + ` z`;
}

Javascript: Get D Path Variant

This one is different. Although it is just getting the string, for use with d 50%.

I put flip effect inside the function. To get this flip effect, I require to compare two bezier line. So the result is longer.

The first bezier is shown in code below.

function getDPathVariant(index) {
  let [
    mx, my,
    c1x, c1y, c2x, c2y, c3x, c3y,
    s1x, s1y, s2x, s2y
  ] = getBezierParams(index+1);

  // shift
  c1x -= 20;
  c2x -= 20;
  c3x -= 20;
  s1x -= 20;

The second bezier and the flip calculation is shown in code below.

  // get flip baseline
  let [
    mx0, my0,
    c1x0, c1y0, c2x0, c2y0, c3x0, c3y0,
    s1x0, s1y0, s2x0, s2y0
  ] = getBezierParams(index);

  // flip
  c1y = 2*c1y + c1y0;
  c2y = 2*c2y + c2y0;
  c3y = 2*c2y + c3y0;

And finally this return the calculated d property string.

  // get the d string property
  return `M ${mx} ${my}`
      + ` C ${c1x} ${c1y} ${c2x} ${c2y} ${c3x} ${c3y}`
      + ` S ${s1x} ${s1y} ${s2x} ${s2y}`
      + ` L 200 0 L 0 0`
      + ` z`;
}

Javascript: Get Bezier Style

After we get the complete d property, we can properly write down the required stylesheet, for one bezier area.

function getBezierStyle(index) {
  const bezier_fromto = getDPath(index);
  const bezier_middle = getDPathVariant(index);
  const duration = 5 + index*3/10;

  const styles_kf = `
    @keyframes area_${index} {
      from { d: path("${bezier_fromto}"); }
      50%  { d: path("${bezier_middle}"); }
      to   { d: path("${bezier_fromto}"); }
    }
`
  const styles_id = `
    path#svg_area_${index} {
      fill: ${blueScale[index]};
      opacity: 0.5;
      animation: area_${index} ${duration}s ease infinite normal;
    }
`
  return styles_kf + styles_id;
}

Stylesheet: Opacity

To get better effect, I setup 50% transparency.

path#svg_area_${index} {
  opacity: 0.5;
}

Javascript: Entry Point

In program entry point such as DOM Content Loaded, we can gather all style sheet strings, of each bezier calculation in a loop.

document.addEventListener(
  "DOMContentLoaded", function(event) {
    const range = [...Array(10)];
    let styles;

    range.forEach((_, index) => {
      styles += getBezierStyle(index);
    });

    // Create style document
    ...
});

And put the stylesheet string in the head.

    // Create style document
    const css = document.createElement('style');
    css.type = 'text/css';
    css.appendChild(document.createTextNode(styles));
    document.getElementsByTagName("head")[0].appendChild(css);

The keyframes do all the hardworks. the javascript does not animate anything. And that is all.

Preview

The result is similar as below:

Our journey has complete here.

What is Next ?

Well. Not really. The journey depends on your creativity.

You can alter the color, parameter, points, and variant, so you change your animation to suit your needs.

I have a few ideas, with entirely different animation, and also the math behind bezier.

Sure, I love math.

But that is different stories. And also different long article series.


Conclusion

What do you think?

Thank you for reading.

Farewell. We shall meet again.