I'd like to have a smooth animation for simple pin with the text on it. This pin is moving slowly around the canvas and I need smooth animation. So my pin consists from the background (filled and shadowed circle) and a text on top of it.
I achieved very smooth movement for the circle itself, but not for the text!
Q: Is it possible to achieve smooth text movement in the HTML5 canvas and how?
What I tried:
Method 0: Just draw the circle, no text on it. Animation is smooth.
Problem: No problem at all, except there is no text.
Method 1: Draw text on top of the circle using canvas method fillText().
Problem: Text is jittering while moving vertically. Horizontal moving does not produce jittering.
Method 2: Draw text to the offscreen canvas, copy canvas as an image on top of the circle. Create offscreen canvas and draw the text on it with sizes twice bigger than the original and then shrink while copying to the screen canvas. This will sharpen the text.
Problem: Text is sharp, but wavy and there is some flickering appears during movement.
Method3: Draw text to the offscreen canvas, copy canvas as an image on top of the circle. Create offscreen canvas and draw the text there. Size of the canvas and the text is the same as on the screen.
Problem: Movement is smooth enough, but text is blurry, out of focus.
My JSFIDDLE: Canvas text jitter animation
My Javascript code:
var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "bold 16px Helvetica";
ctx.shadowOffsetX = ctx.shadowOffsetY = 2;
ctx.shadowBlur = 6;
var bgColor="blue";
var textColor="white";
var shadowColor="rgba(0, 0, 0, 0.4)";
var radius=15;
//Draw empty plate for the pin, no text on top
//Problem: NONE, movements are smooth
function drawPinPlate(x, y)
{
var oldShadow = ctx.shadowColor;
ctx.shadowColor = shadowColor;
ctx.fillStyle = bgColor;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2*Math.PI);
ctx.fill();
ctx.shadowColor = oldShadow;
}
//method 1: Draw pin with text directly.
//Draw text using canvas direct text rendering.
//Problem: Text vertical jittering while animating movement
function drawPin1(x, y, name)
{
drawPinPlate(x, y);
ctx.fillStyle = textColor;
ctx.fillText(name, x, y);
}
//method 2: Draw pin with text using offscreen image with resize
//Draw text using text pre-rendered to offscreen canvas.
//Offscreen canvas is twice large than the original and we do resize (shrink) to the original one
//Problem: Text is sharp but some flickering appears during image movement
function drawPin2(x, y, name)
{
drawPinPlate(x, y);
ctx.drawImage(offImage1, x - radius, y - radius, radius*2, radius*2);
}
//method 2: Draw pin with text using offscreen image
//Draw text using text pre-rendered to offscreen canvas.
//Offscreen canvas is the same size as the original.
//Problem: Text is looking fuzzy, blurry
function drawPin3(x, y, name)
{
drawPinPlate(x, y);
ctx.drawImage(offImage2, x - radius, y - radius);
}
var PIXEL_RATIO = (function ()
{
var ctx = document.createElement("canvas").getContext("2d"),
dpr = window.devicePixelRatio || 1,
bsr = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1;
return dpr / bsr;
})();
//create offscreen canvas
createHiDPICanvas = function(w, h, ratio)
{
if (!ratio) { ratio = PIXEL_RATIO; }
var can = document.createElement("canvas");
can.width = w * ratio;
can.height = h * ratio;
can.style.width = w + "px";
can.style.height = h + "px";
can.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
return can;
}
//create offscreen canvas with text, size of the canvas is twice larger than the original.
function createPin1(name)
{
var cnv = createHiDPICanvas(radius*2, radius*2, 2);
var ctx = cnv.getContext("2d");
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "bold 16px Helvetica";
ctx.fillStyle = textColor;
ctx.fillText(name, radius, radius);
return cnv;
}
//create offscreen canvas with text. Text becomes very blurry.
function createPin2(name)
{
var cnv = createHiDPICanvas(radius*2, radius*2, 1);
var ctx = cnv.getContext("2d");
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "bold 16px Helvetica";
ctx.fillStyle = textColor;
ctx.fillText(name, radius, radius);
return cnv;
}
var offImage1, offImage2;
offImage1 = createPin1("AB");
offImage2 = createPin2("AB");
var startTime;
var speed = 180;
//render one frame
function render(deltaTime)
{
var x = (deltaTime / speed / 2) %100;
var y = (deltaTime / speed) % 200;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i<4; i++)
{
ctx.fillText(i, 20 + x + i * 50, 15 + y);
}
drawPinPlate(20 + x, 40 + y);
drawPin1(70 + x, 40 + y, "AB");
drawPin2(120 + x, 40 + y, "AB");
drawPin3(170 + x, 40 + y, "AB");
}
//Animation loop
function animate()
{
requestAnimationFrame(animate);
render(Date.now() - startTime);
}
//alert("You screen pixel ratio = " + PIXEL_RATIO);
startTime = Date.now();
animate();