Pure CSS: Bicyclist Animation

Cycling

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:

white bike

Process:

  1. Bike
  2. Bike Animation
  3. Girl
  4. 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.

cycling_mixin_wheel

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

mixin-back-front-wheels

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

cycling-html-test

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

cycling-html-test

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; 
}

The HTML for the above code is just this:

cycling-html-test2

Therefore, when looking at the generated CSS, alternative method #3) using Sass placeholder selector with @extend directive:

  1. Simplifies CSS selectors
  2. Keep properties DRY
  3. 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.

cycling_spokescycling_mixin_rotate

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.

cycling_generic_animation

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

cycling_cassette_rotation

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;

cycling_cassette

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:

cycling-move-neck

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.