A loading spinner that draws an SVG triangle and then animates it to a dodecagon and back.
Created
June 19, 2015 03:09
-
-
Save srajagop/5912c59ffb208ebaee57 to your computer and use it in GitHub Desktop.
Polygon spinner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id='container'> | |
<svg class='spinner' version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100px" height="100px" viewBox="0 0 100 100"> | |
<svg preserveAspectRatio="xMidYMid meet" viewBox="0 0 100 100" width="100%" height="100%"> | |
<polygon id='polygon' /> | |
</svg> | |
</svg> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Spinner = function (el) { | |
this.svg = el, | |
this.timing = .3, | |
this.direction = 0, | |
this.timelineEnd = 0, // can be used to end the spinner. | |
this.timeline = new TimelineMax({ // initiate the timeline object | |
paused: true, // paused at start so the tween steps can be added. it gets started at the very end | |
ease: Bounce.easeOut, | |
yoyo: true, | |
repeat: -1, | |
onStart: this.startRepeat.bind(this), | |
onRepeat: this.startRepeat.bind(this) | |
}); | |
this.polygon = new Polygon({ el: this.svg.querySelector('#polygon'), parent: this.svg }); | |
this.setupTimeline(); | |
this.timeline.play(); | |
}; | |
Spinner.prototype.startRepeat = function (el) { | |
if (this.direction) { | |
TweenMax.to(this.polygon.el, this.timing * this.polygon.maxSides * 1.05, { rotation: '10deg', ease: Cubic.easeInOut }); | |
} else { | |
TweenMax.to(this.polygon.el, this.timing * this.polygon.maxSides, { rotation: '370deg', ease: Cubic.easeInOut }); | |
} | |
this.direction = !this.direction; | |
}; | |
Spinner.prototype.applyUpdates = function (tween, sides) { | |
var points = []; | |
for (var j = 0; j <= sides; j++) { | |
points.push(tween.target['x' + j] + ',' + tween.target['y' + j]); | |
} | |
this.polygon.el.setAttribute('points', points.join(' ')); | |
}; | |
Spinner.prototype.setupTimeline = function () { | |
for (var i = this.polygon.sides - 1; i < this.polygon.maxSides; i++) { | |
var finish = this.polygon.setSides(i + 1), | |
_sides = i; | |
this.timeline.to(this.polygon.points, this.timing, _.extend(finish, { | |
ease: Cubic.easeOut, | |
onUpdateParams: ["{self}", _sides], | |
onUpdate: this.applyUpdates.bind(this) | |
})); | |
} | |
// enables the spinner to hide itself at the end of a cycle | |
this.timeline.call(this.fadeOut.bind(this)); | |
}; | |
Spinner.prototype.fadeOut = function () { | |
if (this.timelineEnd) { | |
this.timeline.pause(); | |
TweenMax.to(this.svg, .4, { | |
bezier: { | |
curviness: 1.25, | |
values: [ | |
{ scale: 1.2, opacity: 1 }, | |
{ scale: .8, opacity: .5 }, | |
{ scale: .2, opacity: .1 }, | |
{ scale: 0, opacity: 0 } | |
] | |
} | |
}) | |
} | |
}; | |
// used to kill the spinner when something finished loading. | |
// `timelineEnd` is checked at the end of every animation cycle | |
// via the `fadeOut` method above | |
Spinner.prototype.finish = function () { | |
this.timelineEnd = 1; | |
}; | |
var Polygon = function (opts) { | |
this.el = opts.el; | |
this.parent = opts.parent; | |
this.setupPolygon(); | |
}; | |
Polygon.prototype.setupPolygon = function () { | |
// setup size properties | |
this.size = this.parent.getBoundingClientRect().width, | |
this.center = this.size / 6, | |
this.radius = this.center | |
this.sides = 1, //beginning number of sides. we start with a triangle | |
this.maxSides = 12, // end with a dodecagon | |
this.points = { // defines the initial triangle area | |
x0: 0, y0: 60, x1: 51, y1: -31, x2: -52, y2: -32, x3: -52,y3: -32 | |
}; | |
TweenMax.set(this.el, { | |
transformOrigin: 'center center', | |
x: this.size / 2, | |
y: this.size / 2 | |
}); | |
this.points = this.fillPoints(this.points, this.sides); | |
}; | |
Polygon.prototype.setSides = function (sides) { | |
var points = {}, | |
angle = 2 * Math.PI / sides, | |
i = 0; // keep track of `i` outside of for loop so we can easily pass it to `fillPoints` | |
for (i; i < sides; i++) { | |
var _angle = i * angle, | |
_radius = this.center + this.radius, | |
x = _radius * Math.sin(_angle), | |
y = _radius * Math.cos(_angle); | |
points['x' + i] = Math.floor(x); | |
points['y' + i] = Math.floor(y); | |
} | |
return this.fillPoints(points, i); | |
}; | |
Polygon.prototype.fillPoints = function (points, i) { | |
// fill in remaining values to be the same as the last point | |
// so they dont appear on screen | |
var x = points['x' + (i - 1)], | |
y = points['y' + (i - 1)]; | |
for (i; i < this.maxSides; i++) { | |
points['x' + i] = x; | |
points['y' + i] = y; | |
} | |
return points; | |
}; | |
var spinner = new Spinner(document.querySelector('.spinner')); | |
// uncomment to demo the fading. in real life this would be | |
// called on some event, e.g. something finished loading | |
/* | |
setTimeout( function () { | |
spinner.finish(); | |
}, 6000) | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.16.1/TweenMax.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore-min.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html,body,#container { | |
background: #333; | |
color: #fafafa; | |
height: 100%; | |
} | |
#container { | |
-webkit-transform-style: preserve-3d; | |
-moz-transform-style: preserve-3d; | |
transform-style: preserve-3d; | |
text-align: center; | |
} | |
svg polygon { | |
stroke: #fafafa; | |
fill: transparent; | |
stroke-width: 2; | |
} | |
svg { | |
margin: 0 auto; | |
position: relative; | |
top: 50%; | |
-webkit-transform: translateY(-50%); | |
-ms-transform: translateY(-50%); | |
transform: translateY(-50%); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment