Skip to content

Instantly share code, notes, and snippets.

@rgchris
Created March 25, 2026 12:46
Show Gist options
  • Select an option

  • Save rgchris/3d94601e5f4d8193a7cff85d40fd2bb5 to your computer and use it in GitHub Desktop.

Select an option

Save rgchris/3d94601e5f4d8193a7cff85d40fd2bb5 to your computer and use it in GitHub Desktop.
Whale (Animated SVG Example)

I came across this article, How Designers Should Think About SVG, and was taken by the little whale icon about a third of the way down showcasing the benefits of using SVG for animation. I noticed on clicking through to the CodePen editor that the animation portions were done in CSS. I was curious a) whether the animation could be done in pure SVG/SMIL, and b) whether my SVG module was up to the task of producing such a file in a streamlined fashion. Indeed they were, here is the result!

Some notes:

  • It's not well documented how the additive attribute is required to make multiple transform animations work on a single element. For each keyframe, additive="replace" is necessary to clear out any transform values active on the element, and additive="sum" is required on subsequent values so as not to clear out the prior one within the same keyframe.

  • The compounded values in the animate/animateTransform tags seem quite messy and further belie the idea that SVG is an XML format (it's a multitude of microformats wrapped in an XML envelope).

  • The keySplines attribute requires one less set of values than the values and keyTimes attributes. I'm not sure why, like the animation-timing-function CSS equivalent it can't extrapolate from one value.

  • The original CSS keyframes only required the intermediate stops (e.g. 50%) presumably taking the values for 0 and 100% from the initial attribute value(?). I wasn't able to get this to work, so I manually added these keyframes where needed. (I may be overlooking some way in which this and the additive attribute are supposed to work together.)

  • A general observation: I still don't feel that CSS and SVG go together all that well. SVG should have all the capabilities to do stuff like this without making it an SVG+CSS endeavour. In this case it does, though it can look awkward when pushed to the extreme.

  • I'm still developing the SVG module, though I believe some aspects are relatively intuitive. If it doesn't make creating SVG files in a more literate way that raw XML (or XML + CSS for that matter), then it needs further improvement.

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Rebol [
Title: "Whale (Animated SVG Example)"
Author: "Christopher Ross-Gill"
Type: private
Needs: [
r3:rgchris:svg
]
Comment: [
https://medium.com/sketch-app-sources/how-designers-should-think-about-svg-b2b92efc4d77
https://codepen.io/pnowell/pen/QyZVro
"Original Whale Animation"
]
]
assets: #[
comment: {
Monsieur Whale - Animating SVG Icon
This experiment uses ~CSS~ SVG Transforms and Animations on an SVG icon. These kinds
of techniques are presented in my icon design & SVG class on CreativeLive. To check
out the class or its resources, visit pnowell.com/resources
Animated and designed by: Peter Nowell
(note that the icon is currently enlarged for demonstration)
}
color: #3279fd
paths: #[
tail: {
M21.4 43.9
C20.9 45.4 19.7 48.9 16.7 50.6
13.6 52.4 11.8 51.7 8.3 52.1
14.2 46.3 12.2 43.3 14.9 40.8
12.5 39.2 14.4 37.8 13 34.3
14.9 34.4 18.5 32.9 19.7 33.9
20.9 34.9 20.5 36.3 21.1 38.3
}
body-lower-right: {
M78.5 46.1
C75.5 46.1 75.2 47.3 73.5 48
75.5 47.4 78 47.6 78 47.6
75.5 54.1 66.5 57.5 57.5 57.5
53.1 57.5 50.3 57 45.4 56.1
}
body-lower-left: {
M46.7 56.4
C37.7 54.8 28 51.7 20 47
}
body-top-right: {
M58.5 24.5
C73.5 24.5 77.5 34.5 77.5 39.5
77.5 41.5 77.5 45.1 78.5 46.1
}
body-top-left: {
M21 42
C34.5 42 41.5 24.5 58.5 24.5
}
smile: {
M69.5 47.5
C70.6 49.2 73 48.1 74 47.9
}
flipper: {
M52.5 48.6
C51.4 51.7 49.2 55.5 46.6 58.6
44 61.6 39.6 63 34.4 61.1
31.1 60 33.1 55.1 37.6 52.2
40 50.7 44.4 49.9 43 48.3
}
eye: #[center: 62.5x42.5 radius: 4]
pupil: #[center: 64.25x42.25 radius: 2.25]
blow-hole: {
M53 27.9
C53.8 26.9 55.5 27.4 56 26.9
}
]
sprays: [
#[
path: {
M54 27
C53 21 51 16.5 46.5 16.5
42.9 16.5 42.5 19.5 42.5 19.5
}
length: 19.570466995239258
initial: 19.57
short: 19.5708
long: 58.7125
start: 62%
end: 68%
show?: yes
]
#[
path: {
M53.5 27.5
L53.5 18
C53.5 8.5 51.5 4 47 4
44 4 42.5 6.5 42.5 8.5
42.5 10 43.5 12.5 46 12.5
48 12.5 48.5 11 48.5 10
48.5 7.5 46 7.5 45.5 8.5
}
length: 48.20001983642578
initial: 48.2
short: 48.2011
long: 144.6033
start: 60%
end: 70%
show?: yes
]
#[
path: {
M54.4 27.2
C54 15.5 56.5 3 48.5 0.5
}
length: 29.062421798706055
initial: 29.06
short: 29.0627
long: 87.1881
start: 55%
end: 65%
show?: yes
]
#[
path: {
M55.5 27
C55.5 27 56 15 56.5 10
57 5 59.5 1.5 63.5 1.5
67.5 1.5 68.5 3.5 68.5 3.5
}
length: 34.793601989746094
initial: 34.79
short: 34.7940
long: 104.382
start: 60%
end: 68%
show?: yes
]
#[
path: {
M55.5 27
C55.5 27 56.5 20 57.5 14.5
58.5 9 61 7.5 63.5 7.5
66 7.5 67 9 67 10.5
67 12 66 13.5 64.5 13.5
63 13.5 62.5 12.5 62.5 12
62.5 11 63 10.5 63.5 10.5
}
length: 37.06087112426758
initial: 37.06
short: 37.0617
long: 111.19
start: 56%
end: 72%
show?: yes
]
#[
path: {
M55.5 27
C55.5 21.5 62.5 11 70.5 19
}
length: 22.199106216430664
initial: 22.1995
short: 22.1995
long: 66.598500000000001
start: 56%
end: 70%
show?: yes
]
#[
path: {
M55.5 27
C55.5 21.5 61.5 18.5 66 21
}
length: 14.573626518249512
initial: 14.57
short: 14.5739
long: 43.7218
start: 62%
end: 68%
show?: yes
]
]
flipper: #[
up: #[
stroke-width: 1.2
scale: 1x0.7
]
down: #[
stroke-width: 1
scale: 1x1.2
]
little-up: #[
stroke-width: 1.1
scale: 1x0.85
]
frames: #[
0% up
10% down
20% up
26% down
27% little-up
29% down
30% little-up
31% down
33% little-up
34% down
36% little-up
37% down
39% little-up
40% down
41% little-up
43% down
45% little-up
49% down
52% little-up
55% down
60% up
70% down
80% up
90% down
100% up
]
]
splines: [
.6 0 .4 1
]
]
drawing: svg/create 320x320 [
comment join newline trim/auto assets/comment
group #[
transform: [scale 4x4]
fill: none
stroke: (assets/color)
; class: "whale-container"
stroke-linecap: round
stroke-linejoin: round
][
animate group _ [
; Tail
;
animate path #[
transform-origin: 14.835x42.795
transform: [skewY -15 translate -.4 2.5 rotate 36]
]
assets/paths/tail
[
keyframes-for 'skewY 4 [
begin 1.5
splines assets/splines
at 0 -15
at 50% 20
at 100% -15
]
keyframes-for 'translate 4 [
begin 1.5
splines assets/splines
at 0 [-.4 2.5]
at 50% [.8 -4.3]
at 100% [-.4 2.5]
]
keyframes-for 'rotate 4 [
begin 1.5
splines assets/splines
at 0 36
at 50% 28
at 100% 36
]
]
; Main Body
;
path _ assets/paths/body-lower-right
animate path #[
transform-origin: 46.74x56.36
transform: [rotate -3]
]
assets/paths/body-lower-left
[
keyframes-for 'rotate 4 [
begin 1.5
splines assets/splines
at 0 -3
at 50% 5
at 1 -3
]
]
path _ assets/paths/body-top-right
animate path #[
transform-origin: 58.5x24.5
transform: [rotate -3]
]
assets/paths/body-top-left
[
keyframes-for 'rotate 4 [
begin 1.5
splines assets/splines
at 0 -3
at 50% 2.5
at 1 -3
]
keyframes-for 'scale 4 [
begin 1.5
splines assets/splines
at 0 [1 1]
at 50% [.92 1]
at 1 [1 1]
]
]
path _ assets/paths/smile
; Flipper
;
animate path #[
fill: #fff
transform-origin: 48.544x48.32
stroke-width: 1.2
]
assets/paths/flipper
[
for-each facet [stroke-width scale] [
keyframes-for :facet 12 [
for-each [time value] assets/flipper/frames [
at time assets/flipper/:value/:facet
]
]
]
]
; Eye
;
circle _ assets/paths/eye/center assets/paths/eye/radius
animate circle #[
fill: (assets/color)
stroke: none
transform-origin: 62.5x42.5
]
assets/paths/pupil/center
assets/paths/pupil/radius
[
keyframes-for 'rotate 12 [
splines assets/splines
at 0% 5
at 15% 5
at 25% 100
at 55% 10
at 60% -40
at 70% -50
at 85% 20
at 100% 5
]
]
; Blow Hole
;
path _ assets/paths/blow-hole
][
keyframes-for 'translate 12 [
splines assets/splines
at 0 [0 0]
at 30% [0 10]
at 60% [0 0]
at 80% [0 4]
at 100% [0 0]
]
]
; Spray
;
group _ [
for-each spray assets/sprays [
if spray/show? [
animate path #[
stroke-dasharray: (spray/initial)
stroke-dashoffset: (spray/long)
]
spray/path
[
keyframes-for 'stroke-dashoffset 12 [
at 0 spray/long
at spray/start spray/long
at spray/end spray/short
at 100% spray/short
]
]
]
]
]
]
]
encoding: svg/encode/pretty/precise drawing 0.0001
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment