
See Codepen
My first pure CSS animation! A loose HTML and CSS translation of Dribble shot “Cycling” by Moncho Massé. I say ‘loose’ because it wasn’t my original intention to do a graphic art to HTML/CSS translation — just wanted to use the color palette (which is so warm and gorgeous). Eventually decided to use the original work as replication practice. Based the wheels off of this flat illustration:

Process:
- Bike
- Bike Animation
- Girl
- Girl Animation
1. Bike
I started off with the bike, so that that the girl could later sit on something visible. The stationary but spinning wheels were also a great deal less intimidating to start off with than the moving legs.
a) Wheel
The wheel was easy to make — a circle with background: none , with a border that acted as the tire, and box-shadow: inset as the inner lining of the tire.
CSS Syntax
box-shadow: h-shadow v-shadow blur spread-color
Things to note about box-shadow:
- when all values are 0px, the shadow cannot be seen because it is directly behind its element
- if the element has a border, the box-shadow is contained within this space, regardless of h-shadow or v-shadow values
NEW: Sass
So I learned the basics of Sass with Dan Cederholm’s “Sass for Web Designers,” and implemented what I learned for the first time in this project. Seeing that two wheels are needed, I created a mixin for the properties that both wheel elements would share.

That left the class selectors ‘back-wheel’ and ‘front-wheel’, whose only difference was the value of the left position property.

At the time I was just excited to use my newly acquired knowledge of Sass. Looking back at this, I would not have used @mixin.
The above SCSS code would have compiled to the following in CSS:
.wheel {
position: absolute;
border-radius:50%;
width: 200px;
height: 200px;
background:black;
border: 12px solid $wheel;
box-shadow: inset 20px 500px 0px 10px $wheel-lining;
top: 360px;
z-index: 2;
}
.back-wheel {
position: absolute;
border-radius:50%;
width: 200px;
height: 200px;
background:black;
border: 12px solid $wheel;
box-shadow: inset 20px 500px 0px 10px $wheel-lining;
top: 360px;
z-index: 2;
left: 20px;
}
.front-wheel {
position: absolute;
border-radius:50%;
width: 200px;
height: 200px;
background:black;
border: 12px solid $wheel;
box-shadow: inset 20px 500px 0px 10px $wheel-lining;
top: 360px;
z-index: 2;
left: 440px;
}
This generated CSS is not following the DRY principle. If mixins are used, the styles in them are duplicated all over the classes.
Alternative methods:
1) I could have used another class named ‘wheel’ in place of the mixin:
HTML

SCSS
.wheel {
position: absolute;
border-radius:50%;
width: 200px;
height: 200px;
background:black;
border: 12px solid $wheel;
box-shadow: inset 20px 500px 0px 10px $wheel-lining;
top: 360px;
z-index: 2;
}
.back-wheel {
left: 20px;
}
.front-wheel {
left: 440px;
}
2) I could have also used the @extend directive to share CSS properties between these selectors:
HTML

SCSS
.wheel {
position: absolute;
border-radius:50%;
width: 200px;
height: 200px;
background:black;
border: 12px solid $wheel;
box-shadow: inset 20px 500px 0px 10px $wheel-lining;
top: 360px;
z-index: 2;
}
.back-wheel {
@extend .wheel;
left: 20px;
}
.front-wheel {|
@extend .wheel;
left: 440px;
}
Which compiles to the following in CSS:
.wheel, .back-wheel, .front-wheel {
position: absolute;
border-radius:50%;
width: 200px;
height: 200px;
background:black;
border: 12px solid $wheel;
box-shadow: inset 20px 500px 0px 10px $wheel-lining;
top: 360px;
z-index: 2;
}
.back-wheel {
left: 20px;
}
.front-wheel {
left: 440px;
}
Which brings us to the question — is alternative method #1 or #2 better in this scenario?
Oh wait, newly in: 3) Using Sass placeholder selectors with the @extend directive:
%wheel {
position: absolute;
border-radius:50%;
width: 200px;
height: 200px;
background:black;
border: 12px solid $wheel;
box-shadow: inset 20px 500px 0px 10px $wheel-lining;
top: 360px;
z-index: 2;
}
.back-wheel {
@extend %wheel;
left: 20px;
}
.front-wheel {
@extend %wheel;
left: 440px;
}
Which compiles to the following in CSS:
.back-wheel, .front-wheel {
position: absolute;
border-radius:50%;
width: 200px;
height: 200px;
background:black;
border: 12px solid $wheel;
box-shadow: inset 20px 500px 0px 10px $wheel-lining;
top: 360px;
z-index: 2;
}
.back-wheel {
left: 20px;
}
.front-wheel {
left: 440px;
}
- Placeholder selectors do not show up in the generated CSS!
- The placeholder selector does not have to be an existing class within HTML.
The HTML for the above code is just this:

Therefore, when looking at the generated CSS, alternative method #3) using Sass placeholder selector with @extend directive:
- Simplifies CSS selectors
- Keep properties DRY
- Reduce the class attributes used in HTML
Which seems like the best method for better maintainability of the code.
With regards to better overall performance of CSS (and thus the web page?) — that, I am unsure of. Intuitively I’d yes, there could be a positive effect on performance, but I’m not knowledgeable enough yet to say how and why that would be so. {CHECK}
b) Spokes
Modeled the spokes orientation after the numbers on a clock-face. So the class named ‘_12-6’ is the vertical spoke right in the center of the wheel. Needed 6 spokes in total.
As for spacing between spokes: 360 degrees (of a circle) divided by 12 (hours on a clock) equals 30 degrees (between each hour on the clock-face).
Used the @include directive to reference @mixin rotate($deg). I almost reached the same mixin, but was missing the # symbol and {} braces. The generic transform mixin I found online.


The property background: none gave the illusion of seeing through the spokes of the wheel to the background.
c) Bike body frame, handlebar, seat, hub, chains, crankset, crank
Just rotating and positioning into place.
2. Bike Animation
a) Cassette & Spokes
I made the cassette (cluster of sprockets on the rear hub) the parent div of all the spokes divs. This way, all I need to rotate is the cassette itself, to give the impression of spinning wheels.
The transform-origin of the cassette is the default 50% 50%, smack-dab in its center, where the spokes’ convergence point is fixed.
Used a mixin for the @keyframes rule (found online) to specify the animation code.

{CHECK} Why the # symbol before the animation-name and str?

NOTE: Used the value 359 and not 360 because degrees in rotate() can only take values in this range: [0, 360).
To go clockwise (because the bicycle is supposed to be moving from left to right), a negative value (-359 degrees) was used as the starting property value.
Animation shorthand – CSS Syntax:
animation: name duration timing-function delay iteration-count direction fill-mode play-state;

In order, here I used:
- rotation –> animation-name (specifies the name of the keyframe to bind to the selector)
- 1s –> animation-duration (specifies how many seconds an animation takes to complete)
- infinite –> animation-iteration-count (specifies how many times an animation should be played)
- linear –> animation-timing-function (specifies the speed curve of the animation); linear specifies an animation with the same speed from start to end
So the order in the given syntax doesn’t matter, unless when using both duration and delay.
b) Crank
Had some trouble with transform-origin initially. For some reason, the crank was rotating somewhere way above the crankset. Re-positioned and set transform-origin to ‘bottom center’. Could have done ‘bottom left’, and the result would be the same.
In the beginning, I made the pedal a child div of crank, and had it rotating with the crank. But the pedal was supposed to be fixed in its original position while rotating. In later iterations, the pedal became a child div of shoe, which allowed it to keep its original orientation while rotating. Yes, a hack, I know.
3. Girl
Pelvic region
Started off with the pelvic region, so that there was something digitally-tangible sitting on top of the bike seat.
Face
Would like to point out that the eyes have background: none, but box-shadow: inset so the curved eye effect exists. Without inset, the curve would be flipped to the bottom of the no-background element.
Torso
Made the right arm div a child element of the chest div. Wanted to make the left arm a child element of chest div as well, but it needed to be behind the chest. A child element would always appear above the parent element, so I had to make a separate div for the left arm, give it a lower z-index than the chest div, and position it appropriately.
In retrospect, should have made the belly div a child element of chest as well. Same goes for neck (either a child element of chest or head).
Making child elements like this will make it easier to animate — just animate the parent div to animate the children divs too.
Legs
Similarly, the calves became child elements of the thighs.
4. Girl Animation
The most time-consuming task of all.
After eyeballing the original animation for a very long time, motion could be broken down into:
a) Thighs
Rotating about ~40-60 degrees continuously, at alternating times to each other. The fixed point of rotation (transform-origin) is on the ‘pelvic region’ div. The keyframes values were eventually settled through a lot of trial-and-error.
b) Calves
In the beginning, the calves were not moving on their own, just using motion from the thighs.
After a lot of testing, the movement for the right calf was deconstructed to four parts: starting at the ‘rest’ position, followed by a decrease in degrees (to rotate counter-clockwise; to kick out), the ‘rest’ position again, and an increase in degrees (to rotate clockwise; to kick back).
Again, plenty of trial-and-error and fiddling around with the degree values.
Still haven’t gotten a perfect circular movement.
c) Pelvic region, torso, neck, head
Issue: Multiple transforms i.e. left arm, neck, belly, were all rotated prior to translation. Applying translation after the rotation made these elements revert back to their original orientation before rotation.
For example, I could not do this:
.l-arm {
@include rotate(20);
@include transform(2, 3);
}
When there are multiple transform directives, only the last one will be applied.
Without mixins, multiple transforms could be separated with commas i.e.
.l-arm {
transform: translate(1, 2), rotate(50deg);
}
Couldn’t find an already existing mixin for multiple transforms that worked (and couldn’t develop one that worked) YET; for the sake of saving time, resorted to this:

An eyesore, but functional.
d) Hair, ground streaks, wind
Used transform: translation. Need to make smoother. Use transitions?
New things learned
- On my search for dealing with multiple transforms and animations, I discovered that behind all transforms are browser-translated matrices! Yay linear algebra! Need to read further.
- Used Sass (SCSS syntax) for the first time — variables, mixins, @include directive, @extend directive, placeholder selectors. Really basic. Continue learning more about Sass.
- CSS3 animations
To be improved
- Circular motion of calves — still not a perfect 360 degree rotation
- Make the crank in full — connect the crank to the pedal for both feet
- Position the calf on the knee so that when animating, no bumps are seen on the knees
- Smoother animation for wind movement — transitions?
- Create and/or find multiple transforms and multiple animations mixins
- Closer look at keyframes timelines
- Keep track of z-indices
TBC.














