Preface
Goal: Animating SVG using Keyframe in Stylesheet.
The idea is, we can rewrite,
the d attribute
as ‘css property`.
Preview
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
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.
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
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.