Having finally got to grips on rotating and animating in my HTML5 canvas, I found myself having to battle my next hurdle - adding user interaction!
The basis of adding mouse interaction with a canvas banner is to track the user’s mouse co-ordinates against your canvas elements. If the co-ordinates you can then set a variable, say hoverState = true, and you can then use this to check whether an action should fire within your animation loop. You can see the resulting code at the end of this post.
Having recently finished reading Rob Hawkes’ book on ‘Foundation HTML5 Canvas For Games and Entertainment’ I knew the basis of how I was going to achieve this - my problem? The animation I actually want to achieve.
If you take a look at our current services banner, you will notice that when you hover over one of the service circles, there is a smooth animation which introduces a secondary, slightly transparent circle which grows outwards. “Easy!” I thought, I would just increment the radius of my a new circle when the user hovers over the primary circle! And in essence it would be that simple, but having elements in the canvas which is all transparent is where it gets complicated and frustration ensues.
Problem 1: Overlapping transparent elements means double the opacity
As I said, the first thing I tried was to just introduce a secondary circle upon hover of which the radius increases with each iteration of the animation. The problem which resulted from this was the secondary circle could be seen through the semi-transparent primary circle - which was far from ideal when what is intended to be smooth animation results with a jarring jump in opacity.
Problem 2: Stroke width is not ‘outside’ the element.
After a bit of thinking, I realised what I was trying to recreate was in essence a border around the primary circle and I remembered canvas allows you to stroke a path/element. However, when I tried applying a stroke to the circle of which the width increased with each iteration of the animation, I ran across the problem that the stroke width is not ‘outside’ the element.
If you imagine applying a stroke to a layer in Photoshop, you specify whether it appears inside the layer, outside, or centre. Canvas in-fact applies a stroke similar to that of a centred stroke, which results in the below:
Solution: Combining stroke and increasing radius
My solution was to combine the best of 2 methods I tried. The first step was to increase the stroke of the secondary circle, then with each iteration the radius of the circle increases. This results in us faking the effect of a stroke on the outside of the element.
$(window).mousemove(function(e) {
illustrationOffset = servicesIllustration.offset();
illustrationX = Math.floor(e.pageX - illustrationOffset.left);
illustrationY = Math.floor(e.pageY - illustrationOffset.top);
dX = tmpService.x - illustrationX;
dY = tmpService.y - illustrationY;
distance = Math.sqrt((dX*dX)+(dY*dY));
if(distance < tmpService.radius) {
tmpService.serviceHoverIn = true;
$(servicesIllustration).css("cursor", "pointer");
}
else {
if(tmpService.serviceHoverIn) {
tmpService.serviceHoverIn = false;
tmpService.serviceHoverOut = true;
$(servicesIllustration).css("cursor", "default");
}
}
});
if(tmpService.serviceHoverIn || tmpService.serviceHoverOut) {
context.strokeStyle = tmpService.hoverInnerColor;
context.lineWidth = tmpService.hoverInnerStrokeWidth;
var radius = tmpService.radius + (tmpService.hoverInnerStrokeWidth/2);
context.beginPath();
context.arc(tmpService.x, tmpService.y, radius, 0, Math.PI * 2, false);
context.closePath();
context.stroke();
context.lineWidth = 1;
if(tmpService.serviceHoverIn && tmpService.hoverInnerStrokeWidth < tmpService.hoverInnerRadius) {
tmpService.hoverInnerStrokeWidth++;
}
else if(tmpService.serviceHoverOut && tmpService.hoverInnerStrokeWidth > 0) {
tmpService.hoverInnerStrokeWidth--;
}
else if(tmpService.serviceHoverOut && tmpService.hoverInnerStrokeWidth == 0) {
tmpService.serviceHoverOut = false;
tmpService.hoverInnerStrokeWidth = 0;
}
}
You can see the results of the above code here. My next step is to duplicate the service object for multiple services, and then add some click interaction!