What I have is this:
What I want is to rotate the red rectangle e.g. 20 degrees, but this is what I end up with:
As you can see, the rectangle is rotated perfectly, but it's moved and doesn't match the black object anymore.
My code is:
context.save();
context.rotate(angle * Math.PI / 180); // in the screenshot I used angle = 20
context.translate(angle * 4, 2);
context.fillStyle = "red";
context.fillRect(x + 14, y - 16, 5, 16);
context.restore();
I want to make the rectangle turn without moving from its position.
Thanks.
Sounds like you want to rotate the rect around its centerpoint.
Red rectangle is original, Yellow rectangle is rotated around the centerpoint.
To do that you need to first context.translate to the rect's centerpoint before rotating.
// move the rotation point to the center of the rect
ctx.translate( x+width/2, y+height/2 );
// rotate the rect
ctx.rotate(degrees*Math.PI/180);
Note that the context is now in its rotated state.
That means drawing position [0,0] is visually at [ x+width/2, y+height/2 ].
So you must draw the rotated rect at [ -width/2, -height/2 ] (not at the original unrotated x/y).
// draw the rect on the transformed context
// Note: after transforming [0,0] is visually [-width/2, -height/2]
// so the rect needs to be offset accordingly when drawn
ctx.rect( -width/2, -height/2, width,height);
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/z4p3n/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var startX=50;
var startY=80;
// draw an unrotated reference rect
ctx.beginPath();
ctx.rect(startX,startY,100,20);
ctx.fillStyle="blue";
ctx.fill();
// draw a rotated rect
drawRotatedRect(startX,startY,100,20,45);
function drawRotatedRect(x,y,width,height,degrees){
// first save the untranslated/unrotated context
ctx.save();
ctx.beginPath();
// move the rotation point to the center of the rect
ctx.translate( x+width/2, y+height/2 );
// rotate the rect
ctx.rotate(degrees*Math.PI/180);
// draw the rect on the transformed context
// Note: after transforming [0,0] is visually [x,y]
// so the rect needs to be offset accordingly when drawn
ctx.rect( -width/2, -height/2, width,height);
ctx.fillStyle="gold";
ctx.fill();
// restore the context to its untranslated/unrotated state
ctx.restore();
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
You can use a simple function to do this as an alternative to translate and rotate:
This function will let you draw a line starting at x and y, and end at length at angle:
function lineToAngle(ctx, x1, y1, length, angle) {
angle *= Math.PI / 180;
var x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
return {x: x2, y: y2};
}
Usage:
lineToAngle(ctx, x, y, length, angle);
Demo
var canvas = document.querySelector('canvas'),
ctx = canvas.getContext('2d'),
x = 100, y =50, length = 40, angle = 0, dlt = -2;
(function animate() {
//clear
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
lineToAngle(ctx, x, y, length, angle);
ctx.lineWidth = 10;
ctx.stroke();
angle += dlt;
if (angle < -90 || angle > 0) dlt = -dlt;
requestAnimationFrame(animate);
})();
function lineToAngle(ctx, x1, y1, length, angle) {
angle *= Math.PI / 180;
var x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
return {x: x2, y: y2};
}
body {background-color:#555557}
canvas {background:#ddd;border:1px solid #000}
<canvas></canvas>
In the specific case of a rectangle, you can just draw a line.
In HTML5 canvas, lines are drawn exactly like rectangles, as such, you can use this function:
let canvas = document.getElementById('myCanvas')
let ctx = canvas.getContext('2d')
function drawRotatedRect(ctx, x, y, width, height, angle) {
angle *= Math.PI / 180
ctx.lineWidth = height / 2
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x + width * Math.cos(angle), y + width * Math.sin(angle))
ctx.stroke()
}
drawRotatedRect(ctx, 30, 30, 200, 100, 20)
canvas {
border: 1px solid black;
width: 100%;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Rotated Rectangle</title>
</head>
<body>
<canvas id='myCanvas'></canvas>
</body>
</html>
Related
I'm new to canvas and I'm trying to listen to mouse clicks on the circles that I previously drew on canvas. I'm trying to change the colour of the circles (maybe to green) when I click on them.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var radius = 15;
for (var i = 0; i < 600; i += 100) {
var x = 100 + i;
var y = 100;
draw_circle(i, x, y, radius);
}
function draw_circle(ID, x, y, radius) {
context.beginPath();
context.arc(x, y, radius, 0, Math.PI * 2, false);
context.fillStyle = 'red';
context.fill();
context.lineWidth = 3;
context.strokeStyle = 'black';
context.stroke();
context.closePath();
}
<!DOCTYPE HTML>
<html>
<head>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0px;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="700" height="700"></canvas>
</body>
</html>
Just add the arc path again and use isPointInPath() with the mouse position. Only one arc can be tested at once, but it's fast to clear and add new paths. Do this in a loop.
Example
var c = document.querySelector("canvas"), ctx = c.getContext("2d");
getArc(50, 50, 40);
ctx.fill();
c.onclick = function(e) {
var rect = this.getBoundingClientRect(), // get abs. position of canvas
x = e.clientX - rect.left, // adjust mouse-position
y = e.clientY - rect.top;
getArc(50, 50, 40); // get path wo/ filling/stroking it
if (ctx.isPointInPath(x, y)) alert("Clicked");
};
function getArc(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2);
ctx.closePath();
}
<canvas></canvas>
The other approach is to use math and trigonometry:
// at this point we have the mouse position, so:
var dx = x - arcX,
dy = y - arcY,
dist = Math.abs(Math.sqrt(dx*dx + dy*dy));
if (dist <= radius) { ...clicked... };
Tip: you can skip squaring the dist by using r2 instead.
With SVG, it would be easy - a circle is an element, and can have a click handler, and has fill that you can manipulate to change colour.
With Canvas, you need to:
save the data for each circle you draw (center and radius)
capture click on canvas as cx, cy
check every circle data x, y, r you have, see whether dx * dx + dy * dy < r * r, where dx = cx - x, dy = cy - y. Circles that satisfy this equation were clicked
repaint the circle
With Canvas, you can use function addEventListener. There are quite a few mouse events you can detect: mousedown, mouseup, mousemove, mouseout and mouseover.
Here is a example:
<!DOCTYPE HTML>
<html>
<head>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0px;
}
</style>
<script>
function initialize() {
var canvas = document.getElementById("myCanvas");
canvas.addEventListener("mousedown", doMouseDown, false);
}
function doMouseDown() {
canvas_X = event.pageX;
canvas_Y = event.pageY;
alert("X = " + canvas_X + "Y = " + canvas_Y);
}
</script>
</head>
<body>
<canvas id="myCanvas" width="700" height="700"></canvas>
</body>
</html>
I'm trying to rotate a rectangle about it's center but it's not rotating how I expect.
Here's an example:
https://jsfiddle.net/37ur8dfk/1/
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let x = 100;
let y = 100;
let w = 100;
let h = 50;
// Draw a red dot to highlight the point I want to rotate the rectangle around
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
// Attempt to rotate the rectangle aroumd x,y
ctx.save();
ctx.fillStyle = '#000000';
ctx.translate(-x, -y);
ctx.rotate(10 * Math.PI/180);
ctx.translate(x, y);
ctx.fillRect(x - w/2, y - h/2, w, h);
ctx.restore();
I have the center of the rectangle as x,y coords. I then translate it by -x,-y to change it's origin to 0,0. Then I rotate it by some degrees, but it does not seem to be rotating about the 0,0 coords. It's my understanding that rotate should rotate the entire context about the origin, or 0,0.
Please take a look at the jsfiddle to see what I mean.
What am I missing here?
You got it inversed.
You are not translating the rectangle, but the context's transformation matrix.
Think of this as a sheet of paper and an arm with pen.
When you translate your context, the arm is moving in the direction provided. When you rotate the context, the arm is rotating.
So to set your rectangle's center as the rotation origin you first need to move the arm so that the pen is in the center of the rectangle, then you'll be able to rotate. And we move back the arm to its initial position so that the x and y coords match.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let x = 100;
let y = 100;
let w = 100;
let h = 50;
// Attempt to rotate the rectangle aroumd x,y
ctx.save();
ctx.fillStyle = '#000000';
// move to transformation-origin
ctx.translate(x, y);
// transform
ctx.rotate(10 * Math.PI/180);
// go back to where we were
ctx.translate(-x, -y);
ctx.fillRect(x - w/2, y - h/2, w, h);
ctx.restore();
// Draw a red dot to highlight the point I want to rotate the rectangle around
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
<canvas id="canvas" width="500" height="400"></canvas>
Try (This will allow a rotation around the dot)
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let x = 100;
let y = 100;
let w = 100;
let h = 50;
let angle = Math.PI/8;
ctx.save();
ctx.fillStyle = '#000000';
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillRect(0, 0, w, h);
ctx.restore();
ctx.save();
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
ctx.restore();
canvas {
border: 1px solid black;
}
<canvas id="canvas" width="500" height="400"></canvas>
i am having a problem rotating a ball that is bouncing around the screen when i put the rotate method in it just dissapears off the canvas in a very wide rotating arc.
It is working when the ball is static but when i put x,y velocity the problem starts.
Ant help someone can give me would be greatly appreciated.
Please see below the code i have so far.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Canvas</title>
<style type="text/css">
canvas {
border: 1px solid black;
}
body {
background-color: white;
}
</style>
</head>
<body>
<canvas id="canvas-for-ball"></canvas>
<script type="text/javascript">
// Gets a handle to the element with id canvasOne.
var canvas = document.getElementById("canvas-for-ball");
// Get a 2D context for the canvas.
var ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 500;
// The vertical location of the ball.
//var y = 10;
var yvel = 3;
var xvel = 3 ;
//ball object
var ball = {
x: 90,
y: 150,
r: 50
};
// A function to repeat every time the animation loops.
function repeatme() {
//clear canvas for each frame of the animation.
ctx.clearRect(0,0,500,500);
// Draw the ball (stroked, not filled).
//for loop to draw each line of th pie.
// i is set to 7. set i to whatever amount of sections you need.
for (var i = 0; i < 7; i++) {
//starting point
ctx.beginPath();
//move to positions the pen to the center of the ball
ctx.moveTo(ball.x, ball.y);
//circle craeted with x,y and r set. The final two arguments (Starting and ending point)
//change over each itteration of the loop giving a new point on the circle to draw to.
ctx.arc(ball.x, ball.y, ball.r, i*(2 * Math.PI / 7), (i+1)*(2 * Math.PI / 7));
//set line width
//set colour of the lines
ctx.strokeStyle = '#444';
//render the lines
ctx.stroke();
}
ctx.beginPath();
//the inner circle of the pizza
ctx.moveTo(ball.x, ball.y);
//ball.r is used - 10 pixles to put the smaller circle in the pizza.
ctx.arc(ball.x,ball.y,ball.r-10,0,2*Math.PI);
ctx.lineWidth = 2;
ctx.strokeStyle = '#444';
ctx.stroke();
ctx.translate(ball.x, ball.y);
// Rotate 1 degree
ctx.rotate(Math.PI / 180);
// Move registration point back to the top left corner of canvas
ctx.translate(-ball.x, -ball.y);
//draw the ball
//restore canvas back to original state
ctx.restore();
// Update the y location.
ball.y += yvel;
ball.x += xvel;
//put repeatme function into the animation frame and store it in animate
animate = window.requestAnimationFrame(repeatme);
//condition take into account the radius of the ball so
//it bounces at the edge of the canvas instead
//of going off of the screen to its center point.
if(ball.y >= canvas.height - ball.r){
//window.cancelAnimationFrame(animate);
yvel = yvel *-1;
}else if(ball.y <= ball.r){
yvel = yvel /-1;
}
if(ball.x >= canvas.width- ball.r){
xvel = xvel *-1;
}else if(ball.x <=ball.r){
xvel = xvel / -1;
}
}
// Get the animation going.
repeatme();
</script>
</body>
</html>
To rotate a rendered path. Set the origin to the point of rotation, rotate by the amount you want and render the path at 0,0
rotation += Math.PI / 180; // rotate 1 deg steps
ctx.lineWidth = 2;
ctx.strokeStyle = '#444';
ctx.setTransform(1, 0, 0, 1, ball.x, ball.y); // set the origin at the center
ctx.rotate(rotation); // set the rotation amount
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.arc(0, 0, ball.r - 10, 0, 2 * Math.PI);
ctx.stroke();
ctx.setTransform(1,0,0,1,0,0); // restore the default transform
So I'm trying to have shapes animate across the canvas and then bounce back after hitting the edge, and I'm unclear why my other two shapes do not have velocity like my ball does. I'll post the code below.
<html>
<body>
<section>
<div>
<canvas id="canvas" width="700" height="300"></canvas>
</div>
<script type="text/javascript">
var canvas,
context,
x = Math.floor((Math.random() * 400) + 1), //Ball x coordinate
y = Math.floor((Math.random() * 300) + 1), //Ball y coordinate
dx = Math.floor((Math.random() * 10) + 1), //X velocity
dy = Math.floor((Math.random() * 4) + 1), //Y velocity
width = 700, //Width of the background
height = 300; //Height of the background
function drawCircle(x,y,r) { //Draws the ball
context.beginPath();
context.arc(x, y, r, 0, Math.PI*2, true);
context.fill();
}
function drawRect(x,y,w,h) { //Draws the background
context.beginPath();
context.rect(x,y,w,h);
context.closePath();
context.fill();
}
function start() { //Runs when the window loads. Stores canvas and context in variables. Runs "draw" on an interval of 10ms
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
return setInterval(draw, 10);
}
function drawSquare(){
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.fillStyle = "white";
ctx.beginPath();
ctx.moveTo(200,50);
ctx.lineTo(250,50);
ctx.lineTo(300, 100);
ctx.lineTo(250,25);
ctx.fill();
}
}
function drawTri(){
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75,50);
ctx.lineTo(100,75);
ctx.lineTo(75,25);
ctx.fill();
}
}
function draw() {
context.clearRect(0, 0, width, height); //Clears the drawing space
context.fillStyle = "black"; //Sets fillstyle to black (for the background)
drawRect(0,0, width, height); //Draws the background
context.fillStyle = "white"; //Sets fillstyle to white (for the ball)
drawCircle(x, y, 10);
drawTri();
drawSquare(); //Calls function to draw the ball
if (x + dx > width || x + dx < 0) //If the ball would leave the width (right or left) on the next redraw...
dx = -dx; //reverse the ball's velocity
if (y + dy > height || y + dy < 0) //If the ball would leave the height (top or bottom) on the next redraw...
dy = -dy; //reverse the ball's velocity
x += dx; //Increase ball x speed by velocity
y += dy; //Increase ball y speed by velocity
}
window.onload = start; //Run "start" function after the window loads
</script>
</section>
</body>
</html>
you forgot to do the drawing with the variables. And the variables are the same (as in the 2 shapes will always be together) because if x is 150 for the circle, the rect will be behind it. So, you need more variables to keep track of it like this: circ1x,circ1y,circ2x,circ2y,rect1x,rect1y,rect2x,rect2y
Your ball is being drawn using variable coordinates (x & y)
context.arc(x, y, r, 0, Math.PI*2, true);
Your shapes are being drawn using hardcoded, constant values:
ctx.moveTo(200,50);
ctx.lineTo(250,50);
ctx.lineTo(300, 100);
ctx.lineTo(250,25);
&
ctx.moveTo(75,50);
ctx.lineTo(100,75);
ctx.lineTo(75,25);
To animate them, use variables to draw the other shapes, and update those variables. eg.
var x = 75,
y = 50;
ctx.moveTo(x,y);
ctx.lineTo(x + 25, y + 25);
ctx.lineTo(x, y - 25);
I'm currently trying to make so that when you click on one of the happy face you get an alert box which says "thanks for the feed back", but I'm currently not sure to to incoperate that in tho my code! Thanks! Here is the fiddle http://jsfiddle.net/bsjs9/
<html lang="en">
<head>
<meta charset="utf-8" />
<title>SmileMore</title>
</head>
<body>
<h1>
Bring your Charts to life with HTML5 Canvas</h1>
</hgroup>
<p>
Rendering Dynamic charts in JS
</p>
<div class="smile">
<canvas id="myDrawing" width="200" height="200" style="border:1px solid #EEE"></canvas>
<canvas id="canvas" width="200" height="200" style="border:1px solid #EEE"></canvas>
</div>
<script>
var FacePainter = function(canvasName)
{
var canvas = document.getElementById(canvasName);
var ctx = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var startAngle = 0;
var endAngle = 2 * Math.PI;
function drawFace() {
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle);
ctx.stroke();
ctx.fillStyle = "yellow";
ctx.fill();
}
function drawSmile(startAngle, endAngle)
{
var x = canvas.width / 2;
var y = 150;
var radius = 40;
ctx.beginPath();
ctx.arc(x, y, radius, startAngle * Math.PI, endAngle * Math.PI);
ctx.lineWidth = 7;
// line color
ctx.strokeStyle = 'black';
ctx.stroke();
}
function drawEyes() {
var centerX = 40;
var centerY = 0;
var radius = 10;
// save state
ctx.save();
// translate context so height is 1/3'rd from top of enclosing circle
ctx.translate(canvas.width / 2, canvas.height / 3);
// scale context horizontally by 50%
ctx.scale(.5, 1);
// draw circle which will be stretched into an oval
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
// restore to original state
ctx.restore();
// apply styling
ctx.fillStyle = 'black';
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
//left eye
var centerX = -40;
var centerY = 0;
var radius = 10;
// save state
ctx.save();
// translate context so height is 1/3'rd from top of enclosing circle
ctx.translate(canvas.width / 2, canvas.height / 3);
// scale context horizontally by 50%
ctx.scale(.5, 1);
// draw circle which will be stretched into an oval
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
// restore to original state
ctx.restore();
// apply styling
ctx.fillStyle = 'black';
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
}
this.drawHappyFace = function() {
drawFace();
drawEyes();
drawSmile(1.1, 1.9);
}
this.drawSadFace = function() {
drawFace();
drawEyes();
drawSmile(1.9, 1.1);
;
}
}
new FacePainter('canvas').drawHappyFace();
new FacePainter('myDrawing').drawSadFace();
</script>
</body>
</html>
</body>
</html>
As an extra assignment I would like to know if anyone knows how to fix the "happy" smile, its kinda way off! Thanks all!
Since you draw the happy face on its own canvas, you can simple put an onclick handler on the canvas.
<canvas id="myDrawing" width="200" height="200" style="border:1px solid #EEE" onclick="alert('thanks');"></canvas>
Regarding the smiles, I added a new ofsy parameter to drawSmile, which offsets the arc origin vertically.
Here is the updated fiddle.
If you only want to show the alert, when the user clicks inside the face, you need to get the click coordinates and hittest it against the circle. You can see this in this fiddle.