HTML5 Canvas Text Animating around circle - javascript

Updated what is wrong with the code? I know it doesnt rotate but why is the text screwy.
Does anyone know why
I am tearing my hair out trying to figure this out
function showCircularNameRotating(string, startAngle, endAngle){
//context.clearRect(0, 0, canvas.width, canvas.height);
context.font = '32pt Sans-Serif';
context.fillStyle = '#1826B0';
circle = {
x: canvas.width/2,
y: canvas.height/2,
radius: 200
};
var radius = circle.radius,
angleDecrement = (startAngle - endAngle/string.length-1),
angle = parseFloat(startAngle),
index = 0,
character;
context.save();
while(index <string.length){
character = string.charAt(index);
context.save();
context.beginPath();
context.translate(circle.x + Math.cos(angle) * radius,
circle.y - Math.sin(angle) * radius);
context.rotate(Math.PI/2 - angle);
context.fillText(character, 0,0);
context.strokeText(character,0,0);
angle -= angleDecrement;
index++;
context.restore();
}
context.restore();
}

Yes, it's possible.
Here is a simple approach which you can build upon (I made it right now so it can certainly be optimized and tweaked in various ways).
This uses two objects, one for the text itself and one for each char.
The string is split into char objects in the text object's constructor
The canvas is rotated
The chars are each drawn relative to each other in a circular pattern
Live demo
Text object
function Text(ctx, cx, cy, txt, font, radius) {
this.radius = radius; // expose so we can alter it live
ctx.textBaseline = 'bottom'; // use base of char for rotation
ctx.textAlign = 'center'; // center char around pivot
ctx.font = font;
var charsSplit = txt.split(''), // split string to chars
chars = [], // holds Char objects (see below)
scale = 0.01, // scales the space between the chars
step = 0.05, // speed in steps
i = 0, ch;
for(; ch = charsSplit[i++];) // create Char objects for each char
chars.push(new Char(ctx, ch));
// render the chars
this.render = function() {
var i = 0, ch, w = 0;
ctx.translate(cx, cy); // rotate the canvas creates the movement
ctx.rotate(-step);
ctx.translate(-cx, -cy);
for(; ch = chars[i++];) { // calc each char's position
ch.x = cx + this.radius * Math.cos(w);
ch.y = cy + this.radius * Math.sin(w);
ctx.save(); // locally rotate the char
ctx.translate(ch.x, ch.y);
ctx.rotate(w + 0.5 * Math.PI);
ctx.translate(-ch.x, -ch.y);
ctx.fillText(ch.char, ch.x, ch.y);
ctx.restore();
w += ch.width * scale;
}
};
}
The Char object
function Char(ctx, ch) {
this.char = ch; // current char
this.width = ctx.measureText('W').width; // width of char or widest char
this.x = 0; // logistics
this.y = 0;
}
Now all we need to do is to create a Text object and then call the render method in a loop:
var text = new Text(ctx, cx, cy, 'CIRCULAR TEXT', '32px sans-serif', 170);
(function loop() {
ctx.clearRect(0, 0, w, h);
text.render();
requestAnimationFrame(loop);
})();
As said, there is plenty of room for optimizations here. The most expensive parts are:
Text rendering (render text to images first)
Local rotation for each char using save/restore
Minor things
I'll leave that as an exercise for OP though :)

In Canvas, not so sure. But would be trivial in SVG if you can use that?
Place the text along a path representing the circle.
Use an animateTransform to spin the path

Related

WebGL 2D Canvas Point convert to 3D world point

I have a problem with my code or I expect the wrong value. What I want to achieve is to convert 2D points from canvas to 3D world space point.
If I understand correctly, I should always get the same points regardless of the rotation of the camera because I don't want a VIEW SPACE but a WORLD SPACE point. So imagine that I click on facing wall of cube this between X and Z axis, then that what I think I should get constant Y value and it is working correctly until I make some rotation change to my camera. If we will make that camera is looking on that wall but with some angle then we will be got for each click different values for Y-axis which one should be constant because each point on that wall is on the same Y position.
var r = canvas.getBoundingClientRect();
var x = clientX - r.left;
var y = height - (clientY - r.top);
var projectionMatrix = matrix4.perspective(fov , ratio, near, far);
// convert to clip space
var xClipSpace = x / width * 2.0 - 1.0;
var yClipSpace = y / height * -2.0 + 1.0;
var zClipSpace = 1;
// convert back from clip space to world space
var xyzVec3 = vector3.create(xClipSpace ,yClipSpace ,zClipSpace);
var transfrom = matrix4.multiply(projectionMatrix,viewMatrix);
var inverse = matrix4.invert(transform);
var result = vector3.transformMat4(xyzVec3,inverse);
What I doing wrong ?
I'm not 100% sure I understand your diagram but otherwise your code looks fine.
In most WebGL math a frustum goes -Z in the distance. Of course you can rotate that based on the view. But in any case if you pass clip space [x, y, -1] through the inverse of the (projection * view) matrix then you'll get some point of the far plane of the view frustum. As the view rotates that point moves with the frustum.
If we will make that camera is looking on that wall but with some angle then we will be got for each click different values for Y-axis which one should be constant because each point on that wall is on the same Y position.
No: If you rotate the camera that entire wall will rotate so points on it will rotate.
Here's a diagram looking down on top of the view frustum in world space. The view is rotating. If clipX and clipY are 0 then the point being computed is on the center of the far plane of the frustum (Z = 1 in clip space). You can see that point rotating even though it stays on the plane. Its position in view space would not change but its position in world space changes because the entire view frustum is effectively being rotated.
Of course you'd get a different value for Y if you rotate the camera.
const v3 = twgl.v3;
const m4 = twgl.m4;
const ctx = document.querySelector('canvas').getContext('2d');
const boxTop = [
[-1, 1, -1],
[-1, 1, 1],
[ 1, 1, 1],
[ 1, 1, -1],
];
function render(time) {
time *= 0.001;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const fov = 60 * Math.PI / 180;
const ratio = ctx.canvas.clientWidth / ctx.canvas.clientHeight;
const near = 10;
const far = 40;
const projectionMatrix = m4.perspective(fov , ratio, near, far);
const viewMatrix = m4.rotationY(time);
// convert to clip space
const xClipSpace = 0;
const yClipSpace = 0;
const zClipSpace = 1;
// convert back from clip space to world space
const xyzVec3 = v3.create(xClipSpace ,yClipSpace ,zClipSpace);
const transform = m4.multiply(projectionMatrix, viewMatrix);
const inverse = m4.inverse(transform);
const result = m4.transformPoint(inverse, xyzVec3);
// -------------
ctx.setTransform(1, 0, 0, 1, 150.5, 75.5);
// draw origin
ctx.beginPath();
for (let i = -200; i <= 200; i += 20) {
ctx.moveTo(-400, i);
ctx.lineTo( 400, i);
ctx.moveTo(i, -400);
ctx.lineTo(i, 400);
}
ctx.strokeStyle = '#DDD';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(-400, 0);
ctx.lineTo( 400, 0);
ctx.moveTo(0, -400);
ctx.lineTo(0, 400);
ctx.strokeStyle = '#444';
ctx.stroke();
ctx.fillStyle = '#888';
ctx.fillText('x', 140, 10);
ctx.fillText('z', 5, -65);
// draw frustum
ctx.beginPath();
for (let i = 0; i < 4; ++i) {
const v0 = m4.transformPoint(inverse, boxTop[i]);
const v1 = m4.transformPoint(inverse, boxTop[(i + 1) % 4]);
drawLine(ctx, v0, v1);
}
ctx.strokeStyle = 'black';
ctx.stroke();
{
ctx.beginPath();
ctx.arc(result[0], result[2], 3, 0, Math.PI * 2);
ctx.fillStyle = 'red';
ctx.fill();
ctx.fillText(`${result[0].toFixed(2)}, ${result[2].toFixed(2)}`, result[0] + 5, result[2] + 3);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function drawLine(ctx, v0, v1) {
ctx.moveTo(v0[0], v0[2]);
ctx.lineTo(v1[0], v1[2]);
}
render();
canvas { border: 1px solid black; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.js"></script>

Drawing a angle / arc, filled with a radiant in canvas?

Problem: Im drawing a spaceship on the canvas. Upon hovering over it's x/y, im drawing an arc on the canvas, indicating the starships weapons angle and range (considering the starships current Baring/facing). Currently the determined angle is being drawn in green and extends as far as the weapons range value allows.
However, i would like to use a gradiant to fill the determined arc to indicate a drop-off in accuracy (i.e. gradiant begins at green, moves to orange, turns red the further away from the starships Position the angle is).
However, i dont know how i could replace my stock ctx.fill() on the drawn arc with a gradiant.
var ship {
loc: {x, y}, // lets say 100, 100
facing: facing // lets say facing 0, i.e. straight right
weapons: objects (range, startArc, endArc) // lets say 50, 300, 60 -> 120 degree angle, so -60 and +60 from facing (0/360)
}
for (var i = 0; i < weapon.arc.length; i++){
var p1 = getPointInDirection(weapon.range, weapon.arc[i][0] + angle, pos.x, pos.y);
var p2 = getPointInDirection(weapon.range, weapon.arc[i][1] + angle, pos.x, pos.y)
var dist = getDistance( {x: pos.x, y: pos.y}, p1);
var rad1 = degreeToRadian(weapon.arc[i][0] + angle);
var rad2 = degreeToRadian(weapon.arc[i][1] + angle);
fxCtx.beginPath();
fxCtx.moveTo(pos.x, pos.y);
fxCtx.lineTo(p1.x, p1.y);
fxCtx.arc(pos.x, pos.y, dist, rad1, rad2, false);
fxCtx.closePath();
fxCtx.globalAlpha = 0.3;
fxCtx.fillStyle = "green";
fxCtx.fill();
fxCtx.globalAlpha = 1;
}
is it possible to replace the arc/globalalpha/fill so use a gradiant flow instead of it being colored fixed and if so, how ?
thanks
To fill an arc with a gradient, animated just for the fun.
Uses a radial gradient and set colour stops as a fraction of distance.
The function createRadialGradient takes 6 numbers the position x,y and start radius and the position x,y and end radius of the gradient.
Colour stops are added via the gradient object addColorStop function that takes a value 0 inner to 1 outer part of the gradient and the colour as a CSS color string. "#F00" or "rgba(200,0,0,0.5)" or "RED"
Then just use the gradient as the fill style.
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
function update(time) {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// position of zones in fractions
var posRed = 0.8 + Math.sin(time / 100) * 0.091;
var posOrange = 0.5 + Math.sin(time / 200) * 0.2;
var posGreen = 0.1 + Math.sin(time / 300) * 0.1;
var pos = {
x: canvas.width / 2,
y: canvas.height / 2
};
var dist = 100;
var ang1 = 2 + Math.sin(time / 1000) * 0.5;
var ang2 = 4 + Math.sin(time / 1300) * 0.5;
var grad = ctx.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, dist);
grad.addColorStop(0, "#0A0");
grad.addColorStop(posGreen, "#0A0");
grad.addColorStop(posOrange, "#F80");
grad.addColorStop(posRed, "#F00");
grad.addColorStop(1, "#000");
ctx.fillStyle = grad;
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
ctx.arc(pos.x, pos.y, dist, ang1, ang2);
ctx.fill();
requestAnimationFrame(update);
}
requestAnimationFrame(update);

is it possible to draw a big number of simple geometric figures in HTML5.canvas?

I have this awesome piece of code.
The idea, as you can imagine,is to draw a grid of rectangles. I want a big grid, let's say 100 X 100 or more.
However, when i run the awesome piece of code for the desired size (100X 100), my browser crashes.
How can i achieve that?
* please note: when i say 100X100 i mean the final number of rectangles (10k) not the size of the canvas.
thank u
function init() {
var cnv = get('cnv');
var ctx = cnv.getContext('2d');
var ancho = 12; // ancho means width
var alto = 12; // alto means height
ctx.fillStyle = randomRGB();
for (var i = 0; i < cnv.width; i+= ancho) {
for (var j = 0; j < cnv.height; j+= alto) {
//dibujar means to draw, rectangulo means rectangle
dibujarRectangulo(i+ 1, j+1, ancho, alto, ctx);
}
}
}
function dibujarRectangulo(x, y, ancho, alto, ctx) {
ctx.rect(x, y, ancho, alto);
ctx.fill();
ctx.closePath();
}
The dibujarRectanglo() function calls rect() function which adds a closed rectanglar subpath to the current path. Then calls fill() function to fill the current path. Then calls closePath() function to close the subpath, which does nothing since the subpath is already closed.
In other words, the first dibujarRectanglo() function call is painting a path that contains 1 rectangle subpath. The second call is painting a path that contains 2 rectangle subpaths. The third call is painting a path that contains 3 rectangle subpaths. And so on. If the loop calls dibujarRectanglo() function 10000 times then a total of 1+2+3+...+10000 = 50005000 (i.e. over 50 million) rectangle subpaths will be painted.
The dibujarRectangle() function should be starting a new path each time. For example...
function dibujarRectangulo(x, y, ancho, alto, ctx) {
ctx.beginPath();
ctx.rect(x, y, ancho, alto);
ctx.fill();
}
Then 10000 calls will only paint 10000 rectangle subpaths which is a lot faster that painting 50 million rectangle subpaths.
16,384 boxes on the wall
As I said in the comment its easy to draw a lot of boxes, it is not easy to have them all behave uniquely. Anyways using render to self to duplicate boxes exponential there are 128 * 128 boxes so that's 16K, one more iteration and it would be 64K boxes.
Its a cheat, I could have just drawn random pixels and called each pixel a box.
Using canvas you will get upto 4000 sprites per frame on a top end machine using FireFox with each sprite having a location, center point, rotation, x and y scale, and an alpha value. But that is the machine going flat out.
Using WebGL you can get much higher but the code complexity goes up.
I use a general rule of thumb, if a canva 2D project has more than 1000 sprites then it is in need of redesign.
var canvas = document.getElementById("can");
var ctx = canvas.getContext("2d");
/** CreateImage.js begin **/
var createImage = function (w, h) {
var image = document.createElement("canvas");
image.width = w;
image.height = h;
image.ctx = image.getContext("2d");
return image;
}
/** CreateImage.js end **/
/** FrameUpdate.js begin **/
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;
var ch = h / 2;
var boxSize = 10;
var boxSizeH = 5;
var timeDiv = 1.2;
var bBSize = boxSize * 128; // back buffer ssize
var buff = createImage(bBSize, bBSize);
var rec = createImage(boxSize, boxSize);
var drawRec = function (ctx, time) {
var size, x, y;
size = (Math.sin(time / 200) + 1) * boxSizeH;
ctx.fillStyle = "hsl(" + Math.floor((Math.sin(time / 500) + 1) * 180) + ",100%,50%)";
ctx.strokeStyle = "Black";
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.clearRect(0, 0, boxSize, boxSize);
x = Math.cos(time / 400);
y = Math.sin(time / 400);
ctx.setTransform(x, y, -y, x, boxSizeH, boxSizeH)
ctx.fillRect(-boxSizeH + size, -boxSizeH + size, boxSize - 2 * size, boxSize - 2 * size);
ctx.strokeRect(-boxSizeH + size, -boxSizeH + size, boxSize - 2 * size, boxSize - 2 * size);
}
function update(time) {
var fw, fh, px, py, i;
time /= 7;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, w, h);
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.clearRect(0, 0, bBSize, bBSize)
buff.ctx.drawImage(rec, 0, 0);
buff.ctx.drawImage(rec, boxSize, 0);
fw = boxSize + boxSize; // curent copy area width
fh = boxSize; // curent copy area height
px = 0; // current copy to x pos
py = boxSize; // current copy to y pos
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh); // make square
for (i = 0; i < 6; i++) {
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.drawImage(rec, 0, 0);
fh += fh; // double size across
px = fw;
py = 0;
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh); // make rec
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.drawImage(rec, 0, 0);
fw += fw; // double size down
px = 0;
py = fh;
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh);
}
// draw the boxes onto the canvas,
ctx.drawImage(buff, 0, 0, 1024, 1024);
requestAnimationFrame(update);
}
update();
.canv {
width:1024px;
height:1024px;
}
<canvas id="can" class = "canv" width=1024 height=1024></canvas>

How can i plot letter around a fabricjs circle

i have a circle added to canvas and then some text i would like wrapped around a circle.
here is what i have so far
var circle = new fabric.Circle({
top: 100,
left: 100,
radius: 100,
fill: '',
stroke: 'green',
});
canvas.add(circle);
var obj = "some text"
for(letter in obj){
var newLetter = new fabric.Text(obj[letter], {
top: 100,
left: 100
});
canvas.add(newLetter);
canvas.renderAll();
}
i have a tried a couple other solutions posted around the web but nothing working properly so far with fabric.
Circular Text.
I started this answer thinking it would be easy but it turned a little ugly. I rolled it back to a simpler version.
The problems I encountered are basic but there are no simple solutions..
Text going around the circle can end up upside down. Not good for
reading
The spacing. Because the canvas only gives a basic 2D transform I can
not scale the top and bottom of the text independently resulting in
text that either looks too widely spaced or too squashed.
I have an altogether alternative approch by it is way too heavy for an answer here. It involves a custom scan line render (a GPU hack of sorts) so you may try looking for something along those lines if text quality is paramount.
The problem I encounter were fixed by just ignoring them, always a good solution. LOL
How to render circular text on 2D canvas.
As there is no way to do it in one call I wrote a function that renders each character one at a time. I use ctx.measureText to get the size of the whole string to be drawn and then convert that into an angular pixel size. Then with a little adjustments for the various options, alignment, stretching, and direction (mirroring) I go through each character in the string one at a time, use ctx.measureText to measure it's size and then use ctx.setTransform to position rotate and scale the character, then just call ctx.fillText() rendering just that character.
It is a little slower than just the ctx.fillText()method but then fill text can't draw on circles can it.
Some calculations required.
To workout the angular size of a pixel for a given radius is trivial but often I see not done correctly. Because Javascript works in radians angular pixel size is just
var angularPixelSize = 1 / radius; // simple
Thus to workout what angle some text will occupy on a circle or given radius.
var textWidth = ctx.measureText("Hello").width;
var textAngularWidth = angularPixelSize * textWidth;
To workout the size of a single character.
var text = "This is some text";
var index = 2; // which character
var characterWidth = ctx.measureText(text[index]).width;
var characterAngularWidth = angularPixelSize * textWidth;
So you have the angular size you can now align the text on the circle, either centered, right or left. See the snippet code for details.
Then you need to loop through each character one at a time calculating the transformation, rendering the text, moving the correct angular distance for the next character until done.
var angle = ?? // the start angle
for(var i = 0; i < text.length; i += 1){ // for each character in the string
var c = text[i]; // get character
// get character angular width
var w = ctx.measureText(c).width * angularPixelSize;
// set the matrix to align the text. See code under next paragraph
...
...
// matrix set
ctx.fillText(c,0,0); // as the matrix set the origin just render at 0,0
angle += w;
}
The fiddly math part is setting the transform. I find it easier to work directly with the transformation matrix and that allows me to mess with scaling etc with out having to use too many transformation calls.
Set transform takes 6 numbers, first two are the direction of the x axis, the next two are the direction of the y axis and the last two are the translation from the canvas origin.
So to get the Y axis. The line from the circle center moving outward for each character we need the angle at which the character is being draw and to reduce misalignment (NOTE reduce not eliminate) the angular width so we can use the character's center to align it.
// assume angle is position and w is character angular width from above code
var xDx = Math.cos(angle + w / 2); // get x part of X axis direction
var xDy = Math.sin(angle + w / 2); // get y part of X axis direction
Now we have the normalised vector that will be the x axis. The character is draw from left to right along this axis. I construct the matrix in one go but I'll break it up below. Please NOTE that I made a boo boo in my snippet code with angles so the code is back to front (X is Y and Y is X)
Note that the snippet has the ability to fit text between two angles so I scale the x axis to allow this.
// assume scale is how much the text is squashed along its length.
ctx.setTransform(
xDx * scale, xDy * scale, // set the direction and size of a pixel for the X axis
-xDy, xDx, // the direction ot the Y axis is perpendicular so switch x and y
-xDy * radius + x, xdx * radius + y // now set the origin by scaling by radius and translating by the circle center
);
Well thats the math and logic to drawing a circular string. I am sorry but I dont use fabric.js so it may or may not have the option. But you can create your own function and render directly to the same canvas as fabric.js, as it does not exclude access. Though it will pay to save and restore the canvas state as fabric.js does not know of the state change.
Below is a snippet showing the above in practice. It is far from ideal but is about the best that can be done quickly using the existing canvas 2D API. Snippet has the two functions for measuring and drawing plus some basic usage examples.
function showTextDemo(){
/** Include fullScreenCanvas.js begin **/
var canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
canvas = (function () {
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
} ) ();
var ctx = canvas.ctx;
/** fullScreenCanvas.js end **/
// measure circle text
// ctx: canvas context
// text: string of text to measure
// x,y: position of center
// r: radius in pixels
//
// returns the size metrics of the text
//
// width: Pixel width of text
// angularWidth : angular width of text in radians
// pixelAngularSize : angular width of a pixel in radians
var measureCircleText = function(ctx, text, x, y, radius){
var textWidth;
// get the width of all the text
textWidth = ctx.measureText(text).width;
return {
width :textWidth,
angularWidth : (1 / radius) * textWidth,
pixelAngularSize : 1 / radius
}
}
// displays text alon a circle
// ctx: canvas context
// text: string of text to measure
// x,y: position of center
// r: radius in pixels
// start: angle in radians to start.
// [end]: optional. If included text align is ignored and the text is
// scalled to fit between start and end;
// direction
var circleText = function(ctx,text,x,y,radius,start,end,direction){
var i, textWidth, pA, pAS, a, aw, wScale, aligned, dir;
// save the current textAlign so that it can be restored at end
aligned = ctx.textAlign;
dir = direction ? 1 : -1;
// get the angular size of a pixel in radians
pAS = 1 / radius;
// get the width of all the text
textWidth = ctx.measureText(text).width;
// if end is supplied then fit text between start and end
if(end !== undefined){
pA = ((end - start) / textWidth) * dir;
wScale = (pA / pAS) * dir;
}else{ // if no end is supplied corret start and end for alignment
pA = -pAS * dir;
wScale = -1 * dir;
switch(aligned){
case "center": // if centered move around half width
start -= pA * (textWidth / 2);
end = start + pA * textWidth;
break;
case "right":
end = start;
start -= pA * textWidth;
break;
case "left":
end = start + pA * textWidth;
}
}
// some code to help me test. Left it here incase someone wants to underline
// rmove the following 3 lines if you dont need underline
ctx.beginPath();
ctx.arc(x,y,radius,end,start,end>start?true:false);
ctx.stroke();
ctx.textAlign = "center"; // align for rendering
a = start; // set the start angle
for (var i = 0; i < text.length; i += 1) { // for each character
// get the angular width of the text
aw = ctx.measureText(text[i]).width * pA;
var xDx = Math.cos(a + aw / 2); // get the yAxies vector from the center x,y out
var xDy = Math.sin(a + aw / 2);
if (xDy < 0) { // is the text upside down. If it is flip it
// sets the transform for each character scaling width if needed
ctx.setTransform(-xDy * wScale, xDx * wScale,-xDx,-xDy, xDx * radius + x,xDy * radius + y);
}else{
ctx.setTransform(-xDy * wScale, xDx * wScale, xDx, xDy, xDx * radius + x, xDy * radius + y);
}
// render the character
ctx.fillText(text[i],0,0);
a += aw;
}
ctx.setTransform(1,0,0,1,0,0);
ctx.textAlign = aligned;
}
// set up canvas
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // centers
var ch = h / 2;
var rad = (h / 2) * 0.9; // radius
// clear
ctx.clearRect(0, 0, w, h)
// the font
var fontSize = Math.floor(h/20);
if(h < 400){
var fontSize = 10;
}
ctx.font = fontSize + "px verdana";
// base settings
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillStyle = "#666";
ctx.strokeStyle = "#666";
// Text under stretched
circleText(ctx, "Test of circular text rendering", cw, ch, rad, Math.PI, 0, true);
// Text over stretchered
ctx.fillStyle = "Black";
circleText(ctx, "This text is over the top", cw, ch, rad, Math.PI, Math.PI * 2, true);
// Show centered text
rad -= fontSize + 4;
ctx.fillStyle = "Red";
// Use measureCircleText to get angular size
var tw = measureCircleText(ctx, "Centered", cw, ch, rad).angularWidth;
// centered bottom and top
circleText(ctx, "Centered", cw, ch, rad, Math.PI / 2, undefined, true);
circleText(ctx, "Centered", cw, ch, rad, -Math.PI * 0.5, undefined, false);
// left align bottom and top
ctx.textAlign = "left";
circleText(ctx, "Left Align", cw, ch, rad, Math.PI / 2 - tw * 0.6, undefined, true);
circleText(ctx, "Left Align Top", cw, ch, rad, -Math.PI / 2 + tw * 0.6, undefined, false);
// right align bottom and top
ctx.textAlign = "right";
circleText(ctx, "Right Align", cw, ch, rad, Math.PI / 2 + tw * 0.6, undefined, true);
circleText(ctx, "Right Align Top", cw, ch, rad, -Math.PI / 2 - tw * 0.6, undefined, false);
// Show base line at middle
ctx.fillStyle = "blue";
rad -= fontSize + fontSize;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
circleText(ctx, "Baseline Middle", cw, ch, rad, Math.PI / 2, undefined, true);
circleText(ctx, "Baseline Middle", cw, ch, rad, -Math.PI / 2, undefined, false);
// show baseline at top
ctx.fillStyle = "Green";
rad -= fontSize + fontSize;
ctx.textAlign = "center";
ctx.textBaseline = "top";
circleText(ctx, "Baseline top", cw, ch, rad, Math.PI / 2, undefined, true);
circleText(ctx, "Baseline top", cw, ch, rad, -Math.PI / 2, undefined, false);
}
showTextDemo();
window.addEventListener("resize",showTextDemo);

Adding a background image to a HTML5 Canvas spinner

JSFiddle Link: http://jsfiddle.net/lustre/970tf041/6/
I didn't create this code, but the blog which it was created from isn't working at the moment...
Basically, I'm looking for a way to include an image (http://i.imgur.com/7IBiiOW.png) behind the spinner text, which spins with the spinner when it spins. This is my first foray into canvas' so I'm a bit lost as to where it could be added.
The code below used to colour the background of each segment with a random colour, but now it just makes each segment transparent.
function genHex(){
colorCache.push('transparent');
return 'transparent';
}
Here's the original in case it's useful:
function genHex(){
var colors=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"], color = "", digit = [], i;
for (i=0;i<6;i++){
digit[i]=colors[Math.round(Math.random()*14)];
color = color+digit[i];
}
if($.inArray(color, colorCache) > -1){
genHex();
} else {
colorCache.push('#'+color);
return '#'+color;
}
}
I'm really at a loss as to where it could possibly go... The spinners speed is randomised every time it spins, and I'd like the image to match that spin (even if it has to be slowed down).
I assume the code would need to go into the drawWheel() function, but I have no idea how to include it.
function drawWheel() {
ctx.strokeStyle = params.wheelBorderColor;
ctx.lineWidth = params.wheelBorderWidth;
ctx.font = params.wheelTextFont;
ctx.clearRect(0,0,500,500);
var text = null, i = 0, totalJoiner = pplLength;
for(i = 0; i < totalJoiner; i++) {
text = pplArray[i];
var angle = startAngle + i * arc;
ctx.fillStyle = colorCache.length > totalJoiner ? colorCache[i] : genHex();
ctx.beginPath();
// ** arc(centerX, centerY, radius, startingAngle, endingAngle, antiClockwise);
ctx.arc(250, 250, params.outterRadius, angle, angle + arc, false);
ctx.arc(250, 250, params.innerRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
ctx.save();
ctx.shadowOffsetX = -1;
ctx.shadowOffsetY = -1;
ctx.shadowBlur = 1;
ctx.shadowColor = params.wheelTextShadowColor;
ctx.fillStyle = params.wheelTextColor;
ctx.translate(250 + Math.cos(angle + arc / 2) * params.textRadius, 250 + Math.sin(angle + arc / 2) * params.textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
ctx.restore();
ctx.closePath();
}
drawArrow();
}
Thank you for your time :)
See for example HTML5 Canvas Rotate Image for explanation of image rotation on canvas
The rotated image can be displayed by the following code in the function drawWheel() after ctx.clearRect(0,0,500,500):
var img = document.getElementById("img1");
ctx.save();
ctx.translate(250, 250);
ctx.rotate(startAngle);
ctx.drawImage(img, -params.outterRadius, -params.outterRadius,
2 * params.outterRadius, 2 * params.outterRadius);
ctx.restore();
The image size is adjusted to 2 * params.outterRadius.
Working example: http://jsfiddle.net/0npc395n/1/

Categories

Resources