I need to use several canvas-es with different values (see data-percent) with same reusable code block but "animation" makes it a little bit tricky. Im not sure how to make it reusable. Copy-pasting the same code over and over again is obviously a wrong move, I usually avoid it at any cost.
First thing is obviously to remove id and use class instead, then I could select all the canvas-es:
<canvas class="circle-thingy" width="120" height="120" data-percent="75"></canvas>
<canvas class="circle-thingy" width="120" height="120" data-percent="23"></canvas>
<canvas class="circle-thingy" width="120" height="120" data-percent="89"></canvas>
var allCircles = document.getElementsByClassName('circle-thingy');
But now comes the trickier part.. How about canvas JavaScript code? There's probably a very easy solution but I can't see it! Terrible time to quit smoking I guess (as always), brain is like shut down.
What I tried: for loop with allCircles list. Problem is that I cannot use setInterval and clearTimeout with this approach. Dynamic variable names? How do I reference them later?
Here's my code with a single circle, try it.
// Get canvas context
var ctx = document.getElementById('my-circle').getContext('2d');
// Current percent
var currentPercent = 0;
// Canvas north (close enough)
var start = 4.72;
// Dimensions
var cWidth = ctx.canvas.width;
var cHeight = ctx.canvas.height;
// Desired percent -> comes from canvas data-* attribute
var finalPercent = ctx.canvas.getAttribute('data-percent');
var diff;
function circle() {
diff = ((currentPercent / 100) * Math.PI * 2 * 10).toFixed(2);
ctx.clearRect(0, 0, cWidth, cHeight);
ctx.lineWidth = 3;
// Bottom circle (grey)
ctx.strokeStyle = '#eee';
ctx.beginPath();
ctx.arc(60, 60, 55, 0, 2 * Math.PI);
ctx.stroke();
// Percent text
ctx.fillStyle = '#000';
ctx.textAlign = 'center';
ctx.font="900 10px arial";
ctx.fillText(currentPercent + '%', cWidth * 0.5, cHeight * 0.5 + 2, cWidth);
// Upper circle (blue)
ctx.strokeStyle = '#0095ff';
ctx.beginPath();
ctx.arc(60, 60, 55, start, diff / 10 + start);
ctx.stroke();
// If has desired percent -> stop
if( currentPercent >= finalPercent) {
clearTimeout(myCircle);
}
currentPercent++;
}
var myCircle = setInterval(circle, 20);
<canvas id="my-circle" width="120" height="120" data-percent="75"></canvas>
Feel free to use this code snippet in your own projects.
You can use bind to solve this.
Create a helper function that will start animation for given canvas:
function animateCircle(canvas) {
var scope = {
ctx: canvas.getContext('2d')
// other properties, like currentPercent, finalPercent, etc
};
scope.interval = setInterval(circle.bind(scope), 20);
}
Change your circle function to refer variables from this instead of global ones:
function circle() {
// your old code with corresponding changes
// e.g.
var ctx = this.ctx; // references corresponding scope.ctx
// or
this.currentPercent++; // references corresponding scope.currentPercent
}
Working JSFiddle, if something is not clear.
Related
I am trying to draw parallel lines on a canvas. With one of the lines being fixed. The user inputs the distance between two lines and hence the second line is positioned accordingly. I am new to JavaScript. Please help how should I change the position of second line with user input.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
console.log('myCanvas');
//Fixed Line
ctx.beginPath();
ctx.moveTo(50,200);
ctx.lineTo(300,200);
ctx.strokeStyle='white';
ctx.stroke();
//moving line
ctx.beginPath();
ctx.moveTo(50,250);
ctx.lineTo(300,250);
ctx.strokeStyle='white';
ctx.stroke();
Parallel line
To draw a line parallel to an existing line.
Get the vector from start to end of the line.
Use that vector to get the length of the line
Divide the offset distance by the length of the line to get offset scale
Scale the line vector by the offset scale
Add the scaled vector to the ends of the line and draw.
See example function drawLine
Get input
To get a value from an input element use the elements value property.
To get the value when it changes, add an event listener using the elements addEventListener function. . Do not assign a listener directly to the elements event property eg Avoid doing myElement.oninput = ()=> {/* ... code */};
There are a variety of input events. You can use one or more according to your needs. In this case there are two events change and input. See example.
change fires when the user commits a change to the value
input fires when there is any change to the input value
Always assign an input value a meaningfully value, do not leave it empty if empty has no meaning.
Example
const ctx = myCanvas.getContext("2d");
const myLine = {
from: {x: 50, y: 50},
to: {x: 150, y: 200},
style: {strokeStyle: "#000", lineWidth: 2}
};
distanceElement.addEventListener("input", inputEvent);
var lineOffset = distanceElement.value;
drawLines();
function inputEvent(e) {
lineOffset = e.target.value;
drawLines();
}
function drawLines() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawLine(myLine);
lineOffset !== 0 && drawLine(myLine, lineOffset);
}
function drawLine(line, offset = 0) {
var [ox, oy] = [0, 0];
Object.assign(ctx, line.style);
if (offset) {
const [dx, dy] = [line.from.x - line.to.x, line.from.y - line.to.y];
const scale = offset / (dx * dx + dy * dy) ** 0.5;
[ox, oy] = [-dy * scale, dx * scale];
}
ctx.beginPath();
ctx.lineTo(ox + line.from.x, oy + line.from.y);
ctx.lineTo(ox + line.to.x, oy + line.to.y);
ctx.stroke();
}
input {display:block;}
<label for="distanceElement">Line offset distance:</label>
<input id="distanceElement" placeholder="Enter Distance" type="number" value="0">
<canvas id="myCanvas" width="250" height="250"></canvas>
Edit:
While this answer will work for some people, this answer works better for more situations (notably, it supports diagonals) and has a more thorough explanation of what is going on.
Old Answer:
You can use the oninput event to run every time the input is typed in. Here is an example:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var input = document.getElementById("distance");
input.oninput = ()=>{
// clear
ctx.clearRect(0, 0, c.width, c.height);
// draw fixed line
ctx.beginPath();
ctx.moveTo(50,0);
ctx.lineTo(50,200);
ctx.strokeStyle='black';
ctx.stroke();
let value = parseFloat(input.value);
// draw moved line
ctx.beginPath();
ctx.moveTo(50+value,0);
ctx.lineTo(50+value,250);
ctx.strokeStyle='black';
ctx.stroke();
}
input {
display:block;
}
<canvas id="myCanvas" width="150" height="150"></canvas>
<input id="distance" placeholder="Enter Distance Here (in px)" type="number">
I want to create a canvas, and draw a line that makes turns and draw a musical note (a Sol Key actually) then goes off on the right side of the canvas.
I have searched and found a script that draw a line - but so far the only thing it does is to draw a line. Every attempt to modify it in order to create curves and figures went horribly wrong.
The code I have:
var canvas = $("#paper")[0];
var c = canvas.getContext("2d");
var h = canvas.height;
var startX = 10;
var startY = h/2;
var endX = 500;
var endY = h/2;
var amount = 0;
setInterval(function() {
amount += 0.002; // change to alter duration
if (amount > 1) amount = 1;
c.clearRect(0, 0, canvas.width, canvas.height);
c.strokeStyle = "black";
c.moveTo(startX, startY);
c.lineTo(startX + (endX - startX) * amount, startY + (endY - startY) * amount);
c.stroke();
}, 10);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="paper" width="800" height="600" border="1"></canvas>
Ok, this is a pretty whide question because there is a lot of ways to do it. In the example below at JSFiddle I used an external Canvas helper to make the code easier to read.
I did not use interpolation due to no need for it, so the animating is pretty simple.
How its animating the sol:
Get the SVG path of the sol
Parse it and make it easier readable
Start animating the first path item - wait - and go to the next one
And after a few seconds you have your sol!
Using
const canvas = new Canvas(); // Canvas.js
The draw loop and the update loop are pretty obvious...
JSFiddle: https://jsfiddle.net/y0g2q33g/
Note The code in this fiddle is not optimized at all :)
I've been working on a game that's sort of a Worms clone. In it, the player rotates a cannon with the up up and down keys (it's a 2D game) to fire at enemies coming from above. I use the context.rotate() and context.translate() methods when drawing the cannon, then immediately context.restore() back to the default canvas.The cannon is the only thing (for now) that's rotated.
The problem is, I want to accurately show projectiles coming from the top of the cannon. For this, I need to know the top of the cannon's coordinates at all times. Normally, this is something I could easily calculate. However, because the canvas is rotated only before the cannon is drawn, it's not as simple.
Just use simple trigonometry to track the top:
var canonTopX = pivotX + Math.cos(angleInRadians) * canonLength;
var canonTopY = pivotY + Math.sin(angleInRadians) * canonLength;
You can choose to render the canon using transformations of course, or share the math.
ctx.translate(pivotX, pivotY);
ctx.rotate(angleInRadians);
//render canon from (0,0) pointing right (0°)
ctx.setTransform(1,0,0,1,0,0); // instead of save/restore
// calc canon top for projectiles here
var ctx = c.getContext("2d");
var canonLength = 70;
var angleInRadians = 0;
var angleStep = 0.03;
var pivotX = ctx.canvas.width>>1;
var pivotY = ctx.canvas.height>>1;
ctx.fillStyle = "#000";
ctx.strokeStyle = "#c00";
(function loop() {
angleInRadians += angleStep;
render();
requestAnimationFrame(loop);
})();
function render() {
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
ctx.translate(pivotX, pivotY);
ctx.rotate(angleInRadians);
ctx.fillRect(0, -5, canonLength, 10);
ctx.setTransform(1,0,0,1,0,0); // instead of save/restore
var canonTopX = pivotX + Math.cos(angleInRadians) * canonLength;
var canonTopY = pivotY + Math.sin(angleInRadians) * canonLength;
ctx.beginPath();
ctx.arc(canonTopX, canonTopY, 9, 0, 6.3);
ctx.stroke();
}
<canvas id=c width=600 height=180></canvas>
#.ctx.lineWidth = 20
#.ctx.moveTo(i.x, i.y)
#.ctx.arc(i.x, i.y, 3, 0, Math.PI * 2)
Any reason why that code would make the image above?
I tried your version of the arc, and I find it difficult to understand what you are acctually asking. Therefore I made two versions, in order to visually show you what's happening.
You can look at them here!
UPDATED JSFIDDLE
http://jsfiddle.net/hqB6b/2/
HTML
First with the line inside.
<canvas id="ex" width="300" height="300">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
Second with NO line inside!
<canvas id="example" width="300" height="300">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
JS
var example = document.getElementById('example');
var ctx = example.getContext('2d');
var i = {x:100,
y:100}
ctx.strokeStyle = '#ff0000';
ctx.lineWidth = 1;
ctx.moveTo(i.x, i.y)
//HERE BEGINPATH IS USED AFTER MOVETO
ctx.beginPath();
ctx.arc(i.x, i.y, 50, 0, Math.PI * 2)
ctx.stroke();
var ex = document.getElementById('ex');
var ct = ex.getContext('2d');
var i = {x:100,
y:100}
ct.strokeStyle = '#ff0000';
ct.lineWidth = 1;
//HERE BEGINPATH IS USED BEFORE MOVETO
ct.beginPath();
ct.moveTo(i.x, i.y)
ct.arc(i.x, i.y, 50, 0, Math.PI * 2)
ct.stroke();
use beginPath before creating a path, and use closePath after creating it.
Since closePath... closes the path back to the first point, you might want stroke or fill before or after closing the path depending on what you seek.
I have a black canvas with things being drawn inside it. I want the things drawn inside to fade to black, over time, in the order at which they are drawn (FIFO). This works if I use a canvas which hasn't been resized. When the canvas is resized, the elements fade to an off-white.
Question: Why don't the white specks fade completely to black when the canvas has been resized? How can I get them to fade to black in the same way that they do when I haven't resized the canvas?
Here's some code which demonstrates. http://jsfiddle.net/6VvbQ/35/
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0, 0, 300, 150);
// Comment this out and it works as intended, why?
canvas.width = canvas.height = 300;
window.draw = function () {
context.fillStyle = 'rgba(255,255,255,1)';
context.fillRect(
Math.floor(Math.random() * 300),
Math.floor(Math.random() * 150),
2, 2);
context.fillStyle = 'rgba(0,0,0,.02)';
context.fillRect(0, 0, 300, 150);
setTimeout('draw()', 1000 / 20);
}
setTimeout('draw()', 1000 / 20);
The problem is two-parted:
There is a (rather known) rounding error when you draw with low alpha value. The browser will never be able to get the resulting mix of the color and alpha channel equal to 0 as the resulting float value that is mixed will be converted to integer at the time of drawing which means the value will never become lower than 1. Next time it mixes it (value 1, as alpha internally is a value between 0 and 255) will use this value again and it get rounded to again to 1, and forever it goes.
Why it works when you have a resized canvas - in this case it is because you are drawing only half the big canvas to the smaller which result in the pixels being interpolated. As the value is very low this means in this case the pixel will turn "black" (fully transparent) as the average between the surrounding pixels will result in the value being rounded to 0 - sort of the opposite than with #1.
To get around this you will manually have to clear the spec when it is expected to be black. This will involve tracking each particle/spec yourselves or change the alpha using direct pixel manipulation.
Update:
The key is to use tracking. You can do this by creating each spec as a self-updating point which keeps track of alpha and clearing.
Online demo here
A simple spec object can look like this:
function Spec(ctx, speed) {
var me = this;
reset(); /// initialize object
this.update = function() {
ctx.clearRect(me.x, me.y, 1, 1); /// clear previous drawing
this.alpha -= speed; /// update alpha
if (this.alpha <= 0) reset(); /// if black then reset again
/// draw the spec
ctx.fillStyle = 'rgba(255,255,255,' + me.alpha + ')';
ctx.fillRect(me.x, me.y, 1, 1);
}
function reset() {
me.x = (ctx.canvas.width * Math.random())|0; /// random x rounded to int
me.y = (ctx.canvas.height * Math.random())|0; /// random y rounded to int
if (me.alpha) { /// reset alpha
me.alpha = 1.0; /// set to 1 if existed
} else {
me.alpha = Math.random(); /// use random if not
}
}
}
Rounding the x and y to integer values saves us a little when we need to clear the spec as we won't run into sub-pixels. Otherwise you would need to clear the area around the spec as well.
The next step then is to generate a number of points:
/// create 100 specs with random speed
var i = 100, specs = [];
while(i--) {
specs.push(new Spec(ctx, Math.random() * 0.015 + 0.005));
}
Instead of messing with FPS you simply use the speed which can be set individually per spec.
Now it's simply a matter of updating each object in a loop:
function loop() {
/// iterate each object
var i = specs.length - 1;
while(i--) {
specs[i].update(); /// update each object
}
requestAnimationFrame(loop); /// loop synced to monitor
}
As you can see performance is not an issue and there is no residue left. Hope this helps.
I don't know if i have undertand you well but looking at you fiddle i think that, for what you are looking for, you need to provide the size of the canvas in any iteration of the loop. If not then you are just taking the initial values:
EDIT
You can do it if you apply a threshold filter to the canvas. You can run the filter every second only just so the prefromanece is not hit so hard.
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0,0,300,150);
//context.globalAlpha=1;
//context.globalCompositeOperation = "source-over";
var canvas2 = document.getElementById('canvas2');
var context2 = canvas2.getContext('2d');
canvas2.width=canvas2.height=canvas.width;
window.draw = function(){
var W = canvas2.width;
var H = canvas2.height;
context2.fillStyle='rgba(255,255,255,1)';
context2.fillRect(
Math.floor(Math.random()*W),
Math.floor(Math.random()*H),
2,2);
context2.fillStyle='rgba(0,0,0,.02)';
context2.fillRect(0,0,W,H);
context.fillStyle='rgba(0,0,0,1)';
context.fillRect(0,0,300,150);
context.drawImage(canvas2,0,0,300,150);
setTimeout('draw()', 1000/20);
}
setTimeout('draw()', 1000/20);
window.thresholdFilter = function () {
var W = canvas2.width;
var H = canvas2.height;
var i, j, threshold = 30, rgb = []
, imgData=context2.getImageData(0,0,W,H), Npixels = imgData.data.length;
for (i = 0; i < Npixels; i += 4) {
rgb[0] = imgData.data[i];
rgb[1] = imgData.data[i+1];
rgb[2] = imgData.data[i+2];
if ( rgb[0] < threshold &&
rgb[1] < threshold &&
rgb[2] < threshold
) {
imgData.data[i] = 0;
imgData.data[i+1] = 0;
imgData.data[i+2] = 0;
}
}
context2.putImageData(imgData,0,0);
};
setInterval("thresholdFilter()", 1000);
Here is the fiddle: http://jsfiddle.net/siliconball/2VaLb/4/
To avoid the rounding problem you could extract the fade effect to a separate function with its own timer, using longer refresh interval and larger alpha value.
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0, 0, 300, 150);
// Comment this out and it works as intended, why?
canvas.width = canvas.height = 300;
window.draw = function () {
context.fillStyle = 'rgba(255,255,255,1)';
context.fillRect(
Math.floor(Math.random() * 300),
Math.floor(Math.random() * 300),
2, 2);
setTimeout('draw()', 1000 / 20);
}
window.fadeToBlack = function () {
context.fillStyle = 'rgba(0,0,0,.1)';
context.fillRect(0, 0, 300, 300);
setTimeout('fadeToBlack()', 1000 / 4);
}
draw();
fadeToBlack();
Fiddle demonstrating this: http://jsfiddle.net/6VvbQ/37/