I have a web application where you can draw a rectangle on a canvas. I use two canvas elements: one for the preview while drawing and another one laying exactly under the other one for drawing it.
The problem I have is that in Internet Explorer, canvas2.width = canvas2.width doesn't clear the content of canvas2, which is necessary because for every mousemove the rectangle gets drawn again. I also tried context2.clearRect(0,0,canvas2.width,canvas2.height), but, however, then the preview rectangle doesn't get drawn at all. Try it out on http://jsfiddle.net/Y389a/2/
HTML:
<canvas id="canvas" width="600" height="400"></canvas>
<canvas id="canvas2" width="600" height="400" onmouseup="return drawLine()" onmousedown="return startLine()"></canvas>
CSS:
#canvas, #canvas2 {
position:absolute;
left:0px;
top:0px;
border-width:1px;
border-style:solid;
border-color:#666666;
cursor:default !important;
}
Javascript:
var x; var xStart;
var y; var yStart;
var clicked = false;
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var canvas2 = document.getElementById("canvas2");
var context2 = canvas2.getContext("2d");
context.strokeStyle = "black";
context.lineCap = "round";
canvas2.addEventListener('mousemove', function (evt) {
var rect = canvas2.getBoundingClientRect();
x = evt.clientX - rect.left;
y = evt.clientY - rect.top;
if (clicked) {
canvas2.width = canvas2.width;
context2.rect(xStart, yStart, x - xStart, y - yStart);
context2.stroke();
}
}, false);
function startLine() {
context.beginPath();
xStart = x; yStart = y;
clicked = true;
}
function drawLine() {
clicked = false;
context.rect(xStart, yStart, x - xStart, y - yStart);
context.stroke();
}
Preview
Problem
You are drawing rectangles with context2.rect which is a path command.
Path commands are "remembered" by the canvas until a new context2.beginPath is issued
Therefore, all your previous rects are being remembered and redrawn when you do context2.stroke
Fix
Just put context2.beginPath in your mousemove event handler: http://jsfiddle.net/m1erickson/A8ge6/
canvas2.addEventListener("mousedown",startLine);
canvas2.addEventListener("mouseup",drawLine);
canvas2.addEventListener('mousemove', function (evt) {
var rect = canvas2.getBoundingClientRect();
x = evt.clientX - rect.left;
y = evt.clientY - rect.top;
if (clicked) {
canvas2.width = canvas2.width;
console.log(xStart);
// add beginPath so previous context2.rect's are dismissed
context2.beginPath();
context2.rect(xStart, yStart, x - xStart, y - yStart);
context2.stroke();
}
}, false);
If you only need to stroke a rectangle you can use this version:
context2.strokeRect(xStart, yStart, x - xStart, y - yStart);
instead of rect() + stroke().
This does not add any sub path to the main path but draws directly to canvas. If you need to add other shapes to your path later remember to use beginPath() for rect() in a similar way as you already do in startLine() as rect() add a sub-path.
There is Nothing Wrong with the Code and nothing Wrong With IE 9,What you missed is a l'le concept ,
addEventListener() didn't work For IE instead you have to use attachEvent() for it to make your Code run in IE
//For your code to work in IE
if (!canvas2.addEventListener) {
canvas2.attachEvent("onclick", CanvasFunction);
}
//for rest of the Browser
else {
canvas2.addEventListener("click", CanvasFunction, false);
}
function CanvasFunction(evt)
{
var rect = canvas2.getBoundingClientRect();
x = evt.clientX - rect.left;
y = evt.clientY - rect.top;
if (clicked) {
canvas2.width = canvas2.width;
console.log(xStart);
// add beginPath so previous context2.rect's are dismissed
context2.beginPath();
context2.rect(xStart, yStart, x - xStart, y - yStart);
context2.stroke();
}
}
Playing with Canvas ,remember IE doesn't support addEventListners ..Enjoy Coding
Related
I have an ASP.NET application that allows users to click or tap on a Canvas to indicate pain locations on a body image. A body image is displayed on the Canvas and is the same size as the Canvas.
function drawBodyMap() {
var c = document.getElementById('myCanvas');
var ctx = c.getContext('2d');
var imageObj = new Image();
imageObj.src = 'https://.../body.jpg';
imageObj.onload = function () {
ctx.drawImage(imageObj, 0, 0, 600, 367);
};
}
<canvas id="myCanvas" width="600" height="367"></canvas>
<script>
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
canvas.addEventListener('mouseup', function (evt) {
if (ixPos > 9)
return;
var mousePos = getMousePos(canvas, evt);
bodyX[ixPos] = mousePos.x;
bodyY[ixPos] = mousePos.y;
painType[ixPos] = pain_type;
ixPos++;
ctx.beginPath();
ctx.arc(mousePos.x, mousePos.y, 8, 0, 2 * Math.PI);
if (pain_type == 1)
ctx.fillStyle = "#DC143C";
else if (pain_type == 2)
ctx.fillStyle = "#EA728A";
else if (pain_type == 3)
ctx.fillStyle = "#DAA520";
else if (pain_type == 4)
ctx.fillStyle = "#008000";
else if (pain_type == 5)
ctx.fillStyle = "#4169E1";
ctx.fill();
}, false);
</script>
The X,Y points added to the Canvas on the body image are saved to a database. These points are then loaded into a WPF application that displays the same body image on an XAML Canvas. C# code then adds the points over the image.
WPF CODE:
private void DisplayBodyPain()
{
List<BodyPain> pain = gFunc.sws.GetBodyPain(MemberID);
foreach (BodyPain bp in pain)
{
Border b = new Border();
b.Tag = bp.PainType.ToString();
b.Cursor = Cursors.Hand;
b.Width = 16;
b.Height = 16;
b.CornerRadius = new CornerRadius(8);
b.Background = GetPainBrush((byte)bp.PainType);
cvsBody.Children.Add(b);
Canvas.SetTop(b, bp.YPos);
Canvas.SetLeft(b, bp.XPos);
}
}
The problem I have is that the points drawn on the XAML Canvas are all slightly different from the points that were drawn on the HTML Canvas. Each point is not in exactly the same location.
Is there a way I can fix this? Should I be doing it differently?
HTML Canvas
WPF Canvas
I think you need to subtract the size of the marker from the coordinate where you want to place it. For the last two lines, try this instead:
Canvas.SetTop(b, bp.YPos - (b.Height / 2));
Canvas.SetLeft(b, bp.XPos - (b.Width / 2));
By subtracting half the marker's height and width, the center of the marker is placed on the desired coordinates.
With the plugin i found earlier on stackoverflow. Drawing has become smooth and nice. What i want is to only get the image part which i draw cropped from the canvas as an output and not the complete canvas. Can somebody help.
This is the code i am using for my canvas now: http://jsfiddle.net/sVsZL/1/
function canvasDisplay() {
var c=document.getElementById("canvas");
canvasImage=c.toDataURL("image/png");
document.getElementById("SSMySelectedImage").src=canvasImage;
}
Adding another answer because the other one was completely off.
Live Demo
What you need essentially is to keep track of a bounding box. What I do is create an object that holds the min values and max values of where you've drawn. This enables you to keep track of how big the image is and where it begins/ends.
this.dim = {minX : 9999, minY : 9999, maxX : 0, maxY : 0};
Then I created a function that checks the bounds.
this.setDimensions = function(x,y){
if(x < this.dim.minX){
this.dim.minX = x;
}
if(y < this.dim.minY){
this.dim.minY = y;
}
if(x > this.dim.maxX){
this.dim.maxX= x;
}
if(y > this.dim.maxY){
this.dim.maxY = y;
}
}
Make sure to check during clicking or moving.
this.mousedown = function(ev) {
tool.setDimensions(ev._x,ev._y);
};
this.mousemove = function(ev) {
tool.setDimensions(ev._x,ev._y);
};
And this is just a sample function that draws the portion to a new canvas that you could then save with toDataUrl
var button = document.getElementsByTagName("input")[0];
button.addEventListener("click", function(){
var savedCanvas = document.createElement("canvas"),
savedCtx = savedCanvas.getContext("2d"),
minX = PEN.dim.minX,
minY = PEN.dim.minY,
maxX = PEN.dim.maxX,
maxY = PEN.dim.maxY,
width = maxX - minX,
height = maxY - minY;
savedCanvas.width = width;
savedCanvas.height = height;
document.body.appendChild(savedCanvas);
savedCtx.drawImage(canvas,minX,minY,width,height,0,0,width,height);
});
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 got familiarize with canvas with the help of lot of resources available online, and trying to compare the same with svg. My application needs to draw limited number of shapes, but need to be interactive. I think svg would be more suitable being the shapes are dom elements. it would be great help if someone can translate the canvas example (see demo) to svg with only dependency on jQuery and html5 (don't worry about IE)
In the example, I need to draw a rectangle using mouse (left click and drag). you may add each element to the dom (in canvas I may have to keep an array for the rect object, as the screen clears on each event).
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="draw.js"></script>
</head>
<body>
<canvas id="cvs" height="600" width="800"></canvas>
</body>
< /html>
$(document).ready(function() {
var cvs = $("#cvs"),
ctx = cvs.get(0).getContext("2d");
var v_bufX, v_bufY, v_bufW, v_bufH;
var box = function ( ctx, style, x, y, w, h ) {
ctx.beginPath();
ctx.rect( x, y, w, h );
ctx.closePath();
if ( style.fill ) {
ctx.fillStyle = style.fill;
ctx.fill();
}
if ( style.stroke ) {
ctx.strokeStyle = style.stroke;
ctx.lineWidth = style.width || 1;
ctx.stroke();
}
},
draw = function (res) {
var style = {fill:'rgba(96,185,206, 0.3)',stroke:'rgb(96,185,206)',width:.5};
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
box(ctx, style, res.x, res.y, res.w, res.h);
};
var rect = {
reset : function () {
this.x0 = this.y0 = this.x = this.y = this.w = this.h = -1;
this.started = this.dragging = false;
},
mousedown : function (e) {
this.reset();
this.started = true;
this.x0 = e._x;
this.y0 = e._y;
},
mousemove : function (e) {
if (!this.started) {
return;
}
var x = Math.min(e._x, this.x0),
y = Math.min(e._y, this.y0),
w = Math.abs(e._x - this.x0),
h = Math.abs(e._y - this.y0);
console.log(x, y, w, h);
if (!w || !h) {
return;
};
this.x = x;
this.y = y;
this.w = w;
this.h = h;
draw(this);
},
mouseup : function (ev) {
if (this.started) {
this.mousemove(ev);
this.started = false;
draw(this);
}
}
};
$(window).mousedown(function(e) {
var canvasOffset = cvs.offset();
e._x = Math.floor(e.pageX-canvasOffset.left);
e._y = Math.floor(e.pageY-canvasOffset.top);
rect.mousedown(e);
});
$(window).mousemove(function(e) {
var canvasOffset = cvs.offset();
e._x = Math.floor(e.pageX-canvasOffset.left);
e._y = Math.floor(e.pageY-canvasOffset.top);
rect.mousemove(e);
});
$(window).mouseup(function(e) {
var canvasOffset = cvs.offset();
e._x = Math.floor(e.pageX-canvasOffset.left);
e._y = Math.floor(e.pageY-canvasOffset.top);
rect.mouseup(e);
});
});
I'm not willing to rewrite an entire example, but here are some resources that might help:
Embedding SVG in XHTML5 - includes a simple JavaScript that creates some of the elements programmtically.
Dragging Transformed Elements - uses my own dragging code and accounts for translations in transformed hierarchies.
SVGPan - a nice library for panning and zooming
Raphael - a library designed to create SVG/VML (for old IE) from JavaScript, including its own draggable implementation.
KevLinDev - a venerable but incredibly-rich source of tutorials and code related to SVG.
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.