Skip to content

Instantly share code, notes, and snippets.

@tomsseisums
Created July 2, 2015 11:00
Show Gist options
  • Select an option

  • Save tomsseisums/ad7ba49684549b6674d6 to your computer and use it in GitHub Desktop.

Select an option

Save tomsseisums/ad7ba49684549b6674d6 to your computer and use it in GitHub Desktop.
snap.svg, GSAP progress meters for javascript, preset for bar and donut (circle)
var donut = new ProgressMeter.Donut({
fat : 20,
}, {
progress : Math.random() * 100,
keyframes : function(timeline, style)
{
timeline.fromTo(style.meter, 0.5, { colorProps : { fill : 'lime' } }, { colorProps : { fill : 'orange' } }, 0);
timeline.to(style.meter, 0.5, { colorProps : { fill : 'red' } }, 0.5);
},
complete : function()
{
this.animate(Math.random() * 100);
},
duration : 5000
}, document.getElementById('cpu'));
donut.animate(Math.random() * 100);
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<figure id="cpu" class="progressmeter donut"></figure>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.9.3/lodash.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/latest/plugins/ColorPropsPlugin.min.js"></script>
<!--<script src="//cdnjs.cloudflare.com/ajax/libs/snap.svg/0.3.0/snap.svg-min.js"></script>-->
<script src="snapsvg.min.js"></script>
<script src="plugin.js"></script>
<script src="app.js"></script>
</body>
</html>
(function(global, lodash, snap, timeline, easing)
{
console.info("Loading ProgressMeter v0.0.1-alpha");
// Generators.
var generators =
{
bar : function(progress, fat)
{
progress = Math.max(0, Math.min(progress, 100));
return [
// Top left corner.
['M', 0, 0],
// Top right corner.
['L', progress, 0],
// Bottom right corner.
['L', progress, fat],
// Bottom left corner.
['L', 0, fat],
// Up to top left.
['L', 0, 0],
// Close path.
['z']
];
},
donut : function(progress, fat, offset)
{
progress = Math.max(0, Math.min(progress, 100));
offset = offset || 0;
var center = 50;
var innerRadius = center - fat - offset;
var outerRadius = center - offset;
var alpha = 360 / 100 * progress;
var a = (90 - alpha) * Math.PI / -180;
var innerX = center + innerRadius * Math.cos(a);
var innerY = center + innerRadius * Math.sin(a);
var outerX = center + outerRadius * Math.cos(a);
var outerY = center + outerRadius * Math.sin(a);
var path;
if (progress === 100)
{
path = [
// move to outer ring start
[
"M",
center,
center - outerRadius
],
// arc around the clock
[
"A",
outerRadius,
outerRadius,
0,
+(alpha > 180),
1,
outerX - .1,
outerY
],
// connect
[
"z"
],
// move to inner circle start
[
"M",
innerX,
innerY
],
// arc around the clock
[
"A",
innerRadius,
innerRadius,
0,
+(alpha > 180),
0,
innerX + .1,
innerY
],
// and connect
[
"z"
]
];
}
else
{
path = [
// move to start point of inner ring
[
"M",
center,
center - innerRadius
],
// draw a line to outer ring
[
"L",
center,
center - outerRadius
],
// arc to outer ring end
[
"A",
outerRadius,
outerRadius,
0,
+(alpha > 180),
1,
outerX,
outerY
],
// move to inner ring end
[
"L",
innerX,
innerY
],
// arc to inner ring start
[
"A",
innerRadius,
innerRadius,
0,
+(alpha > 180),
0,
center,
center - innerRadius
],
[
"z"
]
];
}
return path;
}
};
var inherit = function(target, source)
{
target.prototype = new source();
target.prototype.constructor = target;
return target.prototype;
};
var implement = function(target, implementation)
{
return lodash.assign(target, implementation);
};
var Shape = function(style, options, appendTo)
{
// Throw a better error if progress meter is not initialized with
// the new keyword.
if (!(this instanceof Shape))
{
throw new Error("Constructor was called without new keyword.");
}
// Prevent calling constructor wihtout parameters so inheritance
// works correctly.
if (arguments.length === 0) return;
// Optional argument switching.
if (options instanceof Node)
{
appendTo = options;
options = {};
}
// Set up options.
this.options = lodash.merge({
style :
{
fat : 1,
trail :
{
fill : 'gray'
},
meter :
{
fill : 'yellow'
}
},
duration : 1000,
easing : easing.map.linear,
progress : 0,
keyframes : function(timeline, style) {},
environment : function() {},
update : function() {},
complete : function() {}
}, this.specialOptions, options, { style : style });
// Save defaults.
this.defaults = lodash.assign({}, this.options);
// Initialize container.
this.makeContainer();
this.personalizeContainer();
// Initialize snap.svg.
this.snap = snap(this.container);
// Environment callback.
this.options.environment.call(this);
// Create meter elements.
this.makeMeter();
// Create timeline.
this.createTimeline();
// Set the initial value.
this.set(this.defaults.progress);
// Append the element, if needed.
if (appendTo && appendTo instanceof HTMLElement) appendTo.appendChild(this.container);
// Add the class.
this.container.classList.add('progressmeter');
};
implement(Shape.prototype,
{
specialOptions : {},
generator : function() { return ''; },
generatorSpecialArguments : function() { return []; },
makeContainer : function()
{
this.container = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
},
personalizeContainer : function()
{
var viewBox = [0, 0, 100, 100].join(' ');
this.container.setAttribute('viewBox', viewBox);
},
generate : function(progress)
{
progress = progress || this.options.progress;
var specArgs = this.generatorSpecialArguments();
var generatorArguments = [progress, this.options.style.fat].concat(specArgs);
return this.generator.apply(null, generatorArguments);
},
makeMeter : function()
{
// Create tail object if needed.
this.trail = null;
if (this.options.trail !== null)
{
this.trail = this.snap.path(this.generate(100));
this.trail.attr(this.options.style.trail);
}
// Create actual meter.
this.meter = this.snap.path(this.generate(0));
this.meter.attr(this.options.style.meter);
},
createTimeline : function()
{
var self = this;
this.timeline = new timeline({
paused : true,
onUpdate : this.update.bind(this),
});
// Call the callback, to initialize frames.
this.options.keyframes.call(this, this.timeline, this.options.style);
// Add progress change to timeline.
this.timeline.fromTo(this.options, 1, { progress : 0 }, { progress: 100, ease : this.options.easing }, 0);
this.timeline.totalDuration(this.options.duration / 1000);
},
set : function(progress)
{
// console.log('set', progress);
var percentage = progress / 100;
this.timeline.progress(percentage);
},
animate : function(progress, options)
{
var percentage = progress / 100;
options = options || {};
// Get total duration.
var currentTimeScale = this.timeline.timeScale();
var totalDuration = this.timeline.totalDuration();
var currentScaledTotalDuration = totalDuration / currentTimeScale;
// Calculate target duration.
var targetDuration = totalDuration * percentage;
var duration = options.duration || null;
if (duration !== null)
{
// Remove from options.
delete options.duration;
// Secondize.
duration /= 1000;
// If duration is specified, we need to change the timescale
// so that the progress change animation lives the given amount.
var currentPercentage = this.options.progress / 100;
var percentageDifference = Math.max(currentPercentage, percentage) - Math.min(currentPercentage, percentage);
var newTotalDuration = duration / percentageDifference;
this.timeline.totalDuration(newTotalDuration);
this.timeline.tweenTo(targetDuration, lodash.assign({
onComplete : function()
{
// Reset duration back to initial.
this.timeline.totalDuration(currentScaledTotalDuration);
this.options.complete.bind(this)();
}.bind(this)
}, options));
}
else
{
this.timeline.tweenTo(targetDuration, lodash.assign({
onComplete : this.options.complete.bind(this)
}, options));
}
},
redraw : function()
{
// Path is added first, so it can be overwritten if there is need for it.
// Redraw tail, if any.
if (this.trail)
{
var trailOptions = lodash.merge({
path : this.generate(100)
}, this.options.style.trail);
this.trail.attr(trailOptions);
}
// Redraw meter.
var meterOptions = lodash.merge({
path : this.generate()
}, this.options.style.meter);
this.meter.attr(meterOptions);
},
update : function()
{
// Redraw.
this.redraw();
// Call callback.
this.options.update.call(this);
}
});
// Bar.
var Bar = function(options, appendTo)
{
// Call super constructor.
Shape.apply(this, arguments);
this.container.classList.addClass('bar');
};
implement(inherit(Bar, Shape),
{
generator : generators.bar,
personalizeContainer : function()
{
var viewBox = [0, 0, 100, this.options.style.fat].join(' ');
this.container.setAttribute('viewBox', viewBox);
this.container.setAttribute('preserveAspectRatio', 'none');
}
});
// Donut.
var Donut = function(options, appendTo)
{
// Call super constructor.
Shape.apply(this, arguments);
this.container.classList.add('donut');
};
implement(inherit(Donut, Shape),
{
specialOptions : {
offset : 0
},
generator : generators.donut,
generatorSpecialArguments : function()
{
return [this.options.style.offset];
}
});
lodash.assign(global,
{
ProgressMeter :
{
Bar : Bar,
Donut : Donut
}
});
})(window, _, Snap, TimelineMax, Ease);
figure.progressmeter.donut
{
position: relative;
}
figure.progressmeter.donut svg
{
display: block;
margin: 0;
}
figure.progressmeter.donut::before, figure.progressmeter.donut::after
{
content: "";
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
border-radius: 100%;
/*border: solid 5px black;*/
box-shadow: 0px 0px 5px black;
box-sizing: border-box;
}
figure.progressmeter.donut::after
{
width: 60%;
height: 60%;
left: 20%;
top: 20%;
box-shadow: inset 0px 0px 5px black;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment