Skip to content

Instantly share code, notes, and snippets.

@wassname
Last active February 23, 2016 03:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wassname/9afec1bfa3fe5914d15a to your computer and use it in GitHub Desktop.
Save wassname/9afec1bfa3fe5914d15a to your computer and use it in GitHub Desktop.
jQuery plugin to generate flame on a canvas element. Demo: http://codepen.io/wassname/pen/KVOKNe
/**
* @name Flame
* @description jquery plugin to generate flame on a canvas element.
* Based on http://codepen.io/jackrugile/pen/Jbnpv
* @copyright (c) 2015 wassname
* @license: MIT
*/
;(function ( $, window, document, undefined ) {
var pluginName = "flame";
var defaults = {
hueRange: 50,
};
var defaultState = {
parts: [],
partCount: 200,
partsFull: false,
on: true,
rendering: false,
globalTick: 0,
width: null,
height: null,
}
var Part = function(flame){
this.flame=flame;
this.ctx=this.flame.ctx;
this.reset();
};
Part.prototype.reset = function(){
this.startRadius = this.rand(1, 25);
this.radius = this.startRadius;
this.width = this.flame.state.width;
this.height = this.flame.state.height;
this.x = this.width/2 + (this.rand(0, 6) - 3);
this.y = 250;
this.vx = 0;
this.vy = 0;
this.hue = this.rand(this.flame.state.globalTick - this.flame.options.hueRange, this.flame.state.globalTick + this.flame.options.hueRange);
this.saturation = this.rand(50, 100);
this.lightness = this.rand(20, 70);
this.startAlpha = this.rand(1, 10) / 100;
this.alpha = this.startAlpha;
this.decayRate = 0.1;
this.startLife = 7;
this.life = this.startLife;
this.lineWidth = this.rand(1, 3);
}
Part.prototype.update = function(){
this.vx += (this.rand(0, 200) - 100) / 1500;
this.vy -= this.life/50;
this.x += this.vx;
this.y += this.vy;
this.alpha = this.startAlpha * (this.life / this.startLife);
this.radius = this.startRadius * (this.life / this.startLife);
this.life -= this.decayRate;
if(
this.x > this.width + this.radius ||
this.x < -this.radius ||
this.y > this.height + this.radius ||
this.y < -this.radius ||
this.life <= this.decayRate
){
this.reset();
}
};
Part.prototype.render = function(){
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
this.ctx.fillStyle = this.ctx.strokeStyle = 'hsla('+this.hue+', '+this.saturation+'%, '+this.lightness+'%, '+this.alpha+')';
this.ctx.lineWidth = this.lineWidth;
this.ctx.fill();
this.ctx.stroke();
};
Part.prototype.rand = function(min, max){
return Math.floor( (Math.random() * (max - min + 1) ) + min);
};
var Flame = function(element, options){
this.element = element;
this.options = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this.init();
};
Flame.prototype.init = function (arguments) {
if(this.element.getContext) {
this.ctx = this.element.getContext("2d");
} else {
return;
}
if (this.state) this.stop();
this.state = $.extend( {parts: [],partsFull: false,globalTick: 0,}, defaultState, this.state);
// this.state = {
// parts: [],
// partCount: 200,
// partsFull: false,
// on: true,
// rendering: false,
// globalTick: 0,
// width: null,
// height: null,
// }
this.state.width = $(this.element).outerWidth();
this.state.height = $(this.element).outerWidth();
this.start();
}
/** If needed: $(window).on('resize',this.onResize.bind(this) **/
Flame.prototype.onResize = function (arguments) {
this.init();
}
Flame.prototype.createParts = function(){
if(this.state.parts.length > this.state.partCount){
this.state.partsFull = true;
} else {
this.state.parts.push(new Part(this));
}
};
Flame.prototype.updateParts = function(){
var i = this.state.parts.length;
while(i--){
this.state.parts[i].update();
}
};
Flame.prototype.renderParts = function(){
var i = this.state.parts.length;
while(i--){
this.state.parts[i].render();
}
};
Flame.prototype.clear = function (arguments) {
this.ctx.globalAlpha = 1;
this.ctx.fillStyle = 'hsla(0, 0%, 0%, .0)';
this.ctx.clearRect(0,0,this.state.width, this.state.height);
}
/** Fades the previous flames into opacity to product a blur **/
Flame.prototype.fade = function(){
this.ctx.globalCompositeOperation = 'destination-out';
this.ctx.fillStyle = 'hsla(0, 0%, 0%, .3)';
this.ctx.fillRect(0, 0, this.state.width, this.state.height);
this.ctx.globalCompositeOperation = 'lighter';
};
Flame.prototype.draw = function (arguments) {
this.fade();
if (this.state.on){
this.createParts();
} else if (this.state.parts.length>1){
this.state.parts.pop();
}
this.updateParts();
this.renderParts();
}
Flame.prototype.start = function (arguments) {
if (!this.state.rendering){
this.state.rendering=true
this.animate();
return this
} else {return;}
}
Flame.prototype.stop = function (arguments) {
this.state.rendering=false;
this.clear();
return this;
}
/** Toggle fuel and the flame will burn down or up again **/
Flame.prototype.toggleFuel = function (state) {
if (state===undefined)
detector.flame.state.on=!detector.flame.state.on;
else
detector.flame.state.on=state;
return detector.flame.state.on;
}
Flame.prototype.animate = function(){
window.requestAnimFrame(this.animate.bind(this),this.element);
this.state.globalTick++;
if (this.state.rendering) this.draw();
};
$.fn[pluginName] = function ( options ) {
return this.each(function () {
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName,
new Flame( this, options ));
}
});
}
// shim for requestAnimFrame
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
})( jQuery, window, document );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment