I'm trying to make a rectangle follow the angle between it and the mouse. Basically if the angle between the mouse and the current angle of the rectangle is higher than the current angle of the rectangle, it adds 1 and will continue until condition is false. The opposite happens when the angle between the two is lower than the current angle of the rectangle. My problem here is the angle between the mouse and rectangle, after 180 degrees it goes becomes -180. This causes the rectangle to go around again instead of going to 181 degrees. I've tried figuring out a way around this but I somehow always ended up in the same problem.
Thanks in advance, here is my code:
<body>
<canvas id="ctx" width="500" height="500" style="border:1px solid #000000;"></canvas>
</body>
<script>
var canvas = document.getElementById("ctx"),
ctx = canvas.getContext("2d"),
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight,
mouse,
angle = 0;
function test() {
"use strict";
var dX = mouse.X - 200,
dY = mouse.Y - 200,
rad = (Math.atan2(dY, dX)),
deg = (rad * (180 / Math.PI)),
player = {
x : 200,
y : 200,
width : 40,
height : 10,
angle : 0,
dAngle : 0
};
//Test
if (deg > angle) {
angle += 1;
} else if (deg) {
angle -= 1;
}
ctx.clearRect(0, 0, width, height);
ctx.translate(player.x, player.y);
ctx.rotate((angle * Math.PI / 180));
ctx.fillRect(0, -player.height / 2, player.width, player.height);
//console.log("deg :" + deg + "angle : " + angle);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
window.onmousemove = function (mm) {
"use strict";
mouse = {
X : mm.clientX - document.getElementById('ctx').getBoundingClientRect().left,
Y : mm.clientY - document.getElementById('ctx').getBoundingClientRect().top
};
};
setInterval(test, 40);
</script>
The Math.atan2() method returns a numeric value between -π and π representing the angle theta of an (x, y) point as mentioned in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2
So you need just a convert value from [-180;180] to [0;360]. You can do it, let say, in this manner degrees = (degrees + 360) % 360;
<body>
<canvas id="ctx" width="500" height="500" style="border:1px solid #000000;"></canvas>
</body>
<script>
var canvas = document.getElementById("ctx"),
ctx = canvas.getContext("2d"),
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight,
//Initialize with dummy values
mouse={X:0,Y:0},
angle = 0;
function test() {
"use strict";
var dX = mouse.X - 200,
dY = mouse.Y - 200,
rad = (Math.atan2(dY, dX)),
deg = (rad * (180 / Math.PI)),
player = {
x : 200,
y : 200,
width : 40,
height : 10,
angle : 0,
dAngle : 0
};
//Test
if (deg > angle) {
angle += 1;
} else if (deg) {
angle -= 1;
}
ctx.clearRect(0, 0, width, height);
ctx.translate(player.x, player.y);
ctx.rotate((angle * Math.PI / 180));
ctx.fillRect(0, -player.height / 2, player.width, player.height);
//console.log("deg :" + deg + "angle : " + angle);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
window.onmousemove = function (mm) {
"use strict";
mouse = {
X : mm.clientX - document.getElementById('ctx').getBoundingClientRect().left,
Y : mm.clientY - document.getElementById('ctx').getBoundingClientRect().top
};
};
setInterval(test, 40);
</script>
you can notice that angle flip flops within 1.0 of deg by printing (deg-angle). Just make it stop if it's in that range. (and dont use plain > or == when dealing with floats)
Of course there's nothing special about 1.0, here I replaced it with spd:
<body>
<canvas id="ctx" width="500" height="500" style="border:1px solid #000000;"></canvas>
</body>
<script>
var canvas = document.getElementById("ctx"),
ctx = canvas.getContext("2d"),
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight,
mouse = {X:0.0, Y:0.0},
angle = 0;
function test() {
"use strict";
var dX = mouse.X - 200,
dY = mouse.Y - 200,
rad = (Math.atan2(dY, dX)),
deg = (rad * (180 / Math.PI)),
player = {
x : 200,
y : 200,
width : 40,
height : 10,
angle : 0,
dAngle : 0
};
//Test
var spd = 5.0;
if (Math.abs(deg - angle) - spd > 0.0000001)
angle += Math.sign(deg - angle)*spd;
ctx.clearRect(0, 0, width, height);
ctx.translate(player.x, player.y);
ctx.rotate((angle * Math.PI / 180));
ctx.fillRect(0, -player.height / 2, player.width, player.height);
//console.log("deg :" + deg + "angle : " + angle);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
window.onmousemove = function (mm) {
"use strict";
mouse = {
X : mm.clientX - document.getElementById('ctx').getBoundingClientRect().left,
Y : mm.clientY - document.getElementById('ctx').getBoundingClientRect().top
};
};
setInterval(test, 40);
</script>
Related
I'm currently learning about the html5 canvas, and I was making a program that rotates a triangle to face wherever the mouse is. it partially works but skips half of it, and i was wondering if there was a better way to do it and also what is wrong with my current code
thanks!
const ctx = document.getElementById("canvas").getContext("2d")
ctx.canvas.style.backgroundColor = "#303030"
ctx.canvas.width = 500
ctx.canvas.height = 500
const w = ctx.canvas.width
const h = ctx.canvas.height
let x = 0;
let y = 0;
let degrees = 0;
triAngle = 60
ctx.canvas.addEventListener("mousemove", mouseMove, false)
function mouseMove(evt) {
x = evt.clientX
y = evt.clientY
let diffX = x - w / 2;
let diffY = y - h / 2;
console.log(diffX, diffY)
degrees = Math.floor(Math.atan(diffY / diffX) * 57.2958);
//Math.atan(diffY/ diffX)
console.log(degrees)
}
function draw() {
debugger ;
ctx.clearRect(0, 0, w, h)
ctx.fillStyle = "#fff";
ctx.save()
ctx.translate(w / 2, h / 2)
ctx.rotate(degree(degrees + triAngle / 2))
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(0, 100)
ctx.rotate(degree(triAngle))
ctx.lineTo(0, 100)
ctx.closePath()
ctx.fill()
ctx.restore()
requestAnimationFrame(draw)
}
function degree(input) {
return Math.PI / 180 * input
}
draw()
https://jsfiddle.net/tus5nxpb/
Math.atan2
The reason that Math.atan skips half the directions is because of the sign of the fraction. The circle has 4 quadrants, the lines from {x: 0, y: 0} to {x: 1, y: 1}, {x: -1, y: 1}, {x: -1, y: -1} and {x: 1, y: -1} result in only two values (1, and -1) if you divide y by x eg 1/1 === 1, 1/-1 === -1, -1/1 === -1, and -1/-1 === 1 thus there is no way to know which one of the 2 quadrants each value 1 and -1 is in.
You can use Math.atan2 to get the angle from a point to another point in radians. In the range -Math.PI to Math.PI (-180deg to 180deg)
BTW there is no need to convert radians to deg as all math functions in JavaScript use radians
requestAnimationFrame(mainLoop);
const ctx = canvas.getContext("2d")
canvas.height = canvas.width = 300;
canvas.style.backgroundColor = "#303030";
const mouse = {x: 0, y: 0};
canvas.addEventListener("mousemove", e => {
mouse.x = e.clientX;
mouse.y = e.clientY;
});
const shape = {
color: "lime",
x: 150,
y: 150,
size: 50,
path: [1, 0, -0.5, 0.7, -0.5, -0.7],
};
function draw(shape) {
var i = 0;
const s = shape.size, p = shape.path;
ctx.fillStyle = shape.color;
const rot = Math.atan2(mouse.y - shape.y, mouse.x - shape.x);
const xa = Math.cos(rot);
const ya = Math.sin(rot);
ctx.setTransform(xa, ya, -ya, xa, shape.x, shape.y);
ctx.beginPath();
while (i < p.length) { ctx.lineTo(p[i++] * s, p[i++] * s) }
ctx.fill();
}
function mainLoop() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // set default transform
ctx.clearRect(0, 0, canvas.width, canvas.height);
draw(shape);
requestAnimationFrame(mainLoop);
}
body { margin: 0px; }
canvas { position: absolute; top: 0px; left: 0px; }
<canvas id="canvas"></canvas>
I have pie chart like grow/shrink animation, but in the middle of it, there is showing up fill circle on one frame, how can I get rid of it?
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var increase = Math.PI * 2 / 100;
var max = Math.PI * 2;
var angle = 0;
var start = false;
setInterval(function() {
ctx.clearRect(0, 0, 32, 32);
ctx.fillStyle = '#F00A0A';
ctx.beginPath();
if (start) {
ctx.arc(16, 16, 12, angle, 0);
} else {
ctx.arc(16, 16, 12, 0, angle);
}
ctx.lineTo(16, 16);
ctx.fill();
angle += increase;
if (angle >= max) {
angle = 0;
start = !start;
}
}, 20);
<canvas width="32" height="32"/>
I've tried this:
if (angle !== 0) {
ctx.beginPath();
if (start) {
ctx.arc(16, 16, 12, angle, 0);
} else {
ctx.arc(16, 16, 12, 0, angle);
}
ctx.lineTo(16, 16);
ctx.fill();
}
but this don't work, when arc change side it show full circle for one frame.
The easiest solution is to just offset your angle ever so slightly.
That way circle isn't "complete" when you change drawing mode:
var canvas = document.body.appendChild(document.createElement('canvas'));
var canvasSize = canvas.width = canvas.height = 100;
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#F00A0A';
var increase = Math.PI * 2 / 100;
var max = Math.PI * 2;
var angle = 0.0000001;
var start = false;
function update() {
ctx.clearRect(0, 0, canvasSize, canvasSize);
ctx.beginPath();
ctx.arc(canvasSize / 2, canvasSize / 2, canvasSize / 3, angle, 0, start);
ctx.lineTo(canvasSize / 2, canvasSize / 2);
ctx.fill();
angle += increase;
if (angle >= max) {
angle = angle % max;
start = !start;
}
requestAnimationFrame(update);
}
update();
I'm trying to resize a rotated shape on canvas. My problem is that when I call the rendering function, the shape starts "drifting" depending on the shape angle. How can I prevent this?
I've made a simplified fiddle demonstrating the problem, when the canvas is clicked, the shape is grown and for some reason it drifts upwards.
Here's the fiddle: https://jsfiddle.net/x5gxo1p7/
<style>
canvas {
position: absolute;
box-sizing: border-box;
border: 1px solid red;
}
</style>
<body>
<div>
<canvas id="canvas"></canvas>
</div>
</body>
<script type="text/javascript">
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
var counter = 0;
var shape = {
top: 120,
left: 120,
width: 120,
height: 60,
rotation: Math.PI / 180 * 15
};
function draw() {
var h2 = shape.height / 2;
var w2 = shape.width / 2;
var x = w2;
var y = h2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(75,37.5)
ctx.translate(x, y);
ctx.rotate(Math.PI / 180 * 15);
ctx.translate(-x, -y);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, shape.width, shape.height);
ctx.restore();
}
canvas.addEventListener('click', function() {
shape.width = shape.width + 15;
window.requestAnimationFrame(draw.bind(this));
});
window.requestAnimationFrame(draw.bind(this));
</script>
In the "real" code the shape is resized when the resize-handle is clicked and moved but I think this example demonstrates the problem sufficiently.
EDIT: updated fiddle to clarify the issue:
https://jsfiddle.net/x5gxo1p7/9/
Always use local coordinates to define shapes.
When rendering content that is intended to be transformed the content should be in its own (local) coordinate system. Think of a image. the top left pixel is always at 0,0 on the image no matter where you render it. The pixels are at their local coordinates, when rendered they are moved to the (world) canvas coordinates via the current transformation.
So if you make your shape with coordinates set to its local, making the rotation point at its local origin (0,0) the display coordinates are stored separately as world coordinates
var shape = {
top: -30, // local coordinates with rotation origin
left: -60, // at 0,0
width: 120,
height: 60,
world : {
x : canvas.width / 2,
y : canvas.height / 2,
rot : Math.PI / 12, // 15deg clockwise
}
};
Now you don't have to mess about with translating forward and back... blah blah total pain.
Just
ctx.save();
ctx.translate(shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height)
ctx.restore();
or event quicker and eliminating the need to use save and restore
ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);
The local shape origin (0,0) is where the transformation places the translation.
This greatly simplifies a lot of the work that has to be done
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
ctx.fillStyle = "black";
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
var shape = {
top: -30, // local coordinates with rotation origin
left: -60, // at 0,0
width: 120,
height: 60,
world : {
x : canvas.width / 2,
y : canvas.height / 2,
rot : Math.PI / 12, // 15deg clockwise
}
};
function draw() {
ctx.setTransform(1,0,0,1,0,0); // to clear use default transform
ctx.clearRect(0, 0, canvas.width, canvas.height);
// you were scaling the shape, that can be done via a transform
// once you have moved the shape to the world coordinates.
ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
// after the transformations have moved the local to the world
// you can ignore the canvas coordinates and work within the objects
// local. In this case showing the unscaled box
ctx.strokeRect(shape.left, shape.top, shape.width, shape.height);
// and a line above the box
ctx.strokeRect(shape.left, shape.top - 5, shape.width, 1);
ctx.scale(0.5,0.5); // the scaling you were doing
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);
}
canvas.addEventListener('click', function() {
shape.width += 15;
shape.left -= 15 / 2;
shape.world.rot += Math.PI / 45; // rotate to illustrate location
// of local origin
var distToMove = 15/2;
shape.world.x += Math.cos(shape.world.rot) * distToMove;
shape.world.y += Math.sin(shape.world.rot) * distToMove;
draw();
});
// no need to use requestAnimationFrame (RAF) if you are not animation
// but its not wrong. Nor do you need to bind this (in this case
// this = window) to the callback RAF does not bind a context
// to the callback
/*window.requestAnimationFrame(draw.bind(this));*/
requestAnimationFrame(draw); // functionaly identical
// or just
/*draw()*/ //will work
body { font-family : Arial,"Helvetica Neue",Helvetica,sans-serif; font-size : 12px; color : #242729;} /* SO font currently being used */
canvas { border: 1px solid red; }
<canvas id="canvas"></canvas>
<p>Click to grow "and rotate" (I add that to illustrate the local origin)</p>
<p>I have added a red box and a line above the box, showing how using the local coordinates to define a shape makes it a lot easier to then manipulate that shape when rendering "see code comments".</p>
Try this. You had ctx.translate() used where it was not entirely necessary. That caused the problems.
<script type="text/javascript">
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
var counter = 0;
var shape = {
top: 120,
left: 120,
width: 120,
height: 60,
rotation: Math.PI / 180 * 15
};
function draw() {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(75,37.5)
ctx.rotate(Math.PI / 180 * 15);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, shape.width, shape.height);
ctx.restore();
}
canvas.addEventListener('click', function() {
shape.width = shape.width + 15;
window.requestAnimationFrame(draw.bind(this));
});
window.requestAnimationFrame(draw.bind(this));
</script>
This is happening because the x and y are set as the half value of the shape size, which completely changes its position.
You should set a point for the center of the shape, anyway. I set this point as ctx.canvas.[width or height] / 2, the half of the canvas.
var h2 = shape.height / 2;
var w2 = shape.width / 2;
var x = (ctx.canvas.width / 2) - w2;
var y = (ctx.canvas.height / 2) - h2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(x + (shape.width / 2), y + (shape.height / 2));
ctx.rotate(((shape.rotation * Math.PI) / 180) * 15);
ctx.fillStyle = '#000';
ctx.fillRect(-shape.width / 2, -shape.height / 2, shape.width, shape.height);
ctx.restore();
Fiddle.
Found a solution, problem was that I wasn't calculating the new center point coordinates.
The new fiddle with solution: https://jsfiddle.net/HTxGb/151/
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width =500;
canvas.height = 500;
var x = canvas.width/2;
var y = canvas.height/2;
var rectw = 20;
var recth = 20;
var rectx = -rectw/2;
var recty = -recth/2;
var rotation = 0;
var addedRotation = Math.PI/12;
var addedWidth = 20;
var addedHeight = 10;
var draw = function() {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.fillRect(rectx, recty, rectw, recth);
ctx.restore();
}
document.getElementById('growXRight').addEventListener('click', function() {
rectx -= addedWidth/2;
x += addedWidth/2 * Math.cos(rotation);
y -= addedWidth/2 * Math.sin(-rotation);
rectw += addedWidth;
draw();
})
document.getElementById('growXLeft').addEventListener('click', function() {
rectx -= addedWidth/2;
x -= addedWidth/2 * Math.cos(rotation);
y += addedWidth/2 * Math.sin(-rotation);
rectw += addedWidth;
draw();
})
document.getElementById('growYTop').addEventListener('click', function() {
recty -= addedHeight/2;
x += addedHeight/2 * Math.sin(rotation);
y -= addedHeight/2 * Math.cos(-rotation);
recth += addedHeight;
draw();
})
document.getElementById('growYBottom').addEventListener('click', function() {
recty -= addedHeight/2;
x -= addedHeight/2 * Math.sin(rotation);
y += addedHeight/2 * Math.cos(-rotation);
recth += addedHeight;
draw();
})
document.getElementById('rotatePlus').addEventListener('click', function() {
rotation += addedRotation;
rotation = rotation % (Math.PI*2);
if(rotation % Math.PI*2 < 0) {
rotation += Math.PI*2;
}
draw();
})
document.getElementById('rotateMinus').addEventListener('click', function() {
rotation -= addedRotation;
rotation = rotation % (Math.PI*2);
if(rotation % Math.PI*2 < 0) {
rotation += Math.PI*2;
}
draw();
})
draw();
I have two lines that make a 90 degree angle, I hope. I want to make it so that the vertical line moves down to the horizontal line. The angle is the pivot point. so the angle should decrease to 0 I guess. 45 would be half way.
the length of the lines should be the same during the animation.
the animation should be looping. It should go from 90 degree angle to
0 degree angle and back.
1 way I was thinking to figure this out was to change the context.moveTo(50,50) the parameters numbers so the line should begin to be drawn at the new coordinates during the animation. I had problems keeping the line the same size as the horizontal.
another way I was thinking was to change the Math.atan2. I don't know have it start at 90 degrees then go to 0 and have that reflect on the moveto parameters I don't know how to put this together.
I would prefer to use a solution with trigonometry because that is what I'm trying to get good at
for extra help if you could attach a hypotenuse so I could see the angle change the size of the triangle that would be great. That was my original problem. Thanks
window.onload = function(){
var canvas =document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 50
context.beginPath();
context.moveTo(50,50)
context.lineTo(50,200);
context.stroke();
context.closePath();
context.beginPath();
context.moveTo(50, 200);
context.lineTo(200, 200)
context.stroke();
context.closePath();
var p1 = {
x: 50,
y : 50
}
var p2 = {
x: 50,
y: 200
}
var angleDeg = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
console.log(angleDeg)
}
<canvas id="canvas" width="400" height="400"></canvas>
This might help.
window.onload = function() {
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 150;
var angle = 270;
var maxAngle = 360;
var minAngle = 270;
var direction = 0;
var p1 = {
x: 50,
y: 200
};
var p2 = {
x: 200,
y: 200
};
context.fillStyle = "rgba( 255, 0, 0, 0.5)";
function draw() {
context.clearRect(0, 0, 400, 400);
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y)
if (angle >= maxAngle) {
direction = 1;
} else if (angle <= minAngle) {
direction = 0;
}
if (direction == 0) {
angle++;
} else {
angle--;
}
var x = p1.x + length * Math.cos(angle * Math.PI / 180);
var y = p1.y + length * Math.sin(angle * Math.PI / 180);
context.moveTo(p1.x, p1.y);
context.lineTo(x, y);
context.lineTo(p2.x, p2.y);
context.stroke();
context.fill()
context.closePath();
}
setInterval(draw, 50);
}
<canvas id="canvas" width="400" height="400"></canvas>
To get angle sequence (in degrees) like 90-45-0-45-90-45..., you can use this simple algo (pseudocode):
i = 0
while (drawingNeeded) do
angle = Math.Abs(90 - (i % 180)) * Math.PI / 180;
endPoint.x = centerPoint.x + lineLength * Math.Cos(angle);
endPoint.y = centerPoint.y + lineLength * Math.Sin(angle);
//redraw canvas, draw static objects
drawLine(centerPoint, endPoint);
i++;
I'm playing around with HTML5 canvas, and I am trying to implement a way of moving an image around on a canvas using translation, scaling, and rotation.
I have got translation and scaling working using setTransform:
canvas.getContext('2d').setTransform(a,b,c,d,e,f)
Which is handy as it discards previous transforms applied, then applies new ones, so there is no need to remember previous state when scaling etc.
On W3 schools is states that the 2nd and 3rd params are skewY and skewX, which I at first assumed to be rotate x and y. However after applying a transform passing some values to these params, it seems it doesn't rotate - it skews the canvas! (strange I know :-D).
Can anyone tell me why there is not rotate in set transform (I'm interested as it seems strange, and skew seems pretty useless to me), and also what is the best way to do a rotate around the center of a canvas along with using setTransform at the same time?
setTransform is based on a 2D Matrix (3x3). These kinds of matrices are used for 2D/3D projections and are typically handled by game engines these days, rather than the programmers who make games.
These things are a little bit linear-algebra and a little bit calculus (for the rotation).
You're not going to like this a whole lot, but here's what you're looking at doing:
function degs_to_rads (degs) { return degs / (180/Math.PI); }
function rads_to_degs (rads) { return rads * (180/Math.PI); }
Start with these helper functions, because while we think well in degrees, computers and math systems work out better in radians.
Then you want to start with calculating your rotation:
var rotation_degs = 45,
rotation_rads = degs_to_rads(rotation_degs),
angle_sine = Math.sin(rotation_rads),
angle_cosine = Math.cos(rotation_rads);
Then, based on the layout of the parameters:
ctx.setTransform(scaleX, skewY, skewX, scaleY, posX, posY);
in the following order, when rearranged into a transform matrix:
//| scaleX, skewX, posX |
//| skewY, scaleY, posY |
//| 0, 0, 1 |
...you'd want to submit the following values:
ctx.setTransform(angle_cosine, angle_sine, -angle_sine, angle_cosine, x, y);
// where x and y are now the "centre" of the rotation
This should get you rotation clockwise.
The marginal-benefit being that you should then be able to multiply everything by the scale that you initially wanted (don't multiply the posX and posY, though).
I have do some tests Based on the answer of #Norguard.
The following is the whole process of drawing a sprite on the canvas with translate, scale, rotate(at the center of rotation) and alpha(opacity):
var width = sprite.width;
var height = sprite.height;
var toX = sprite.transformOriginX * width;
var toY = sprite.transformOriginY * height;
// get the sin and cos value of rotate degree
var radian = sprite.rotate / 180 * Math.PI;
var sin = Math.sin(radian);
var cos = Math.cos(radian);
ctx.setTransform(
cos * sprite.scaleX,
sin * sprite.scaleX,
-sin * sprite.scaleY,
cos * sprite.scaleY,
sprite.x + toX,
sprite.y + toY
);
ctx.globalAlpha = sprite.alpha;
ctx.fillStyle = sprite.color;
ctx.fillRect(-toX, -toY, width, height);
And I made an interactive showcase you can play with:
// prepare the context
var myCanvas = document.getElementById('myCanvas');
var ctx = myCanvas.getContext('2d');
// say we have a sprite looks like this
var sprite = {
x: 50,
y: 50,
width: 50,
height: 100,
transformOriginX: 0.5, // the center of sprite width
transformOriginY: 0.5, // the center of sprite height
scaleX: 1.5,
scaleY: 1,
rotate: 45,
alpha: 0.5, // opacity
color: 'red'
};
function drawSprite() {
var width = sprite.width;
var height = sprite.height;
var scaleX = sprite.scaleX;
var scaleY = sprite.scaleY;
// get the transform-origin value
var toX = sprite.transformOriginX * width;
var toY = sprite.transformOriginY * height;
// get the sin and cos value of rotate degree
var radian = sprite.rotate / 180 * Math.PI;
var sin = Math.sin(radian);
var cos = Math.cos(radian);
ctx.setTransform(
cos * scaleX,
sin * scaleX,
-sin * scaleY,
cos * scaleY,
sprite.x + toX,
sprite.y + toY
);
ctx.globalAlpha = sprite.alpha;
ctx.fillStyle = sprite.color;
ctx.fillRect(-toX, -toY, width, height);
if (toShowInfo) {
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(-toX + width / 2, -toY + height / 2);
ctx.lineTo(-toX + width / 2, -toY);
ctx.strokeStyle = 'lime';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(-toX + width / 2, -toY + height / 2);
ctx.lineTo(-toX + width, -toY + height / 2);
ctx.strokeStyle = 'yellow';
ctx.stroke();
}
}
function draw() { // main launcher
// rest the ctx
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, myCanvas.width, myCanvas.height);
ctx.fillStyle = 'white';
ctx.font = '12px Arial';
ctx.textAlign = 'end';
ctx.textBaseline = 'hanging';
ctx.fillText('made by Rex Hsu', 395, 5);
// draw sprite
drawSprite();
// draw info
if (toShowInfo) { drawInfo(); };
}
function drawInfo() {
var x = sprite.x;
var y = sprite.y;
var width = sprite.width;
var height = sprite.height;
var toX = sprite.transformOriginX * width;
var toY = sprite.transformOriginY * height;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.arc(x + toX, y + toY, 3, 0, Math.PI * 2);
ctx.fillStyle = 'lime';
ctx.fill();
ctx.font = '12px Arial';
ctx.textAlign = 'start';
ctx.textBaseline = 'middle';
ctx.fillText('center of rotation', x + toX + 10, y + toY + 0);
ctx.beginPath();
ctx.rect(x, y, width, height);
ctx.strokeStyle = 'lime';
ctx.stroke();
}
function modifySprite() {
var name = this.id;
var value = this.value;
if (name !== 'color') {
value *= 1;
}
sprite[name] = value;
draw();
}
// init
var toShowInfo = true;
document.getElementById('checkbox').onchange = function() {
toShowInfo = !toShowInfo;
draw();
};
var propsDom = document.getElementById('props');
for (var i in sprite) {
var div = document.createElement('div');
var span = document.createElement('span');
var input = document.createElement('input');
span.textContent = i + ':';
input.id = i;
input.value = sprite[i];
input.setAttribute('type', 'text');
input.addEventListener('keyup', modifySprite.bind(input));
div.appendChild(span);
div.appendChild(input);
propsDom.appendChild(div);
}
draw();
body {
font-family: monospace;
}
canvas {
float: left;
background-color: black;
}
div {
float: left;
margin: 0 0 5px 5px;
}
div > div {
float: initial;
}
span {
font-size: 16px;
}
input[type="text"] {
margin: 0 0 5px 5px;
color: #999;
border-width: 0 0 1px 0;
}
<canvas id="myCanvas" width="400" height="400"></canvas>
<div id="props" style="float: left; width: calc(100% - 400px - 5px);">
<div style="float: initial;">
<input type="checkbox" id="checkbox" checked><span>Show origin-shape and the center of rotation</span>
</div>
</div>