I'm trying to draw a graphic pattern of lines from the black to the red depending on the Y value of a wave. To find out if I'm doing it right whit the approach, I started a test in JSFiddle:
Test
var j,k;
k=255;
var green=150;
var blue=150;
var canvas=document.getElementById('canvas');
var ctx=canvas.getContext('2d');
for(j=0;j<k;j++)
{
ctx.beginPath();
ctx.moveTo(j, 0);
ctx.lineTo(j, 150);
ctx.strokeStyle = "rgb("&j&", 0, 0)";
ctx.stroke();
}
But the result is just a grey tone in all the lines, although the drawing method is inside a loop and the 'red' value is changing.
Putting #Juhana's good suggestion into practice:
var j,k;
k=255;
var green=150;
var blue=150;
var canvas=document.getElementById('canvas');
var ctx=canvas.getContext('2d');
for(j=0;j<k;j++){
ctx.beginPath();
ctx.moveTo(j, 0);
ctx.lineTo(j, 150);
ctx.strokeStyle = "rgb("+j+",0,0)";
ctx.stroke();
}
Related
When creating an HTML canvas I was planning on making these cylinders and animating marbles moving inside them. However, when trying to do so it would just delete everything. After messing around with my code, I discovered the problem was due to the fillStyle which was a CanvasPattern from an image.
This snippet simulates exactly what I am experiencing. The rectangle draws perfectly fine, however, after 1 second, when the interval runs, it disappears and there is no arc or "marble" drawn. There are no errors in console either
With Interval (Not working):
let canv = document.getElementById("canvas");
let ctx = canv.getContext('2d');
let matte = new Image(canv.width, canv.height);
matte.onload = function() {
var pattern = ctx.createPattern(matte, 'repeat');
ctx.globalCompositeOperation = 'source-in';
ctx.rect(0, 0, canv.width, canv.height);
ctx.fillStyle = pattern;
ctx.fill();
};
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
ctx.lineWidth = "5";
ctx.fillRect(0, 0, 50, 50); // This dissapears when the setInterval runs???? Marble doesn't even draw
let x = 60,
y = 20;
var draw = setInterval(function() { // Drawing the marble
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
y += 1;
}, 1 * 1000);
<html>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
When I get rid of the interval it would work, but when the interval is there, nothing is drawn.
I have absolutely no idea why this is happening and I cannot find anything on the internet regarding this problem. Is there a way I can animate this marble while having the image continue to mask its fillStyle??
Without Interval (Working):
let canv = document.getElementById("canvas");
let ctx = canv.getContext('2d');
let matte = new Image(canv.width, canv.height);
matte.onload = function() {
var pattern = ctx.createPattern(matte, 'repeat');
ctx.globalCompositeOperation = 'source-in';
ctx.rect(0, 0, canv.width, canv.height);
ctx.fillStyle = pattern;
ctx.fill();
};
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
ctx.lineWidth = "5";
ctx.fillRect(0, 0, 50, 50); // This dissapears when the setInterval runs???? Marble doesn't even draw
let x = 60,
y = 20;
//var draw = setInterval(function() { // Drawing the marble
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
y += 1;
//}, 1 * 1000);
<html>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
Things I've tried:
Got rid of beginPath and closePath, doesn't make anything disappear but doesn't display arc
Recreating pattern inside the interval
Making the fillstyle a colour for everything (Works)
Making the fillstyle of the marble a colour (Doesnt work)
EDIT: After looking some more, I believe the problem is in the globalCompositeOperation. It's what deals with the pattern intersecting the drawing. When looking at all the types, source-in is the only one that satisfies my expected result, however, it's not working in this situation weirdly.
Thank you in advance
The problem is your ctx.globalCompositeOperation instruction. Using source-in, you're explicitly telling the canvas to make anything that's a different color from the new thing you're drawing (on a per pixel basis) transparent. Since every pixel is different, everything becomes transparent and you're left with what looks like an empty canvas (even if the ImageData will show RGBA data in which the RGB channels have meaningful content, but A is 0).
Remove the globalCompositeOperation rule and you're good to go, but you should probably take some time to rewrite the logic here, so that nothing happens until your image is loaded, because your code is pretty dependent on that pattern existing: wait for the image to load, the build the pattern, assign it to the context, and then start your draw loop.
const canv = document.getElementById("canvas");
const ctx = canv.getContext('2d');
let x = 60, y = 20;
function start() {
const matte = new Image(canv.width, canv.height);
matte.addEventListener(`load`, evt =>
startDrawing(ctx.createPattern(matte, 'repeat'))
);
matte.addEventListener(`load`, evt =>
console.error(`Could not load ${matte.src}...`);
);
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
}
function startDrawing(pattern) {
ctx.strokeStyle = `red`;
ctx.fillStyle = pattern;
setInterval(() => {
draw();
y += 10;
}, 1 * 1000);
}
function draw() {
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
// and kick everything off
start();
Although on another note, normally setInterval is not the best choice for animations: you usually want requestAnimationFrame instead, with a "wall time" check (e.g. doing things depending on the actual clock, instead of trusting intervals):
...
function startDrawing(pattern) {
ctx.strokeStyle = `red`;
ctx.fillStyle = pattern;
startAnimation();
}
let playing, lastTime;
function startAnimation() {
playing = true;
lastTime = Date.now();
requestAnimationFrame(nextFrame);
}
function stopAnimation() {
playing = false;
}
function nextFrame() {
let newTime = Date.now();
if (newTime - lastTime >= 1000) {
draw();
}
if (playing) {
lastTime = newTime;
requestAnimationFrame(nextFrame);
}
}
...
https://jsbin.com/wawecedeve/edit?js,output
Say I have created a polygon-shaped image by creating a shape in HTML5 canvas and then filling it with an image, e.g. as below:
Now I want to round the corners on this hexagon.
There is a lineJoin = "round" property available but this doesn't seem to work (I believe because the shape is filled and there is no outer line to round).
Does anyone have any idea how to do this with HTML5 canvas or any other means?
Here is the code used to create the image:
var ctx = document.getElementById('myCanvas').getContext('2d');
var a = ctx.canvas.width, r = a / 5;
var side = Math.sqrt((4/3) * r * r);
// Draw your image onto the canvas (here I'll just fill the
// surface with red
var img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/0/03/Mountain_Bluebird.jpg";
img.onload = function () {
var pattern = ctx.createPattern(img, "no-repeat");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, a, a);
// Switch the blending mode
ctx.globalCompositeOperation = 'destination-in';
// Draw the hexagon shape to mask the image
ctx.beginPath();
ctx.moveTo(0, side/2);
ctx.lineTo(0, 3*side/2);
ctx.lineTo(r, 2*side);
ctx.lineTo(2*r, 3*side/2);
ctx.lineTo(2*r, side/2);
ctx.lineTo(r, 0);
ctx.closePath();
ctx.fill();
};
<canvas width="1000" height="1000" id="myCanvas"></canvas>
Just change the order you draw in and then change globalCompositeOperation to 'source-in'.
I made some adjustments because some of the corners in your code were getting clipped off but didn't adjust the image position (I hope that's easy enough to do)
Preview
You need to adjust the image position by the way like I said
Snippet
var ctx = document.getElementById('myCanvas').getContext('2d');
var a = ctx.canvas.width, r = a / 5;
var side = Math.sqrt((4/3) * r * r) - 20;
// Draw your image onto the canvas (here I'll just fill the
// surface with red
var img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/0/03/Mountain_Bluebird.jpg";
img.onload = function () {
ctx.lineJoin = "round";
ctx.lineWidth = 50;
// Draw the hexagon shape to mask the image
ctx.beginPath();
ctx.moveTo(50, 50 + side/2);
ctx.lineTo(50, 50 + 3*side/2);
ctx.lineTo(50 + r, 50 + 2*side);
ctx.lineTo(50 + 2*r, 50 + 3*side/2);
ctx.lineTo(50 + 2*r, 50 + side/2);
ctx.lineTo(50 + r, 50);
ctx.closePath();
ctx.stroke();
ctx.fill();
// Switch the blending mode
ctx.globalCompositeOperation = 'source-in';
var pattern = ctx.createPattern(img, "no-repeat");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, a, a);
};
<canvas width="1000" height="1000" id="myCanvas"></canvas>
There is no automatic way of doing this. The canvas API is pretty low-level, so it doesn't know that you've drawn a shape and want to detect where all the edges are. What you can do, but it would be a bit of a hassle to do is use bezierCurveTo. This method takes size arguments and can create curves. Combine that with your lines, and you can create rounded corners.
bezierCurveTo
You could also add circles at all the corners, with the arc method.
I want to know how to rotate a line in canvas.
Say I have the canvas set-up.
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
ctx.closePath();
How do I rotate this line?
Thanks,
Alex
Here's how to rotate a line segment between p1 & p2 around that segment's midpoint:
The idea is to:
Save the unrotated state of the context using context.save
Set the rotation point at the midpoint of the line using context.translate
Rotate to a specified radian angle using context.rotate
Draw the line. This is the tricky part...Since the canvas is already rotated and since the canvas origin is now the line's midpoint, you must moveTo minus the line's length/2 and lineTo the lines length/2: context.moveTo(-length/2,0); and context.lineTo(length/2,0);
Restore the context to its unrotated state with context.restore
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var p1={x:75,y:75};
var p2={x:150,y:150};
var dx=p2.x-p1.x;
var dy=p2.y-p1.y;
var length=Math.sqrt(dx*dx+dy*dy);
var angle=Math.atan2(dy,dx);
var midX=(p2.x+p1.x)/2;
var midY=(p2.y+p1.y)/2;
console.log(midX,midY);
draw(angle);
requestAnimationFrame(animate);
function animate(time){
requestAnimationFrame(animate);
draw(angle);
angle+=Math.PI/30;
}
function draw(radianAngle){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.translate(midX,midY);
ctx.rotate(radianAngle);
ctx.beginPath();
ctx.strokeStyle='red';
ctx.moveTo(-length/2,0);
ctx.lineTo(length/2,0);
ctx.stroke();
ctx.restore();
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
Note: This code shows rotation around your line's midpoint, but you can rotate around any point by using context.translate(anyRotationPointX,anyRotationPointY);
To rotate anything you can use context method - rotate().
We can't rotate line like object (like in SVG), but we can - redraw canvas with new rotated line.
var canvas = document.getElementById("example"),
ctx = example.getContext('2d'),
canvasWidth = canvas.width,
canvasHeight = canvas.height,
p1 = {x:canvasWidth/2+50,y:canvasHeight/2},
p2 = {x:p1.x,y:p1.y+100},
middlePoint = {x:(p1.x+p2.x)/2,y:(p1.y+p2.y)/2};
function rotate(degree,rotatePoint,drFunc) {
rotatePoint = rotatePoint || {x:canvasWidth/2,y:canvasHeight/2};
// Clear the canvas
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// Move registration point to the center of the canvas
ctx.translate(rotatePoint.x, rotatePoint.y);
// Rotate 1 degree
ctx.rotate((Math.PI / 180)*degree);
// Move registration point back to the top left corner of canvas
ctx.translate(-rotatePoint.x, -rotatePoint.y);
drFunc();
}
function drFunc(){
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
ctx.closePath();
}
rotate(1,middlePoint,drFunc);
Fiddle
I want to draw a line with one color, and the next line with a different color. But when I call stroke() the second time, the first line is draw again! How can I avoid it? Here's my code:
var canv = document.getElementById("canvas");
var ctx = canv.getContext("2d");
ctx.moveTo(0,0);
ctx.lineTo(100,100);
ctx.strokeStyle = "#FF0000";
ctx.stroke();
ctx.moveTo(100,100);
ctx.lineTo(100,200);
ctx.strokeStyle = "#999999";
ctx.stroke();
Thanks in advance!
Just insert a beginPath() in there:
var canv = document.getElementById("canvas");
var ctx = canv.getContext("2d");
ctx.moveTo(0,0);
ctx.lineTo(100,100);
ctx.strokeStyle = "#FF0000";
ctx.stroke();
cxt.beginPath(); // <---
ctx.moveTo(100,100);
ctx.lineTo(100,200);
ctx.strokeStyle = "#999999";
ctx.stroke();
That will reset your path. A stroke just strokes what exists on the path but does not clear it. You will have to manually reset the path for each new shape you want to draw.
The advantage is that you can reuse the path for fill, clip and point testing. The disadvantage is that it's easy to forget sometimes.
I am trying to change the color of line drawn on canvas dynamically...
ctx.moveTo(0, 0);
ctx.lineTo(0, 200);
ctx.strokeStyle = "Grey"
It could be mouseover event or press botton or mouse click event, I want to change the color of line or make it bold. Is it possible to change the color by adding event or is it possible to give style on an event on particular element?
Very close. In a sense, you can't really "change" the color of an element on the canvas because it has no scene graph, or, in other words, it has no history of what has been drawn on the canvas. To change the color of a line, you would have to redraw the line.
ctx.moveTo(0, 0);
ctx.lineTo(0, 200);
ctx.strokeStyle = "Grey";
ctx.stroke();
// To make the line bold and red
ctx.moveTo(0, 0);
ctx.lineTo(0, 200);
ctx.strokeStyle = "Red";
ctx.lineWidth = 5;
ctx.stroke();
If the canvas had a more complex scene going on, you would have to redraw the entire scene. There are numerous Javascript libraries that extend the base features of the canvas tag, and provide other drawing capabilities. You may want to take a look at Processing, it looks quite impressive.
I was having the same problem, I did it by moving another line with another color of different canvas element, so it gives appearance of line changing its color dynamically.
function drawGreyLine() {
ctx1.clearRect(0, 0, WIDTH, HEIGHT);
ctx1.strokeStyle = "Grey"; // line color
ctx1.moveTo(0, 0);
ctx1.moveTo(0, 200);
ctx1.lineTo(200, 200);
}
function drawColorLine() {
x += dx;
if (x <= 200) {
ctx2.beginPath();
ctx2.lineWidth = 5;
ctx2.lineCap = "round";
ctx2.strokeStyle = "sienna"; // line color
ctx2.moveTo(0, 200);
ctx2.lineTo(x, 200);
ctx2.moveTo(200, 200);
ctx2.stroke();
}
}
Hope this solves your problem.... :)
var canvas = document.getElementById('canvas');
var ctx=canvas.getContext('2d');
var line1={x:10,y:10, l:40, h:1}
var down=false;
var mouse={x:0,y:0}
canvas.onmousemove=function(e){ mouse={x:e.pageX-this.offsetLeft,y:e.pageY-this.offsetTop};
this.onmousedown=function(){down=true};
this.onmouseup=function(){down=false} ;
}
setInterval(function(){
ctx.clearRect(0,0,canvas.width,canvas.height)
ctx.beginPath()
ctx.moveTo(line1.x,line1.y)
ctx.lineTo(line1.x +line1.l,line1.y)
ctx.lineTo(line1.x +line1.l,line1.y+line1.h)
ctx.lineTo(line1.x,line1.y+line1.h)
ctx.isPointInPath(mouse.x,mouse.y)? (ctx.fillStyle ="red",line1.x=down?mouse.x:line1.x,line1.y=down?mouse.y:line1.y):(ctx.fillStyle ="blue")
ctx.fill()
},100)