Create a gist now

Instantly share code, notes, and snippets.

@rgthree /_readme.md
Last active Aug 22, 2016

What would you like to do?
Google's iOS Gmail folding loading animation using CSS transitions & JS

NOTE

This has stopped working due to an odd timing issue with a minuscule -webkit-transition-duration value firing a webkitTransitionEnd

Checkout a pure CSS version here: https://github.com/rgthree/google-loader-css


Google/Gmail's "Folding Circle"

A recreation of Gmail's "Folding Circle" loading animation using CSS transitions & javascript.

Right now, it's built for webkit (using webkitTransitionEnd and -webkit- CSS properties). However, it should work for any browser capable of CSS Transitions, with the appropriates changes.

Whoa, how'd you do that?

Good question! The loader is constructed of a single element which has a :before and :after pseduo element blocks positioned at the left half, and the right half of the square element, each rounded to appear as a full circle. A single child element is then "folded" from the right to the left with a 3d animation. The right pseudo element is changed to the next color before the fold, and the single child element the same after the first half of the fold. After each fold the child element is positioned back to the right, the pseduo elements reflect their new color states, and the parent element is rotated to 90 degrees to switch between horizontal and vertical folds,

But wait, why not use CSS Animations and drop the JavaScript?

Ah yes, that would be great. Unfortunately, while it can be built this way the timing is not guarenteed. There are essentially three animations occuring:

  1. The folding and color change of the child
  2. The rotation of the parent element after each fold of the child
  3. The color change of the pseudo elements at each half of the child's fold

Because we cannot gurantee that the keyframes of individual CSS Animations match up, some awful flickering is likely to occur. Further, we cannot animate pseduo elements at this time (though, I believe Mozilla can at the time of this writing).

Therefore, we use CSS transitions with a JS listener for the end of each transition to trigger the state of the element through a data-state attribute. We use data-state and CSS3 attribute selectors to reuse transitions based on part of the attributes value.

Demo

This gist has been setup using jsfiddle to demo. See it here: http://jsfiddle.net/gh/gist/library/pure/4547132/

Enjoy!

.gloader {
display:inline-block; position:relative;
width:50px; height:50px; margin:10px;
font-size:0px;
}
.gloader:before,
.gloader:after,
.gloader > * {
content:' '; display:block; width:50%; height:100%;
position:absolute; top:0px; left:0px; z-index:1;
background:#21aa29; border-radius:25px 0px 0px 25px;
}
.gloader:after {
left:auto; right:0px;
background:#21aa29; border-radius:0px 25px 25px 0px;
}
.gloader > * {
position:absolute; top:0px; left:auto; right:0px; z-index:3;
background:#21aa29; border-radius:0px 25px 25px 0px;
-webkit-transform-style:preserve-3d;
-webkit-transform-origin:0px 50%;
-webkit-transition:all 0.33s 0s linear;
}
/* Generic Timing */
.gloader[data-state] > *,
.gloader[data-state$=".5"] > *{
z-index:10;
-webkit-transition-duration:0.0000001s;
-webkit-transition-timing-function:linear;
-webkit-transform:rotateY(0deg);
}
.gloader[data-state$=".5"] > * {
-webkit-transform:rotateY(-90deg);
}
.gloader[data-state$=".25"] > *,
.gloader[data-state$=".75"] > * {
z-index:11;
-webkit-transition-duration:0.33s;
-webkit-transition-timing-function:ease-in;
-webkit-transform:rotateY(-90deg);
}
.gloader[data-state$=".75"] > * {
-webkit-transition-timing-function:ease-out;
-webkit-transform:rotateY(-180deg);
}
/* Rotation - not animated */
.gloader[data-state^="0"],
.gloader[data-state^="2"] {
-webkit-transform:rotate(0deg);
}
.gloader[data-state^="1"],
.gloader[data-state^="3"] {
-webkit-transform:rotate(-90deg);
}
/*
Google Colors (Green -> Blue -> Red -> Yellow...):
Green: #21aa29 / #105514
Blue: #2159d6 / #102c6b
Red: #d62408 / #6b1204
Yellow: #ffcf00 / #7f6700
*/
/* Green -> Blue */
.gloader[data-state="0.75"] > *,
.gloader[data-state^="0"]:after {
background:#2159d6; /* Final */
}
.gloader[data-state="0"] > *,
.gloader[data-state^="0"]:before,
.gloader[data-state="0"]:after {
background:#21aa29; /* Start */
}
.gloader[data-state="0.25"] > * {
background:#105514; /* Start Middle */
}
.gloader[data-state="0.5"] > * {
background:#102c6b; /* End Middle */
}
/* Blue -> Red */
.gloader[data-state="1.75"] > *,
.gloader[data-state^="1"]:after {
background:#d62408; /* Final */
}
.gloader[data-state="1"] > *,
.gloader[data-state^="1"]:before,
.gloader[data-state="1"]:after {
background:#2159d6; /* Start */
}
.gloader[data-state="1.25"] > * {
background:#102c6b; /* Start Middle */
}
.gloader[data-state="1.5"] > * {
background:#6b1204; /* End Middle */
}
/* Red -> Yellow */
.gloader[data-state="2.75"] > *,
.gloader[data-state^="2"]:after {
background:#ffcf00; /* Final */
}
.gloader[data-state="2"] > *,
.gloader[data-state^="2"]:before,
.gloader[data-state="2"]:after {
background:#d62408; /* Start */
}
.gloader[data-state="2.25"] > * {
background:#6b1204; /* Start Middle */
}
.gloader[data-state="2.5"] > * {
background:#7f6700; /* End Middle */
}
/* Yellow -> Green */
.gloader[data-state="3.75"] > *,
.gloader[data-state^="3"]:after {
background:#21aa29; /* Final */
}
.gloader[data-state="3"] > *,
.gloader[data-state^="3"]:before,
.gloader[data-state="3"]:after {
background:#ffcf00; /* Start */
}
.gloader[data-state="3.25"] > * {
background:#7f6700; /* Start Middle */
}
.gloader[data-state="3.5"] > * {
background:#105514; /* End Middle */
}
<!-- can be any element w/ a single child -->
<span class="gloader"><span></span></span>
// Look for uninitialized gloader elements and start them up
function gloader(){
var els, i, l;
els = document.querySelectorAll('.gloader');
for(var i = 0, l = els.length; i < l; i++){
if(els[i].getAttribute('data-state') == null){
(function(el){
function inc(el){
var state = parseFloat(el.getAttribute('data-state'), 10)+.25;
if(state === 4){ state = 0; }
el.setAttribute('data-state', state);
};
el.addEventListener('webkitTransitionEnd', function(e){
// Because we're changing many different properties
// we need a constant property to check against.
// Arbitrarily chose z-index
if(e.propertyName === 'z-index'){ inc(el); }
}, false);
el.setAttribute('data-state', 0);
inc(el);
})(els[i]);
}
}
};
window.addEventListener('load', gloader);
name: Google's iOS Gmail loading animation
description: Google's iOS Gmail folding loading animation using CSS transitions & JS
authors:
- Regis Gaughan, III
resources:
normalize_css: yes

dakoder commented Feb 6, 2013

Absolutely splendid! Really great!

isyara commented Apr 19, 2013

Great !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment