Related
i want to draw a simple path in canvas like this:
i have allready tried (also im not sure how to create the point with a radius at 440, 400):
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(500, 0);
ctx.lineTo(440, 400);
ctx.lineTo(0, 500);
ctx.lineTo(0, 0);
ctx.fill();
but it shows me just a black 600x600 rectangle
https://jsfiddle.net/aaroniker/pmgkymch/
Thanks!
Canvas elements contain rasterized pixel data for an image of the same dimensions as those of the canvas element's width and height attributes, which default to 300 and 150 respectively. Drawing to a canvas element uses pixel coordinates of the canvas unless a context drawing transform is in effect.
Setting width and height of a canvas element in CSS scales the canvas to the dimensions set in CSS when presenting it on screen. As with scaling an ordinary image element, canvas image quality can drop noticeably if a small canvas is scaled to too large a size.
Answer A
You are seeing a black square because you drew onto a 300 x 150 pixel canvas using 600 x 600 coordinates. Filling the oversized path drawn filled in all the actual canvas pixels. The 300 x 150 pixel canvas was presented as a 600 x 600 screen area due to CSS scaling.
Answer B
The context's path drawing arcto method is used to create a rounded corner but you don't need to work out where it leaves the drawing position provided you continue by drawing a line to a known point.
In this example I've set the canvas element dimensions in HTML to 600 x 600, and scaled it to a different size (150px x 150px) in CSS
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
var radius = 100; // a number
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(500, 0);
ctx.arcTo( 440, 400, 0, 500, radius)
ctx.lineTo( 0, 500); // join end of arc to next point
ctx.lineTo(0, 0);
ctx.fill();
}
}
draw();
#canvas {
width: 150px;
height: 150px;
}
<canvas id="canvas" width="600", height="600"></canvas>
As I stated in my comment, the coordinate system gets deformed when you define canvas dimensions in CSS. Use either inline styling (as I've done) or code it into your JS. For the arc you need, use ctx.arcTo(x1, x2, y1, y2, r), where x1, y1 is the point you are arcing around (440, 400 in your case) and x2,y2 is where you want the arc to meet back up with your figure, r is the radius.
https://www.w3schools.com/tags/canvas_arcto.asp
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(500, 0);
ctx.lineTo(441.2, 392);
ctx.arcTo(440, 400, 431.2, 402, 8);
ctx.lineTo(0, 500);
ctx.lineTo(0, 0);
ctx.fillStyle = "#008AFF";
ctx.fill();
}
}
draw();
<canvas height="600" id="canvas" width="600"></canvas>
I am trying to understand why my circle is not in the middle of my Canvas in HTML 5.
I am trying to create a circle in the middle the canvas.
The canvas is as follows:
Canvas:
Width: 600
Height: 300
Then I draw a circle with the following code:
context.beginPath();
context.fillStyle = '#424';
context.arc(300, 150, 10, 0, 2*Math.PI, false);
context.fill();
context.closePath();
The circle is drawn inthe lower right corner.
Now if I change the (x, y) to (150, 75) then it shows in the middle.
I am just hoping someone can shed a little light on why the original code doesn't work.
You are not showing how you are setting the actual width and height of the canvas, but if the center point always is 150, 75 then the canvas is always at default size which means you are probably setting the size using css instead of directly on the element.
Try something like this:
<canvas id="myCanvas" width=600 height=300></canvas>
in JavaScript:
canvas.width = 600; // don't use canvas.style.*
canvas.height = 300;
You could also set the center this way for the arc (to make it adopt center automatically):
context.arc(context.canvas.width * 0.5, context.canvas.height * 0.5,
10, 0, 2*Math.PI, false);
I want to place a number of light sources on a background for a game I'm making, which works great with one light source as shown below:
This is achieved by placing a .png image above everything else that becomes more transperant towards the center, like this:
Works great for one light source, but I need another approach where I can add more and move the light sources around.
I have considered drawing a similar "shadow layer" pixel by pixel for each frame, and calculate the transparency depending of the distance to each light source. However, that would probably be very slow and I'm sure there are way better solutions to this problem.
The images are just examples and each frame will have considerably more content to move around and update using requestAnimationFrame.
Is there a light weight and simple way to achieve this? Thanks in advance!
Edit
With the help of ViliusL, I came up with this masking solution:
http://jsfiddle.net/CuC5w/1/
// Create canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 300;
document.body.appendChild(canvas);
// Draw background
var img=document.getElementById("cat");
ctx.drawImage(img,0,0);
// Create shadow canvas
var shadowCanvas = document.createElement('canvas');
var shadowCtx = shadowCanvas.getContext('2d');
shadowCanvas.width = canvas.width;
shadowCanvas.height = canvas.height;
document.body.appendChild(shadowCanvas);
// Make it black
shadowCtx.fillStyle= '#000';
shadowCtx.fillRect(0,0,canvas.width,canvas.height);
// Turn canvas into mask
shadowCtx.globalCompositeOperation = "destination-out";
// RadialGradient as light source #1
gradient = shadowCtx.createRadialGradient(80, 150, 0, 80, 150, 50);
gradient.addColorStop(0, "rgba(255, 255, 255, 1.0)");
gradient.addColorStop(1, "rgba(255, 255, 255, .1)");
shadowCtx.fillStyle = gradient;
shadowCtx.fillRect(0, 0, canvas.width, canvas.height);
// RadialGradient as light source #2
gradient = shadowCtx.createRadialGradient(220, 150, 0, 220, 150, 50);
gradient.addColorStop(0, "rgba(255, 255, 255, 1.0)");
gradient.addColorStop(1, "rgba(255, 255, 255, .1)");
shadowCtx.fillStyle = gradient;
shadowCtx.fillRect(0, 0, canvas.width, canvas.height);
Another way to play with light is to use the globalCompositeOperation mode 'ligther' to ligthen things, and just use globalAlpha to darken things.
First here's an image, with a cartoon lightening on the left, and a more realistic lightening on the right, but you'd rather watch the fiddle, since it's animated :
http://jsfiddle.net/gamealchemist/ABfVj/
So how i did things :
To darken :
- Choose a darkening color( most likely black, but you can choose a red or another color to teint the result).
- choose an opacity ( 0.3 seems a good start value ).
- fillRect the area you want to darken.
function darken(x, y, w, h, darkenColor, amount) {
ctx.fillStyle = darkenColor;
ctx.globalAlpha = amount;
ctx.fillRect(x, y, w, h);
ctx.globalAlpha = 1;
}
To lighten :
- Choose a lightening color. Beware that this color's r,g,b will be added to the previous point's r,g,b : if you use a high value your color will get burnt.
- change the globalCompositeOperation to 'lighter'
- you might change opacity also, to have more control over the lightening.
- fillRect or arc the area you want to lighten.
If you draw several circles while in lighter mode, the results will add up, so you can choose a quite low value and draw several circles.
function ligthen(x, y, radius, color) {
ctx.save();
var rnd = 0.03 * Math.sin(1.1 * Date.now() / 1000);
radius = radius * (1 + rnd);
ctx.globalCompositeOperation = 'lighter';
ctx.fillStyle = '#0B0B00';
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * π);
ctx.fill();
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius * 0.90+rnd, 0, 2 * π);
ctx.fill();
ctx.beginPath();
ctx.arc(x, y, radius * 0.4+rnd, 0, 2 * π);
ctx.fill();
ctx.restore();
}
Notice that i added a sinusoidal variation to make the light more living.
Ligthen : another way :
You can also, while still using the 'ligther' mode, use a gradient to have a smoother effect (first one is more cartoon like, unless you draw a lot of circles.).
function ligthenGradient(x, y, radius) {
ctx.save();
ctx.globalCompositeOperation = 'lighter';
var rnd = 0.05 * Math.sin(1.1 * Date.now() / 1000);
radius = radius * (1 + rnd);
var radialGradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
radialGradient.addColorStop(0.0, '#BB9');
radialGradient.addColorStop(0.2 + rnd, '#AA8');
radialGradient.addColorStop(0.7 + rnd, '#330');
radialGradient.addColorStop(0.90, '#110');
radialGradient.addColorStop(1, '#000');
ctx.fillStyle = radialGradient;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * π);
ctx.fill();
ctx.restore();
}
i also added here a sin variation.
Rq : creating a gradient on each draw will create garbage : store the gradient if you use a single gradient, and store them in an array if you want to animate the gradients.
If you are using the same light in several places, have a single gradient built, centered on (0,0), and translate the canvas before drawing always with this single gradient.
Rq 2 : you can use clipping to prevent some parts of the screen to be lightened (if there's an obstacle).
I added the blue circle on my example to show this.
So you might want to ligthen directly your scene with those effects, or create separately a light layer that you darken/lighten as you want before drawImage it on the screen.
There are too many scenari to discuss them here (light animated or not, clipping or not, pre-compute a light layer or not, ...) but as far as speed is concerned, for Safari and iOS safari, the solution using rect/arc draws -either with gradient or a solid fill- will be rocket faster than drawing an image/canvas.
On Chrome it will be quite the opposite : it's faster to draw an image than to draw each geometry when the geometry count raises.
Firefox is rather similar to Chrome for this.
your png should have full transparent corners and not transparent white in middle.
or you can draw this, but not pixel by pixel like here jsfiddle.net/pr9r7/2/
More examples: jsfiddle.net/pr9r7/3/ http://codepen.io/cwolves/pen/prvnb
Here is my Take on it:
A. Don't worry about performance until you have tried it out. The Canvas is pretty darn fast at drawing.
B. Rather than having a image with dark Corners and a Transparent middle. Why don't you try and make it more "IRL" and have the overall world be more Dark and let the light-source illuminate the Area? Highlight a small area, instead of darken everything EXCEPT a small Area.
I've created a canvas element and set it's width and height.
Then I've set the border-radius on the ID of the canvas so that the canvas looks like a circle.
However, if I draw something outside the circle area, it'll still draw it, as shown on my example code :
http://jsfiddle.net/mN9Eh/
JavaScript :
<script>
function animate() {
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.save();
ctx.clearRect(0, 0, c.width, c.height);
if(i > 80) {
i = 1;
}
if( i > 40) {
ctx.beginPath();
ctx.arc(50, 50, i-40, 0, 2 * Math.PI, true);
ctx.fillStyle = "#FF0033";
ctx.fill();
}
i++;
ctx.restore();
setTimeout(animate, 10);
}
var i = 0;
animate();
</script>
CSS :
#myCanvas {
background: #333;
border-radius: 300px;
}
HTML :
<canvas id="myCanvas" width="300" height="300"></canvas>
I remember reading something that you can't apply CSS transformations to canvas elements as it won't know about them (i.e. setting width in the CSS instead of the element didn't work right). How would I fix my canvas element to appear as a circle that doesn't allow drawing outside the circle (or at least doesn't appear for users if drawn outside the circle).
Use the circle to create a "clipping path" for all subsequent drawing actions.
var cx = c.width / 2;
var cy = c.height / 2;
var r = Math.min(cx, cy);
ctx.beginPath();
ctx.arc(cx, cy, r, 0, 2 * Math.PI);
ctx.clip();
See http://jsfiddle.net/alnitak/MvSB2/
Note that there's a bug in Chrome which prevents the clipping mask edge from being antialiased, although it seems that your border-radius hack prevents that from looking as bad as it might.
Try using a clipping mask:
ctx.beginPath();
ctx.arc(150,150,150,0,360,false);
ctx.clip();
The final code that worked for me was:
<canvas id="bg-admin-canvas" width="500" height="500" style="margin:15px; background:#09F;"></canvas>
<script>
var postit = function(width,height,angle){
var canvas = document.getElementById("bg-admin-canvas");
var ctx = canvas.getContext("2d");
var radians = angle * Math.PI / 180;
var move = width*Math.sin(radians);
if(angle < 0 ){ ctx.translate(0,-move); }else{ ctx.translate(move,0); }
ctx.rotate(radians);
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0.05,"rgba(0,0,0,0)");
gradient.addColorStop(0.5,"rgba(0,0,0,0.3)");
ctx.fillStyle = gradient;
ctx.fillRect(0,0,width,height);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(width, 0);
ctx.lineTo(width,height);
ctx.lineTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.closePath();
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0,'#f7f8b9');
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
ctx.beginPath();
ctx.moveTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.quadraticCurveTo(width*.05,height-height*.05,width*.1,height-height*.1);
ctx.quadraticCurveTo(width*.1,height-height*.07,width-width*.8,height-height*.02);
ctx.closePath();
ctx.fillStyle = '#ffffff';
ctx.fill();
var gradient = ctx.createLinearGradient(0,height,width*.1,height-height*.1);
gradient.addColorStop(0,"rgba(222,222,163,0.8)");
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
}
postit(300, 300, 10);
</script>
Hi,
I made a quick and dirty "post-it" note with html5's canvas and some js.
I want to be able to rotate them anyway I want so I tried to use the translate. The example below I have a translate of 0,250 just so you could see the whole thing.
Ideally, I know if my canvas was 300,300 then I would
ctx.translate(150,150);
ctx.rotate(-30);
ctx.translate(-150,-150);
Of course since I'm rotating a square it gets cut off.
How would I rotate the square and move it on the canvas so the whole thing is showing but at the very top left edge of the canvas?
I added an image with my thinking of just getting the height of a triangle and moving it that much, but when translated, it doesn't seem to work just right.
I'll paste my whole function so you can look at it, but if you have any ideas, I would appreciate it. This isn't important, just messing around today.
var postit = function(width,height,angle){
var canvas = jQuery("#bg-admin-canvas").get(0);
var ctx = canvas.getContext("2d");
/*var area = (width*width*Math.sin(angle))/2;
var h = (area*2) / width + 30;
ctx.translate(0,h);
*/
//ctx.translate(150,150);
ctx.translate(0,250);
ctx.rotate(angle*Math.PI / 180);
//ctx.translate(-150,-150);
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0.05,"rgba(0,0,0,0)");
gradient.addColorStop(0.5,"rgba(0,0,0,0.3)");
ctx.fillStyle = gradient;
ctx.fillRect(0,0,width,height);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(width, 0);
ctx.lineTo(width,height);
ctx.lineTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.closePath();
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0,'#f7f8b9');
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
ctx.beginPath();
ctx.moveTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.quadraticCurveTo(width*.05,height-height*.05,width*.1,height-height*.1);
ctx.quadraticCurveTo(width*.1,height-height*.07,width-width*.8,height-height*.02);
ctx.closePath();
ctx.fillStyle = '#ffffff';
ctx.fill();
var gradient = ctx.createLinearGradient(0,height,width*.1,height-height*.1);
gradient.addColorStop(0,"rgba(222,222,163,0.8)");
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
}
postit(300, 300, -35);
MORE INFO
Phrog, I think you know what I'm trying to do. This image shows what I want to do:
Now, the only thing is, I want to be able to pass in any width and height and angle and make the adjustment on the fly.
As an example with the following code:
var canvas = document.getElementById("bg-admin-canvas");
var ctx = canvas.getContext("2d");
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.translate(50, 50);
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.translate(-25, -25);
ctx.arc(0,0,3,0,360,true); ctx.fill();
I get the following image:
Now, if I add a rotate in there like this:
var canvas = document.getElementById("bg-admin-canvas");
var ctx = canvas.getContext("2d");
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.translate(50, 50);
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.rotate(30*Math.PI/180);
ctx.translate(-25, -25);
ctx.arc(0,0,3,0,360,true); ctx.fill();
I now have a sloped coordinates as the result is:
As I found, this is because the coordinates are no longer horizontal and vertical.
So, with this rotated coordinate structure, I can't figure out how to move my square (which could be any size and rotated at any angle) back to the left and top (so it fits in as little space as possible)
Does that make sense?
In short:
Translate the context in the Y direction only to put the corner where it should be.
Rotate the context around this offset point.
Draw your object at 0,0.
Here is an interactive, working example, which you can see online here:
http://phrogz.net/tmp/canvas_rotate_square_in_corner.html
<!DOCTYPE HTML>
<html lang="en"><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>HTML5 Canvas Rotate Square in Corner</title>
<style type="text/css" media="screen">
body { background:#eee; margin:2em; text-align:center }
canvas { display:block; margin:auto; background:#fff; border:1px solid #ccc }
</style>
</head><body>
<canvas width="250" height="200"></canvas>
<script type="text/javascript" charset="utf-8">
var can = document.getElementsByTagName('canvas')[0];
var ctx = can.getContext('2d');
ctx.strokeStyle = '#600'; ctx.lineWidth = 2; ctx.lineJoin = 'round';
ctx.fillStyle = '#ff0'
document.body.onmousemove = function(evt){
var w=140, h=120;
var angle = evt ? (evt.pageX - can.offsetLeft)/100 : 0;
angle = Math.max(Math.min(Math.PI/2,angle),0);
ctx.clearRect(0,0,can.width,can.height); ctx.beginPath();
ctx.save();
ctx.translate(1,w*Math.sin(angle)+1);
ctx.rotate(-angle);
ctx.fillRect(0,0,w,h);
ctx.strokeRect(0,0,w,h);
ctx.restore();
};
document.body.onmousemove();
</script>
</body></html>
Analysis
In the above diagram, point A is the upper-left corner of our post-it note and point B is the upper-right corner. We have rotated the post-it note -a radians from the normal angle (clockwise rotations are positive, counter-clockwise are negative).
We can see that the point A stays on the y axis as the post-it rotates, so we only need to calculate how far down the y axis to move it. This distance is expressed in the diagram as BD. From trigonometry we know that
sin(a) = BD / AB
Rearranging this formula gives us
BD = AB * sin(a)
We know that AB is the width of our post-it note. A few details:
Because our angle will be expressed as a negative number, and the sin of a negative number yields a negative result, but because we want a positive result, we must either negate the result
BD = -AB * sin(-a)
or just 'cheat' and use a positive angle:
BD = AB * sin(a)
We need to remember to translate our context before we rotate it, so that we first move directly down the axis to establish our origin at the right spot.
Remember that rotations in HTML5 Canvas use radians (not degrees). If you want to rotate by 20 degrees, you need to convert that to radians by multiplying by Math.PI/180:
ctx.rotate( 20*Math.PI/180 );
This also applies to the arc command; you should be doing ctx.arc(x,y,r,0,Math.PI*2,false); for a full circle.
You should create you canvas element and then rotate it using CSS. It would keep your canvas intact and only rotate the element itself.
Here is some example css rules:
-webkit-transform: rotate(-30deg);
-moz-transform: rotate(-30deg);
Refer to http://snook.ca/archives/html_and_css/css-text-rotation