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/
Related
I have a canvas with multiple circles in different colours and I want add a fade out effect only to some circles. The effect is only applicable to the ones in red and green.
The code is as follows
function drawPiece(pieceX, pieceY, color) {
if (color === "rgba(0,0,0,1)" || color === "rgba(255,255,255,1)"){
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(pieceX, pieceY, 50, 0, 2 * Math.PI, false);
ctx.fill();
ctx.lineWidth = "4";
ctx.strokeStyle = "rgba(0,0,0,1)";
ctx.stroke();
ctx.closePath();
}
else {
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(pieceX, pieceY, 10, 0, 2 * Math.PI, false);
ctx.fill();
ctx.lineWidth = "4";
ctx.strokeStyle = "rgba(0,0,0,1)";
ctx.stroke();
ctx.closePath();
setTimeout(function(){
var fadeTarget = document.getElementById("canvasGame");
var fadeEffect = setInterval(function () {
if (!fadeTarget.style.opacity) {
fadeTarget.style.opacity = 1;
}
if (fadeTarget.style.opacity > 0) {
fadeTarget.style.opacity -= 0.02;
} else {
clearInterval(fadeEffect);
}
}, 20);
},0.5);
}
}
The fade effect works but it fades out the whole canvas and not the individual circles.
How can I achieve this, that only some elements are faded out.
Thanks in advance
A great canvas 2d resource is MDN's CanvasRenderingContext2D
Animations using canvas.
You will need a render loop if you want to animate canvas content.
The render loop is called 60 times a second, if possible, drawing too much and the rate will drop below 60fps.
The main loop clears the canvas, then draws the animated content, then requests the next frame.
requestAnimationFrame(mainLoop); // request the first frame to start the animation
function mainLoop() {
ctx.globalAlpha = 1; // default to 1 in case there is other content drawn
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // clear the canvas
drawContent(); // render the content.
requestAnimationFrame(mainLoop); // request the next frame (in 1/60th second)
}
A function to draw the circle. You can remove the alpha from the color and use globalAlpha to set the transparency.
Math.TAU = Math.PI * 2; // set up 2 PI
function drawCircle(x, y, radius, color, alpha = 1) {
ctx.globalAlpha = alpha;
ctx.fillStyle = color;
ctx.strokeStyle = "#000";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.TAU);
ctx.fill();
ctx.stroke();
}
Create an object to hold a circle's description and an array to put them in
const circles = [];
function circle(x,y,r = 10, col = "#FFF", alpha = 1) {
return {x, y, r, col, alpha, alphaTarget: alpha};
}
Then in the drawContent function draw the circles one at a time
function drawContent() {
for (const circle of circles) {
if(circle.alpha !== circle.alphaTarget) {
const aStep = circle.alphaTarget - circle.alpha;
const dir = Math.sign(aStep);
circle.alpha += Math.min(Math.abs(aStep), dir * 0.02)) * dir;
}
drawCircle(circle.x, circle.y, circle.r, circle.col, circle.alpha);
}
}
Demo
The demo draws 100 circles each with their own color and alpha. The alpha is randomly selected to fade out and then back in.
You will need a render loop if you want to animate canvas content.
I move the circle so that if a device is to slow to render the content then it will be easier to see the low frame rate.
Math.TAU = Math.PI * 2; // set up 2 PI
Math.rand = (val) => Math.random() * val;
Math.randI = (val) => Math.random() * val | 0;
requestAnimationFrame(mainLoop);
const ctx = canvas.getContext("2d");
const W = canvas.width = innerWidth; // size canvas to page
const H = canvas.height = innerHeight; // size canvas to page
const circleCount = 100;
const circleFadeRate = 0.01; // per 60th second
const circles = [];
const circle = (x,y,r = 10, col = "#FFF", alpha = 1) => ({x, y, r, col, alpha, alphaTarget: alpha});
createCircles();
function createCircles() {
var i = circleCount;
while (i--) {
circles.push(circle(Math.rand(W), Math.rand(H), Math.rand(10) + 10, "#" + Math.randI(0xFFF).toString(16).padStart(3,"0"), 1));
}
circles.sort((a,b) => a.r - b.r); // big drawn last
}
function mainLoop() {
ctx.globalAlpha = 1;
ctx.clearRect(0, 0, W, H);
drawContent();
requestAnimationFrame(mainLoop);
}
function drawCircle(x, y, radius, color, alpha = 1) {
ctx.globalAlpha = alpha;
ctx.fillStyle = color;
ctx.strokeStyle = "#000";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.TAU);
ctx.fill();
ctx.stroke();
}
function drawContent() {
for (const circle of circles) {
if(circle.alpha !== circle.alphaTarget) {
const aStep = circle.alphaTarget - circle.alpha;
const dir = Math.sign(aStep);
circle.alpha += Math.min(Math.abs(aStep), 0.02) * dir;
} else if(Math.random() < 0.01) {
circle.alphaTarget = circle.alpha < 0.7 ? 1 : Math.random() * 0.4;
}
circle.y += (circle.r - 10) / 5;
circle.y = circle.y > H + circle.r + 2 ? -(circle.r + 2) : circle.y;
drawCircle(circle.x, circle.y, circle.r, circle.col, circle.alpha);
}
}
body {
padding: 0px;
}
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
For more information on the 2D canvas API see the link at top of this answer.
Canvas is a painting surface. Meaning you can't change it after you paint it. You can only clear it, or paint over it. Just like a real painting, you can't change the color of a stroke you've already painted.
So you must clear the canvas and then redraw it all, except this time draw some circles with a different opacity. Just change the last number on those rgba values to be between 0 and 1 to change the opacity.
Store opacity in a variable somewhere:
var circleOpacity = 1;
Change the opacity and then redraw in your interval function:
circleOpactiy -= 0.2;
drawMyCanvas();
Now draw the some pieces with a fillStyle something like:
ctx.fillStyle = shouldBeFaded ? `rgba(0,0,0,${circleOpacity})` : 'rgba(0,0,0,1)'
Alternatively, you could position two canvases absolutely so they are on top of each other and you could fade the top one as you are already doing. That way you won't have to re-render the canvas constantly. If the only thing you want to do is fade some circles, this might be easier. But if you want to anything more complex on that canvas (like render a game of some sort) you'll want to redraw the canvas every frame of animation anyway.
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);
hey guys i could need some advice cause i'm stuck at the moment
i made for clarity 2 screenshots
Basically what i want is if i push one of the buttons (left/right) the whole circle should rotate - the darkred active state should fade out in to grey and the grey field which becomes active should fade in to darkred
The words should keep the position until the go upside down - after this they should flip
I took some ideas but i 've no idea how to start.
Is css3 + rotation possible, do i need canvas or is there any other possibility around?
How would you guys solve this ?
Thx in advance for your suggestions.
Copy the placeImage function and use it to center and rotate an image on your canvas. Make sure to clear your canvas before doing the rotation.
function placeImage(ctx, img, cx, cy, deg) {
var to_rad = Math.PI / 180;
deg *= to_rad;
ctx.save();
// move to center
ctx.translate(cx, cy);
// rotate about left top edge
ctx.rotate(deg);
// center image and place on canvas.
ctx.translate(-img.width / 2, -img.height / 2);
ctx.drawImage(img, 0, 0);
// clear rotations and translations
ctx.restore();
}
function makeCirc() {
var to_rad = Math.PI / 180;
var circ = document.getElementById('circ');
var circx = circ.getContext('2d');
circ.width = circ.height = 50;
circx.beginPath();
circx.moveTo(45, 25);
circx.arc(25, 25, 20, 0, 360 * to_rad);
circx.strokeStyle = "#00FFFF";
circx.lineWidth = 3;
circx.stroke();
circx.fillStyle = '#FF00FF';
circx.textAlign = 'center';
circx.font = "20px Arial";
circx.fillText('^', 25, 25);
circx.beginPath();
circx.lineWidth = 2;
circx.strokeStyle = "#FF00FF";
circx.moveTo(25, 40);
circx.lineTo(25, 13);
circx.stroke();
return circ;
}
(function main() {
var can = document.getElementById('can');
var ctx = can.getContext('2d');
var w = can.width = 200;
var h = can.height = 150;
var to_rad = Math.PI / 180;
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, w, h);
var circ = makeCirc();
var deg = 0;
var dstep = 360 / 60;
var dmax = 360;
setInterval(function() {
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, w, h);
placeImage(ctx, circ, w / 2, h / 2, deg);
deg += dstep;
deg %= dmax;
}, 1000);
})();
<canvas id="can"></canvas>
<canvas id="circ"></canvas>
I made a cylinder gauge, very similar to this one:
It is drawn using about 7 or so functions... mine is a little different. It is very fleixble in that I can set the colors, transparency, height, width, whether there is % text shown and a host of other options. But now I have a need for the same thing, but all rotated 90 deg so that I can set the height long and the width low to generate something more like this:
I found ctx.rotate, but no mater where it goes all the shapes fall apart.. ctx.save/restore appears to do nothing, I tried putting that in each shape drawing function. I tried modifying, for example, the drawOval function so that it would first rotate the canvas if horizontal was set to one; but it appeared to rotate it every single iteration, even with save/restore... so the top cylinder would rotate and the bottom would rotate twice or something. Very tough to tell what is really happening. What am I doing wrong? I don't want to duplicate all this code and spend hours customizing it, just to produce something I already have but turned horizontal. Erg! Help.
Option 1
To rotate everything just apply a transform to the element itself:
canvas.style.transform = "rotate(90deg)"; // or -90 depending on need
canvas.style.webkitTransform = "rotate(90deg)";
Option 2
Rotate context before drawing anything and before using any save(). Unlike the CSS version you will first need to translate to center, then rotate, and finally translate back.
You will need to make sure width and height of canvas is swapped before this is performed.
ctx.translate(ctx.canvas.width * 0.5, ctx.canvas.height * 0.5); // center
ctx.rotate(Math.PI * 0.5); // 90°
ctx.translate(-ctx.canvas.width * 0.5, -ctx.canvas.height * 0.5);
And of course, as an option 3, you can recalculate all your values to go along the other axis.
Look at the rotate function in this example. You want to do a translation to the point you want to rotate around.
example1();
example2();
function rotate(ctx, degrees, x, y, fn) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(degrees * (Math.PI / 180));
fn();
ctx.restore();
}
function rad(deg) {
return deg * (Math.PI / 180);
}
function example2() {
var can = document.getElementById("can2");
var ctx = can.getContext('2d');
var w = can.width;
var h = can.height;
function drawBattery() {
var percent = 60;
ctx.beginPath();
ctx.arc(35,50, 25,0,rad(360));
ctx.moveTo(35+percent+25,50);
ctx.arc(35+percent,50,25,0,rad(360));
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = "rgba(0,255,0,.5)";
ctx.arc(35,50,25,0,rad(360));
ctx.arc(35+percent,50,25,0,rad(360));
ctx.rect(35,25,percent,50);
ctx.fill();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "#666666";
ctx.moveTo(135,25);
ctx.arc(135,50,25, rad(270), rad(269.9999));
//ctx.moveTo(35,75);
ctx.arc(35,50,25,rad(270),rad(90), true);
ctx.lineTo(135,75);
ctx.stroke();
}
drawBattery();
can = document.getElementById("can3");
ctx = can.getContext('2d');
w = can.width;
h = can.height;
rotate(ctx, -90, 0, h, drawBattery);
}
function example1() {
var can = document.getElementById('can');
var ctx = can.getContext('2d');
var color1 = "#FFFFFF";
var color2 = "#FFFF00";
var color3 = "rgba(0,155,255,.5)"
var text = 0;
function fillBox() {
ctx.save();
ctx.fillStyle = color3;
ctx.fillRect(0, 0, can.width / 2, can.height);
ctx.restore();
}
function drawBox() {
ctx.save();
ctx.beginPath();
ctx.strokeStyle = ctx.fillStyle = color1;
ctx.rect(10, 10, 50, 180);
ctx.font = "30px Arial";
ctx.fillText(text, 25, 45);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = color2;
ctx.lineWidth = 10;
ctx.moveTo(10, 10);
ctx.lineTo(60, 10);
ctx.stroke();
ctx.restore();
}
fillBox();
rotate(ctx, 90, can.width, 0, fillBox);
text = "A";
drawBox();
color1 = "#00FFFF";
color2 = "#FF00FF";
text = "B";
rotate(ctx, 90, can.width, 0, drawBox);
centerRotatedBox()
function centerRotatedBox() {
ctx.translate(can.width / 2, can.height / 2);
for (var i = 0; i <= 90; i += 10) {
var radians = i * (Math.PI / 180);
ctx.save();
ctx.rotate(radians);
ctx.beginPath();
ctx.strokeStyle = "#333333";
ctx.rect(0, 0, 50, 50)
ctx.stroke();
ctx.restore();
}
}
}
#can,
#can2,
#can3 {
border: 1px solid #333333
}
<canvas id="can" width="200" height="200"></canvas>
<canvas id="can2" width="200" height="100"></canvas>
<canvas id="can3" width="100" height="200"></canvas>
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