Related
I have this simple canvas webpage that lets user upload photo from camera by using HTML input type file. The idea is to let user make free drawing on their image. However, I have one problem.
On some devices, the image from camera is drawn onto the canvas with wrong orientation, so I have to provide users a button to rotate their image to get the drawing with correct orientation.
The problem is that after the canvas has been transformed and rotated to get the correct orientation, the drawing coordinates seems to be way off. For example, if I draw straight horizontal line, I get instead straight vertical line after the image has been rotated once. I think the problem lies in that fact that canvas orientation is changed.
So how can I correct back the drawing coordinate after image has been transformed and rotate? My code is below..
window.onload = init;
var canvas, ctx, file, fileURL;
var mousePressed = false;
var lastX, lastY;
function init(){
canvas = document.getElementById('myCanvas')
ctx = canvas.getContext('2d')
canvas.addEventListener('mousedown', touchstartHandler, false)
canvas.addEventListener('mousemove', touchmoveHandler, false)
canvas.addEventListener('mouseup', touchendHandler, false)
canvas.addEventListener('mouseleave', touchcancelHandler, false)
}
function touchstartHandler(e){
e.preventDefault()
mousePressed = true;
Draw(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, false);
}
function touchmoveHandler(e){
e.preventDefault()
if (mousePressed) {
Draw(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, true);
}
}
function touchendHandler(e){
e.preventDefault()
if (mousePressed) {
mousePressed = false;
}
}
function touchcancelHandler(e){
e.preventDefault()
if (mousePressed) {
mousePressed = false;
}
}
function Draw(x, y, isDown) {
if (isDown) {
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.lineWidth = 12;
ctx.lineJoin = "round";
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke();
}
lastX = x;
lastY = y;
}
<!DOCTYPE html>
<html>
<head>
<title>Portrait</title>
</head>
<body>
<canvas id="myCanvas"></canvas><br/>
<input type="file" onchange="fileUpload(this.files)" id="file-input" capture="camera"><br/><br/>
<button onclick="rotate()">Rotate</button>
<script>
var file, canvas, ctx, image, fileURL;
function fileUpload(files){
file = files[0]
fileURL = URL.createObjectURL(file)
canvas = document.getElementById('myCanvas')
canvas.style.backgroundColor = "blue"
ctx = canvas.getContext('2d')
image = new Image()
image.onload = function() {
canvas.width = 500
canvas.height = (500*this.height)/this.width
ctx.drawImage(image,0,0,canvas.width,canvas.height)
ctx.save();
}
image.src = fileURL
}
function rotate(){
ctx.clearRect(0,0,canvas.width,canvas.height)
ctx.translate(canvas.width/2, canvas.height/2)
ctx.rotate(90*Math.PI/180)
ctx.translate(-canvas.width/2, -canvas.height/2)
ctx.drawImage(image,0,0,canvas.width,canvas.height)
}
</script>
</body>
</html>
You need to save the canvas state before rotating and translating, and then restore the state when the transformation is done.
var file, canvas, ctx, image, fileURL, rotation = 90;
function fileUpload(files) {
file = files[0]
fileURL = URL.createObjectURL(file)
canvas = document.getElementById('myCanvas')
canvas.style.backgroundColor = "blue"
ctx = canvas.getContext('2d')
image = new Image()
image.onload = function() {
canvas.width = 500
canvas.height = (500 * this.height) / this.width
ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
}
image.src = fileURL
}
function rotate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save(); //save canvas state
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(rotation * Math.PI / 180);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
rotation += 90;
ctx.restore(); //restore canvas state
}
canvas {border: 1px solid red}
<canvas id="myCanvas"></canvas>
<br/>
<input type="file" onchange="fileUpload(this.files)" id="file-input" capture="camera">
<br/>
<br/>
<button onclick="rotate()">Rotate</button>
Simple rotation
Quickest way to rotate the image by steps of 90 deg
ctx.setTransform(
0,1, // direction of x axis
-1,0 // direction of y axis
canvas.width,0 // location in pixels of the origin (0,0)
);
Then draw the image
ctx.drawImage(image,0,0);
Rather than use ctx.restore() that can be slow in many situations you can eset only the transform to the default with.
ctx.setTransform(1,0,0,1,0,0);
Rotate 90, 180, -90deg
Thus to rotate 90 deg
ctx.setTransform(0,1,-1,0,canvas.width,0);
ctx.drawImage(image,0,0);
ctx.setTransform(1,0,0,1,0,0);
Thus to rotate 180 deg
ctx.setTransform(-1,0,0,-1,canvas.width,canvas.height);
ctx.drawImage(image,0,0);
ctx.setTransform(1,0,0,1,0,0);
Thus to rotate -90 deg
ctx.setTransform(0,-1,1,0,0,canvas.height);
ctx.drawImage(image,0,0);
ctx.setTransform(1,0,0,1,0,0);
I have to create a rotation wheel with 12 fields like in the image below link :http://www.resilienciacomunitaria.org/
How i create through which approach?
I used canvas for this but not successful i used d3.js svg but not successful .
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="600" height="600"
style="background-color:#ffff">
</canvas>
<script type="text/javascript">
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var radius = canvas.height /2; //400
//alert(radius);
//draw a circle again and agian
ctx.translate(radius, radius);
radius =radius*0.85;
setInterval(drawCircle, 50);
function drawCircle() {
var pos = .01;
var length = 100;
var width = 40;
drawFace(ctx, radius);
drawHand(ctx, pos, length, width);
}
function drawFace(ctx,radius){
ctx.beginPath();
ctx.arc(0, 0, 50, 0, 2 * Math.PI, false);
ctx.fillStyle = '#ffff';
ctx.strokeStyle = 'blue';
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI, false);
ctx.strokeStyle = 'yellow';
ctx.fillStyle = 'blue';
ctx.lineWidth = 50;
ctx.fill();
ctx.stroke();
}
function drawHand(ctx, pos, length, width) {
ctx.beginPath();
ctx.lineWidth = 30;
ctx.moveTo(-radius,0);
ctx.lineTo(radius, 0);
ctx.moveTo(-radius,150);
ctx.lineTo(radius, -150);
ctx.moveTo(-radius,-150);
ctx.lineTo(radius, 150);
ctx.moveTo(-radius,380);
ctx.lineTo(radius, -380);
ctx.moveTo(-radius,-380);
ctx.lineTo(radius, 380);
ctx.moveTo(0, -radius);
ctx.lineTo(0, radius);
ctx.stroke();
/*
ctx.globalCompositeOperation='destination-over';
ctx.font="20px Verdana";
ctx.fillStyle = 'white';
ctx.fillText("Explore Zero",180,180);
ctx.stroke();
ctx.globalCompositeOperation='source-over';*/
ctx.rotate(-pos);
}
</script>
</body>
</html>
Thanks in advance
Here's code to get you started:
You can style it to your specific needs
Create an in-memory canvas containing your wheel.
Create an in-memory canvas containing your spike-indicator.
Rotate the canvas and draw the wheel on the main canvas.
Draw the indicator on the main canvas.
Change the rotation angle for the next loop.
Repeat, repeat, repeat using requestAnimationFrame.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var PI2=Math.PI*2;
var myData = [1,2,3,4,5,6,7,8,9,10,11,12];
var cx=150;
var cy=150;
var radius=150;
var wheel=document.createElement('canvas');
var wheelCtx=wheel.getContext('2d');
var indicator=document.createElement('canvas');
var indicatorCtx=indicator.getContext('2d');
var angle=PI2-PI2/4;
var myColor = [];
for(var i=0;i<myData.length;i++){ myColor.push(randomColor()); }
makeWheel();
makeIndicator();
requestAnimationFrame(animate);
function makeWheel(){
wheel.width=wheel.height=radius*2+2;
wheelCtx.lineWidth=1;
wheelCtx.font='24px verdana';
wheelCtx.textAlign='center';
wheelCtx.textBaseline='middle';
var cx=wheel.width/2;
var cy=wheel.height/2;
var sweepAngle=PI2/myData.length;
var startAngle=0;
for(var i=0;i<myData.length;i++){
// calc ending angle based on starting angle
var endAngle=startAngle+sweepAngle;
// draw the wedge
wheelCtx.beginPath();
wheelCtx.moveTo(cx,cy);
wheelCtx.arc(cx,cy,radius,startAngle,endAngle,false);
wheelCtx.closePath();
wheelCtx.fillStyle=myColor[i];
wheelCtx.strokeStyle='black';
wheelCtx.fill();
wheelCtx.stroke();
// draw the label
var midAngle=startAngle+(endAngle-startAngle)/2;
var labelRadius=radius*.85;
var x=cx+(labelRadius)*Math.cos(midAngle);
var y=cy+(labelRadius)*Math.sin(midAngle);
wheelCtx.fillStyle='gold';
wheelCtx.fillText(myData[i],x,y);
wheelCtx.strokeText(myData[i],x,y);
// increment angle
startAngle+=sweepAngle;
}
}
function makeIndicator(){
indicator.width=indicator.height=radius+radius/10;
indicatorCtx.font='18px verdana';
indicatorCtx.textAlign='center';
indicatorCtx.textBaseline='middle';
indicatorCtx.fillStyle='skyblue';
indicatorCtx.strokeStyle='blue';
indicatorCtx.lineWidth=1;
var cx=indicator.width/2;
var cy=indicator.height/2;
indicatorCtx.beginPath();
indicatorCtx.moveTo(cx-radius/8,cy);
indicatorCtx.lineTo(cx,cy-indicator.height/2);
indicatorCtx.lineTo(cx+radius/8,cy);
indicatorCtx.closePath();
indicatorCtx.fillStyle='skyblue'
indicatorCtx.fill();
indicatorCtx.stroke();
indicatorCtx.beginPath();
indicatorCtx.arc(cx,cy,radius/3,0,PI2);
indicatorCtx.closePath();
indicatorCtx.fill();
indicatorCtx.stroke();
indicatorCtx.fillStyle='blue';
indicatorCtx.fillText('Prizes',cx,cy);
}
function animate(time){
ctx.clearRect(0,0,cw,ch);
ctx.translate(cw/2,ch/2);
ctx.rotate(angle);
ctx.drawImage(wheel,-wheel.width/2,-wheel.height/2);
ctx.rotate(-angle);
ctx.translate(-cw/2,-ch/2);
ctx.drawImage(indicator,cw/2-indicator.width/2,ch/2-indicator.height/2)
angle+=PI2/360;
requestAnimationFrame(animate);
}
function randomColor(){
return('#'+Math.floor(Math.random()*16777215).toString(16));
}
body{ background-color: ivory; padding:10px; }
canvas{border:1px solid red;}
<canvas id="canvas" width=400 height=300></canvas>
You can just put the "wheel" image on the website and just rotate it.
document.getElementById("TheImage").style.transform = "rotate("+YourAngle+"deg)";
Also you will need to put the "pointer" image on top of "wheel" image. (you will not rotate this one)
This is a fiddle written in 20 minutes, hope it helps.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 500;
var lines = new Array();
lines[0] = new Array();
lines[0] = ["Prize 1","#000000"];
lines[1] = new Array();
lines[1] = ["Prize 2","#ffff00"];
lines[2] = new Array();
lines[2] = ["Prize 3","#ff00ff"];
lines[3] = new Array();
lines[3] = ["Prize 4","#00ffff"];
lines[4] = new Array();
lines[4] = ["Prize 5","#00ff00"];
var TO_RADIANS = Math.PI / 180;
var angle = 360 / lines.length; //to see how far apart the lines need to be
var angle_offset = 0; //this will determine the spinning
var angle_speed = 1; //degrees per cycle
var center_offset = 20; //the radius of your spinner in the middle
function animate() {
ctx.clearRect(0,0,canvas.width,canvas.height);
angle_offset+=angle_speed;
ctx.font="20px Verdana";
ctx.fillStyle="#000000";
for (i=0; i<lines.length; i++) {
ctx.fillStyle=lines[i][1];
ctx.save();
ctx.translate(canvas.width/2, canvas.height/2);
ctx.rotate((angle * i + angle_offset) * TO_RADIANS);
//Here you can also decorate with boxes and stuff
ctx.fillText(lines[i][0],center_offset,0);
ctx.restore();
}
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
I have a quadratic curve rendered on a canvas. I want to animate it by means of window.setInterval and changing it's dimensions (note not simply changing it's scale) thereafter.
How do I retain an editable reference to the path after calling context.closePath()?
I'd recommend that you maintained a reference to the path in a new Path object; that way you could modify x, y, points etc on the fly and then render it each animation step.
var testPath = new Path(100, 100, [[40, 40], [80, 80]]);
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
function Path(x, y, points)
{
this.x = x;
this.y = y;
this.points = points;
}
function update()
{
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = 'red';
ctx.moveTo(testPath.points[0][0], testPath.points[0][1]);
for (var i = 1; i < testPath.points.length; i++)
{
ctx.lineTo(testPath.points[i][0], testPath.points[i][1]);
}
ctx.stroke();
testPath.points[1][1]++; // move down
// loop
requestAnimationFrame(update);
}
update();
For some reason JSFiddle doesn't play nice with Paul Irish's requestAnimationFrame polyfill but it should work locally. I'd definitely recommend this over setInterval.
http://jsfiddle.net/d2sSg/1/
Simple animation that creates a firework-like effect on the canvas with each click. The issue is the animation is made with a setInterval(draw) and every time the canvas is redrawn the location of each particle is += particle.speed. But with each click the particles move faster and faster as it seems the speed of each particle is not reset.
As you can see with a couple clicks on the working example here: , with the first click the particles move very (correctly) slowly, but with each subsequent click the speed is increased.
JS used is pasted below as well, any help is greatly appreciated!
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.addEventListener("click", startdraw, false);
//Lets resize the canvas to occupy the full page
var W = window.innerWidth;
var H = window.innerHeight;
canvas.width = W;
canvas.height = H;
ctx.fillStyle = "black";
ctx.fillRect(0, 0, W, H);
//global variables
var radius;
radius = 10;
balls_amt = 20;
balls = [];
var locX = Math.round(Math.random()*W);
var locY = Math.round(Math.random()*H);
//ball constructor
function ball(positionx,positiony,speedX,speedY)
{
this.r = Math.round(Math.random()*255);
this.g = Math.round(Math.random()*255);
this.b = Math.round(Math.random()*255);
this.a = Math.random();
this.location = {
x: positionx,
y:positiony
}
this.speed = {
x: -2+Math.random()*4,
y: -2+Math.random()*4
};
}
function draw(){
ctx.globalCompositeOperation = "source-over";
//Lets reduce the opacity of the BG paint to give the final touch
ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
ctx.fillRect(0, 0, W, H);
//Lets blend the particle with the BG
//ctx.globalCompositeOperation = "lighter";
for(var i = 0; i < balls.length; i++)
{
var p = balls[i];
ctx.beginPath();
ctx.arc(p.location.x, p.location.y, radius, Math.PI*2, false);
ctx.fillStyle = "rgba("+p.r+","+p.g+","+p.b+", "+p.a+")";
ctx.fill();
var consolelogX = p.location.x;
var consolelogY = p.location.y;
p.location.x += p.speed.x;
p.location.y += p.speed.y;
}
}
function startdraw(e){
var posX = e.pageX; //find the x position of the mouse
var posY = e.pageY; //find the y position of the mouse
for(i=0;i<balls_amt;i++){
balls.push(new ball(posX,posY));
}
setInterval(draw,20);
//ball[1].speed.x;
}
After each click startdraw is called, which starts every time a new periodical call (setInterval) for the draw method. So after the 2nd click you have 2 parallel intervals, after the 3rd you have 3 parallel intervals.
It is not exponentially, only linearly increasing :)
A possible dirty fix:
Introduce an interval global variable, and replace this row:
setInterval(draw,20);
with this one:
if (!interval) interval = setInterval(draw,20);
Or a nicer solution is to start the interval at the onLoad event.
setInterval will repeat its call every 20th ms, and returns an ID.
You can stop the repetition by calling clearInterval(ID).
var id = setInterval("alert('yo!');", 500);
clearInterval(id);
I am trying to move an image from the right to the center and I am not sure if this is the best way.
var imgTag = null;
var x = 0;
var y = 0;
var id;
function doCanvas()
{
var canvas = document.getElementById('icanvas');
var ctx = canvas.getContext("2d");
var imgBkg = document.getElementById('imgBkg');
imgTag = document.getElementById('imgTag');
ctx.drawImage(imgBkg, 0, 0);
x = canvas.width;
y = 40;
id = setInterval(moveImg, 0.25);
}
function moveImg()
{
if(x <= 250)
clearInterval(id);
var canvas = document.getElementById('icanvas');
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
var imgBkg = document.getElementById('imgBkg');
ctx.drawImage(imgBkg, 0, 0);
ctx.drawImage(imgTag, x, y);
x = x - 1;
}
Any advice?
This question is 5 years old, but since we now have requestAnimationFrame() method, here's an approach for that using vanilla JavaScript:
var imgTag = new Image(),
canvas = document.getElementById('icanvas'),
ctx = canvas.getContext("2d"),
x = canvas.width,
y = 0;
imgTag.onload = animate;
imgTag.src = "http://i.stack.imgur.com/Rk0DW.png"; // load image
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
ctx.drawImage(imgTag, x, y); // draw image at current position
x -= 4;
if (x > 250) requestAnimationFrame(animate) // loop
}
<canvas id="icanvas" width=640 height=180></canvas>
drawImage() enables to define which part of the source image to draw on target canvas. I would suggest for each moveImg() calculate the previous image position, overwrite the previous image with that part of imgBkg, then draw the new image. Supposedly this will save some computing power.
Here's my answer.
var canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var myImg = new Image();
var myImgPos = {
x: 250,
y: 125,
width: 50,
height: 25
}
function draw() {
myImg.onload = function() {
ctx.drawImage(myImg, myImgPos.x, myImgPos.y, myImgPos.width, myImgPos.height);
}
myImg.src = "https://mario.wiki.gallery/images/thumb/c/cc/NSMBUD_Mariojump.png/1200px-NSMBUD_Mariojump.png";
}
function moveMyImg() {
ctx.clearRect(myImgPos.x, myImgPos.y, myImgPos.x + myImgPos.width, myImgPos.y +
myImgPos.height);
myImgPos.x -= 5;
}
setInterval(draw, 50);
setInterval(moveMyImg, 50);
<canvas id="canvas" class="canvas" width="250" height="150"></canvas>
For lag free animations,i generally use kinetic.js.
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var hexagon = new Kinetic.RegularPolygon({
x: stage.width()/2,
y: stage.height()/2,
sides: 6,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4
});
layer.add(hexagon);
stage.add(layer);
var amplitude = 150;
var period = 2000;
// in ms
var centerX = stage.width()/2;
var anim = new Kinetic.Animation(function(frame) {
hexagon.setX(amplitude * Math.sin(frame.time * 2 * Math.PI / period) + centerX);
}, layer);
anim.start();
Here's the example,if you wanna take a look.
http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-animate-position-tutorial/
Why i suggest this is because,setInterval or setTimeout a particular function causes issues when large amount of simultaneous animations take place,but kinetic.Animation deals with framerates more intelligently.
Explaining window.requestAnimationFrame() with an example
In the following snippet I'm using an image for the piece that is going to be animated.
I'll be honest... window.requestAnimationFrame() wasn't easy for me to understand, that is why I coded it as clear and intuitive as possible. So that you may struggle less than I did to get my head around it.
const
canvas = document.getElementById('root'),
btn = document.getElementById('btn'),
ctx = canvas.getContext('2d'),
brickImage = new Image(),
piece = {image: brickImage, x:400, y:70, width:70};
brickImage.src = "https://i.stack.imgur.com/YreH6.png";
// When btn is clicked execute start()
btn.addEventListener('click', start)
function start(){
btn.value = 'animation started'
// Start gameLoop()
brickImage.onload = window.requestAnimationFrame(gameLoop)
}
function gameLoop(){
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Draw at coordinates x and y
ctx.drawImage(piece.image, piece.x, piece.y)
let pieceLeftSidePos = piece.x;
let middlePos = canvas.width/2 - piece.width/2;
// Brick stops when it gets to the middle of the canvas
if(pieceLeftSidePos > middlePos) piece.x -= 2;
window.requestAnimationFrame(gameLoop) // Needed to keep looping
}
<input id="btn" type="button" value="start" />
<p>
<canvas id="root" width="400" style="border:1px solid grey">
A key point
Inside the start() function we have:
brickImage.onload = window.requestAnimationFrame(gameLoop);
This could also be written like: window.requestAnimationFrame(gameLoop);
and it would probably work, but I'm adding the brickImage.onload to make sure that the image has loaded first. If not it could cause some issues.
Note: window.requestAnimationFrame() usually loops at 60 times per second.