Skip to main content

The drifting snowfall animation as used on the Tesco homepage for Christmas 2015.

- (incept: )

Snow animation for 2015

A pure CSS solution for the festive season as used for Tesco Christmas, the Tesco homepage and store-locator too. This piece is a furtherance to Estelle Weyl's snow demo.

One issue outstanding in IE10, where only some flakes spin and drift, which is odd to say the least.


This is what a single row looks like:

Language HTML
<div id=flakes class=flakes>

  <!-- 1 row at 768px -->

  <!-- Common device widths -->
  <i class=snow-1024px></i><i class=snow-1024px></i><i class=snow-1024px></i>
  <i class=snow-1280px></i><i class=snow-1280px></i>
  <i class=snow-1366px></i><i class=snow-1366px></i>
  <i class=snow-1440px></i>
  <i class=snow-1600px></i><i class=snow-1600px></i>
  <i class=snow-1800px></i><i class=snow-1800px></i>
  <i class=snow-1920px></i><i class=snow-1920px></i>


To increase the number of flakes simply duplicate ALL <i> tags and increase .flakes offset height, and falling distance.

Disable flex while changing the quantities of snowflakes. Otherwise flex will resize the <i> tags to fit the available width.


For the sake of brevity I've excluded browser specific prefixes here. Though the stand-alone demo contains everything you need.

The snowflakes container

flex is used to space the <i> tags evenly across the horizontal plane. Playing nicely at in-between device widths. Be sure to temporarily disable flex if you are adjusting the number of flakes. 20vh is allocated per row of flakes. The falling animation would require adjustment too.

Hardware acceleration is encouraged via 3D transforms.

Language CSS
.flakes {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  text-align: center;
  z-index: -1;

  transform: translate(0, -20vh);
  transform: translate3d(0, -20vh, 0);

  justify-content: space-between;
  flex-wrap: wrap;

Cover the flakes container with the background color. That's in case low screen height allows the start position of the flakes to show through.

Language CSS
.flakes div {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: #fff;

Generic styling of the snowflakes, setting defaults for the animation and transforms. Again encouraging hardware acceleration where supported.

Language CSS
.flakes i {
  display: inline-block;

  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  perspective: 1000;
  animation: falling;
  animation-iteration-count: infinite;

  background-size: contain;
  background-repeat: no-repeat;
  backface-visibility: hidden;
  background-image: url("data:image/png;base64,…");

There are four flake sizes, each requires an individual origin set.

Language CSS
.flakes i:nth-child(4n+0) {
  width: 130px;
  height: 130px;
  transform-origin: -5% -5%;
.flakes i:nth-child(4n+1) {
  width: 97px;
  height: 97px;
  transform-origin: 15% 0;
.flakes i:nth-child(4n+2) {
  width: 65px;
  height: 65px;
  transform-origin: 0 -30%;
.flakes i:nth-child(4n+3) {
  width: 32px;
  height: 32px;
  transform-origin: -100% -100%;

Keep the first snowflake close to the left edge:

Language CSS
.flakes i:first-child {
  transform-origin: 60% 40%;

Delay the start times of the snowflake animations:

Language CSS
.flakes i:nth-of-type(5n+0) {
  animation-delay: 0s;
.flakes i:nth-of-type(5n+1) {
  animation-delay: 2s;
.flakes i:nth-of-type(5n+2) {
  animation-delay: 4s;
.flakes i:nth-of-type(5n+3) {
  animation-delay: 6s;
.flakes i:nth-of-type(5n+4) {
  animation-delay: 8s;

Animation durations are calculated to be multiples of each other +/- a few seconds for initial delays.

Language CSS
.flakes i:nth-child(3n+0) {
  animation-duration: 12s;
.flakes i:nth-child(3n+1) {
  animation-duration: 18s;
.flakes i:nth-child(3n+2) {
  animation-duration: 24s;

Tweak the timing functions to improve the randomness.

Language CSS
.flakes i:nth-of-type(6n+0) {
  animation-timing-function: ease-in-out;
.flakes i:nth-of-type(6n+1) {
  animation-timing-function: ease-out;
.flakes i:nth-of-type(6n+2) {
  animation-timing-function: ease;
.flakes i:nth-of-type(6n+3) {
  animation-timing-function: ease-in;
.flakes i:nth-of-type(6n+4) {
  animation-timing-function: linear;
.flakes i:nth-of-type(6n+5) {
  animation-timing-function: cubic-bezier(0.2, 0.3, 0.8, 0.9);

Tweak opacity for even more randomness.

Language CSS
.flakes i:nth-of-type(7n+0) {opacity: 0.5;}
.flakes i:nth-of-type(7n+1) {opacity: 0.8;}
.flakes i:nth-of-type(7n+2) {opacity: 0.3;}
.flakes i:nth-of-type(7n+4) {opacity: 0.7;}
.flakes i:nth-of-type(7n+6) {opacity: 0.6;}

Increase the number of snowflakes at common device widths. Remember that flex evenly distributes them across the horizontal.

Language CSS
.flakes .snow-1024px,
.flakes .snow-1280px,
.flakes .snow-1366px,
.flakes .snow-1440px,
.flakes .snow-1600px,
.flakes .snow-1800px,
.flakes .snow-1920px {
  display: none
@media screen and (max-width:  767px) {
  .flakes {display: none}
@media screen and (min-width: 1024px) {
  .flakes .snow-1024px {display: inline-block}
@media screen and (min-width: 1280px) {
  .flakes .snow-1280px {display: inline-block}
@media screen and (min-width: 1366px) {
  .flakes .snow-1366px {display: inline-block}
@media screen and (min-width: 1440px) {
  .flakes .snow-1440px {display: inline-block}
@media screen and (min-width: 1600px) {
  .flakes .snow-1600px {display: inline-block}
@media screen and (min-width: 1800px) {
  .flakes .snow-1800px {display: inline-block}
@media screen and (min-width: 1920px) {
  .flakes .snow-1920px {display: inline-block}

One simple animation function for all the snowflakes:

Language CSS
@keyframes falling {
  from {
      translate(0, 0)
  to {
      translate(0, 120vh)

Adding snowflakes dynamically

Need to get around fixed template limitations? JavaScript once again to the rescue, and it may as well late-load all the assets too.

Language JavaScript
(function () {
  "use strict";

  // Dynamically add snowflake HTML & CSS to the page upon page-load.

  function letItSnow() {
    var flakesDiv = document.createElement("div"),
        link = document.createElement("link"),
        html = "";

    // Fetch snow CSS:
    link.href = "snow.css";
    link.rel = "stylesheet";

    // Build HTML: = "flakes";
    flakesDiv.className = "flakes";

    // 1 row at 768px
    html += '<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>';

    // Common device widths
    html += '<i class=snow-1024px></i><i class=snow-1024px></i><i class=snow-1024px></i>';
    html += '<i class=snow-1280px></i><i class=snow-1280px></i>';
    html += '<i class=snow-1366px></i><i class=snow-1366px></i>';
    html += '<i class=snow-1440px></i>';
    html += '<i class=snow-1600px></i><i class=snow-1600px></i>';
    html += '<i class=snow-1800px></i><i class=snow-1800px></i>';
    html += '<i class=snow-1920px></i><i class=snow-1920px></i>';
    html += '<div></div>';

    // Add HTML
    flakesDiv.innerHTML = html;
    document.body.insertBefore(flakesDiv, document.body.childNodes[0]);

  // classList test to eliminate IE9 specifically
  if (window.addEventListener && document.documentElement.classList) {
    window.addEventListener("load", letItSnow, false);