I'm working on a little project where I have to create sort of an image mask. Overall my code works quite good in all browsers but sporadically the animations "flickr" on Chrome (Windows). So I was wondering if I could increase to performance when repainting my canvas (on mouseover/out).
JS:
// Stores all dday objects
var Ddays = {};
// Configuration options
var radiusGap = 10,
radiusSteps = 9,
imageLeftOffsetLeft = 200;
var Dday = function(index, canvas) {
this.ctx = canvas.getContext('2d');
this.$canvas = $(canvas);
// Store relevant information on object
this.ddayClasses = this.$canvas.parent().attr('class');
this.sectorColor = this.$canvas.parent().css('background-color');
this.width = this.$canvas.width();
this.height = this.$canvas.height();
this.animationMode = '';
this.isSold = this.ddayClasses.indexOf('sold') !== -1 ? true : false;
// We need to store the current object in a superior object
// to access it on mouseover/out
this.$canvas.attr('data-id', index);
Ddays[index] = this;
if(!this.isSold) {
this.setRadiuses();
}
var _self = this;
$('<img src="'+ _self.$canvas.data('img') +'">').load(function() {
_self.image = this;
// Scale image to fit height
if(this.height > _self.height) {
_self.imageHeight = _self.height - radiusGap * 3;
_self.imageWidth = this.width - (this.height - _self.height) - radiusGap * 3;
} else {
_self.imageWidth = this.width;
_self.imageHeight = this.height;
}
_self.setPositioning(_self);
_self.drawStage(_self);
if(!_self.isSold) {
_self.$canvas.on('mouseover mouseout', _self.animate);
}
});
}
/********************************************************
Calculate and store x/y positioning
********************************************************/
Dday.prototype.setPositioning = function(_self) {
var positioning = {}
if(_self.ddayClasses.indexOf('dday-big') !== -1 ||
_self.ddayClasses.indexOf('dday-listview') !== -1) {
positioning.x = imageLeftOffsetLeft;
positioning.imgX = positioning.x - _self.imageWidth / 2;
} else {
positioning.x = _self.width / 2;
positioning.imgX = positioning.x - _self.imageWidth / 2;
}
positioning.y = _self.height / 2;
positioning.imgY = positioning.y - _self.imageHeight / 2;
_self.positioning = positioning;
};
/********************************************************
Calculate and store radii of object depending on the given p* class
********************************************************/
Dday.prototype.setRadiuses = function() {
var pSize = this.ddayClasses.match(/[p.][0-9][0-9]/)[0].substr(1);
this.maxRadius = this.height / 2 - radiusGap;
this.initialRadius = this.radius = (this.maxRadius / radiusSteps) * (pSize / 10);
};
/********************************************************
Mouseover/out callback to initialize animation
********************************************************/
Dday.prototype.animate = function(ev) {
var _self = Ddays[$(this).data('id')];
_self.animationMode = ev.type;
requestAnimationFrame(function () {
_self.drawStage(_self);
});
};
Dday.prototype.drawStage = function(_self) {
/********************************************************
Draw background image, sector color overlay and
circle background
********************************************************/
_self.ctx.fillStyle = '#fff';
_self.ctx.fillRect(0, 0, _self.width, _self.height);
_self.ctx.drawImage(_self.image, _self.positioning.imgX, _self.positioning.imgY, _self.imageWidth, _self.imageHeight);
_self.ctx.fillStyle = _self.sectorColor;
_self.ctx.fillRect(0, 0, _self.width, _self.height);
// We don't need the stuff bellow when the item is sold
if(_self.isSold) {
return;
}
_self.ctx.fillStyle = '#fff';
_self.ctx.beginPath();
_self.ctx.arc(_self.positioning.x, _self.positioning.y, _self.radius, 0, 2 * Math.PI, true);
_self.ctx.closePath();
_self.ctx.fill();
/********************************************************
Setup new canvas which we later append back to the existing.
We have to do this because when using clip() for the image
the arc won't render antialised in Chrome (crispy borders):
http://stackoverflow.com/questions/9536257/how-to-anti-alias-clip-edges-in-html5-canvas-under-chrome-windows/13854185#answer-13854185
********************************************************/
var scratchCanvas = document.createElement('canvas');
scratchCanvas.width = _self.width;
scratchCanvas.height = _self.height;
var scratchCtx = scratchCanvas.getContext('2d');
/********************************************************
Clear stage and draw image with standard composite operation
********************************************************/
scratchCtx.clearRect(0, 0, scratchCanvas.width, scratchCanvas.height);
scratchCtx.globalCompositeOperation = 'source-over';
scratchCtx.drawImage(_self.image, _self.positioning.imgX, _self.positioning.imgY, _self.imageWidth, _self.imageHeight);
/********************************************************
Render arc and clip image
https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
********************************************************/
scratchCtx.fillStyle = '#fff';
scratchCtx.globalCompositeOperation = 'destination-in';
scratchCtx.beginPath();
scratchCtx.arc(_self.positioning.x, _self.positioning.y, _self.radius, 0, 2 * Math.PI, true);
scratchCtx.closePath();
scratchCtx.fill();
/********************************************************
Append created canvas to the existing one
********************************************************/
_self.ctx.drawImage(scratchCanvas, 0, 0);
/********************************************************
Restart drawing when animationMode is set
********************************************************/
if(_self.animationMode === 'mouseover') {
if(_self.radius < _self.maxRadius) {
// Prevent that radius is higher than the maximal radius
_self.radius = _self.radius * 1.15 >= _self.maxRadius ? _self.maxRadius : _self.radius * 1.15;
requestAnimationFrame(function () {
_self.drawStage(_self);
});
}
} else if(_self.animationMode === 'mouseout') {
if(_self.radius > _self.initialRadius) {
// Prevent that radius is lower than the initial radius
_self.radius = _self.radius * 0.9 <= _self.initialRadius ? _self.initialRadius : _self.radius * 0.9;
requestAnimationFrame(function () {
_self.drawStage(_self);
});
}
} else {
_self.animationMode = '';
}
};
/********************************************************
Setup new Objects
********************************************************/
$('.dday canvas').each(function(index, canvas){
new Dday(index, canvas);
});
/********************************************************
AnimationFrame polyfill
********************************************************/
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
The HTML code is predefined and I can't make changes to it. I'd appreciate any suggestions on how to improve my code and especially the performance.