Multiplayer Drawing with HTML5 , JS and Node js - javascript

Im trying to make multiplayer drawing for touch optimized devices, i used node js with socket io to plot points over canvas. but the problem is, "after touchend event is called, its not resetting",
To be clear, check this image below. Red lines are needed but Blue lines are plotted automatically on next touch
Here is my code:
if (is_touch_device) {
var drawer = {
isDrawing: false,
touchstart: function (coors) {
prev.x = coors.x;
prev.y = coors.y;
context.beginPath();
context.moveTo(coors.x, coors.y);
this.isDrawing = true;
},
touchmove: function (coors) {
if($.now() - lastEmit > 5){
socket.emit('mousemove',{
'x': coors.x,
'y': coors.y,
'drawing': drawer.isDrawing,
'id': id,
'color': 'test'
});
lastEmit = $.now();
}
if (this.isDrawing) {
plot(prev.x, prev.y, coors.x, coors.y);
prev.x = coors.x;
prev.y = coors.y;
}
},
touchend: function (coors) {
if (this.isDrawing) {
this.isDrawing = false;
}
}
};
function draw(event) {
var coors = {
x: event.targetTouches[0].pageX,
y: event.targetTouches[0].pageY
};
var obj = sigCanvas;
if (obj.offsetParent) {
do {
coors.x -= obj.offsetLeft;
coors.y -= obj.offsetTop;
}
while ((obj = obj.offsetParent) != null);
}
drawer[event.type](coors);
}
Node Plotting Part:
socket.on('moving', function (data) {
if(data.drawing && clients[data.id]){
plot(clients[data.id].x, clients[data.id].y, data.x, data.y);
}
clients[data.id] = data;
clients[data.id].updated = $.now();
});
Plot Function:
function plot(x1, y1, x2, y2)
{
var sigCanvas = document.getElementById("canvasSignature");
var context = sigCanvas.getContext("2d");
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
}
Script js: http://abnode.azurewebsites.net/script.js
My node js : http://abnode.azurewebsites.net/server.js
Update
socket.on('moving', function (data) {
if(data.drawing && clients[data.id] ){
// Problem is it gets plotted automatically from one point on touchend, its not stopped
drawLine(clients[data.id].x, clients[data.id].y, data.x, data.y);
}
clients[data.id] = data;
clients[data.id].updated = $.now();
});
function drawLine(fromx, fromy, tox, toy){
context.beginPath();
context.moveTo(fromx, fromy);
context.lineTo(tox, toy);
context.strokeStyle = "Black";
context.stroke();
}

As per your question , you have to set drawing=false when touchend occurs and than when touchstart event occurs you should get touchPosition by getTouchPos() method from canvas and emit mousemove event with drawing=false, and then set drawing=true.
For example what I have done in my code with same issue you faced:
canvas.on('touchstart', function(e) {
//e.preventDefault();
var pos = getTouchPos(canvas, e);
//e.preventDefault();
prev.x = pos.x;
prev.y = pos.y;
socket.emit('mousemove', {
'x' : pos.x,
'y' : pos.y,
'drawing' : drawing,
'id' : id
});
drawing = true;
// alert("touch experienced");
});
//getTouchMethod according to canvas
function getTouchPos(canvas, evt) {
var rect = canvas.offset();
return {
x : evt.originalEvent.touches[0].pageX - rect.left,
y : evt.originalEvent.touches[0].pageY - rect.top
};
}

Related

How to make a ctrl+z keyboard event in a canvas?

I am trying to make an 'undo' action like Ctrl + Z.
I made a simple canvas paint example to make it easier to understand what I would like. When the user moves the mouse without releasing mouse1, something will be drawn on the canvas. Then, when they stop pressing mouse1, the drawing ends. The Ctrl + Z shortcut will undo these drawings.
Here is the code:
//var
const canvas = document.getElementById('canvas');
const cC = canvas.getContext('2d');
//trigger for write or not
var pressedQ = false;
//
function getMousePosition(evt) {
var rect = canvas.getBoundingClientRect();
var root = document.documentElement;
var mouseX = evt.clientX - rect.left - root.scrollLeft;
var mouseY = evt.clientY - rect.top - root.scrollTop;
return {
x: mouseX,
y: mouseY
};
}
function writeCircle(posX, posY, size, color) {
cC.fillStyle = 'black';
cC.beginPath();
cC.arc(posX, posY, size, 0, Math.PI*2, true);
cC.fill();
}
function pencil(evt) {
if (pressedQ == true) {
var mousePos = getMousePosition(evt);
writeCircle(mousePos.x, mousePos.y, 50);
}
else{}
}
function activate(textmode, evt) {
pressedQ = true;
console.log('start');
}
function deactivate() {
pressedQ = false;
console.log('finish');
}
window.onload = function() {
cC.clearRect(0, 0, canvas.width, canvas.height);
canvas.addEventListener('mousedown', activate);
canvas.addEventListener('mousemove', pencil);
canvas.addEventListener('mouseup', deactivate);
}
<canvas id="canvas" width="700" height="490"></canvas>
Thanks!
Note: I cant understand ES2015+ syntax
What you're looking for is the command design pattern. Since you're using functions here, you just need to store the function name and its parameters. Then, you can use that data to call the function again later. The following example isn't perfect, but it should demonstrate the basic idea. (I tried to avoid any JavaScript syntax that was added after the 2015 update)
var canvas = document.getElementById('canvas');
var cC = canvas.getContext('2d');
var pressedQ = false; //trigger for write or not
var commands = [];
var commandTypes = {
drawCircle: function (posX, posY, size, color) {
cC.fillStyle = 'black';
cC.beginPath();
cC.arc(posX, posY, size, 0, Math.PI * 2, true);
cC.fill();
}
};
function execute() {
var commandType = arguments[0];
var data = Array.prototype.slice.call(arguments, 1);
if (!commandTypes.hasOwnProperty(commandType))
throw new Error(commandType + ' is not a real command');
commandTypes[commandType].apply(null, data);
}
function pushAndExecute() {
commands.push(arguments);
execute.apply(null, arguments);
}
function getMousePosition(evt) {
var rect = canvas.getBoundingClientRect();
var root = document.documentElement;
return {
x: evt.offsetX - rect.left - root.scrollLeft,
y: evt.offsetY - rect.top - root.scrollTop
};
}
function pencil(evt) {
if (!pressedQ) return;
var mousePos = getMousePosition(evt);
pushAndExecute('drawCircle', mousePos.x, mousePos.y, 50);
}
function activate(evt) {
pressedQ = true;
// console.log('start');
pencil(evt);
}
function deactivate() {
pressedQ = false;
// console.log('finish');
}
function handleKeys(evt) {
if (evt.ctrlKey && evt.key === 'z') {
// console.log('undo');
// Remove the most recent command from the list
commands.splice(-1, 1);
// Clear canvas
cC.clearRect(0, 0, canvas.width, canvas.height);
// Re-play all commands (re-draw canvas from scratch)
commands.forEach(function (command) {
execute.apply(null, command);
});
}
}
window.onload = function() {
cC.clearRect(0, 0, canvas.width, canvas.height);
canvas.addEventListener('mousedown', activate);
canvas.addEventListener('mousemove', pencil);
canvas.addEventListener('mouseup', deactivate);
window.addEventListener('keydown', handleKeys);
}
<canvas id="canvas" width="700" height="490"></canvas>

HTML5 Canvas line draw between two clicks, live

You might see that this is my first post so excuse any beginner mistakes using stackoverflow.
I'm currently working on a floor plan web app where you can simply draw lines to create the floor plan of your housing.
The wanted effect is:
When clicking once the user draws a temporary line where the start point X is the clicked point and the target point Z is the mouse which the user can move around.
I'm currently using canvas for this effect but somehow the line is invisible or just not there. I've tried some debugging which brought me here.
This is the current code:
function drawLineXY(fromXY, toXY) {
if (!lineElem) {
lineElem = document.createElement('canvas');
lineElem.style.position = "absolute";
lineElem.style.zIndex = 100;
document.body.appendChild(lineElem);
console.log("Added line element");
}
var leftpoint, rightpoint;
if (fromXY.x < toXY.x) {
leftpoint = fromXY;
rightpoint = toXY;
} else {
leftpoint = toXY;
rightpoint = fromXY;
}
var lineWidthPix = 4;
var gutterPix = 0;
var origin = {
x: leftpoint.x - gutterPix,
y: Math.min(fromXY.y, toXY.y) - gutterPix
};
lineElem.width = "1000px";
lineElem.height = "1000px";
lineElem.style.left = "0px";
lineElem.style.top = "0px";
var ctx = lineElem.getContext('2d');
// Use the identity matrix while clearing the canvas
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, lineElem.width, lineElem.height);
ctx.restore();
ctx.lineWidth = 4;
ctx.strokeStyle = '#09f';
ctx.beginPath();
ctx.moveTo(fromXY.x - origin.x, fromXY.y - origin.y);
ctx.lineTo(toXY.x - origin.x, toXY.y - origin.y);
ctx.stroke();
console.log("drawing line..");
}
function moveHandler(evt) {
var startCentre, startBounds;
var targets = [];
if (clicked.length === 2) {
targets = clicked;
} else if (clicked.length === 1) {
targets.push(clicked[0]);
if (typeof hoverElement !== 'undefined') {
targets.push(hoverElement);
}
}
if (targets.length == 2) {
var start = {
x: targets[0],
y: targets[0]
};
var end = {
x: targets[1],
y: targets[1]
};
drawLineXY(start, end);
} else if (targets.length == 1) {
var start = {
x: targets[0],
y: targets[0]
};
drawLineXY(start, {
x: evt.clientX,
y: evt.clientY
});
}
};
function clickHandler(e) {
if (clicked.length == 2) {
clicked = [];
}
clicked.push(e.target);
};
document.onclick = clickHandler;
document.onmousemove = moveHandler;
As you can see in drawLineXY's last line I've made a debug console log "drawing line"
This works as I move the mouse around. Like it should.
But there is no line, does someone has help?
PS: #canvas is specified in style.css.
I created a very basic example of probably what you are trying to achieve:
let c, ctx, fromXY, toXY;
window.onload = function(){
document.onclick = clickHandler;
document.onmousemove = moveHandler;
c = document.getElementById("myCanvas");
ctx = c.getContext("2d");
reset();
}
function draw(){
clear();
ctx.beginPath();
ctx.moveTo(fromXY.x, fromXY.y);
ctx.lineTo(toXY.x, toXY.y);
ctx.stroke();
ctx.closePath();
}
function clear(){
ctx.clearRect(0, 0, c.width, c.height);
}
function reset() {
fromXY = {};
toXY = {};
}
function moveHandler(e) {
if(typeof fromXY.x !== "undefined"){
toXY.x = e.clientX;
toXY.y = e.clientY;
draw();
}
}
function clickHandler(e) {
if(typeof fromXY.x === "undefined"){
fromXY.x = e.clientX;
fromXY.y = e.clientY;
}else{
reset();
}
}
<canvas id="myCanvas" height="500" width="500"></canvas>
You can set line options in the draw() function, and if you want the lines to persist, you would save their fromXY and toXY in an array and redraw them as well.

canvas - how to select multi rectangle area on image?

var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var rect = {};
var drag = false;
make_base();
init();
function make_base() {
base_image = new Image();
base_image.src = 'https://www.w3schools.com/css/img_fjords.jpg';
base_image.onload = function() {
context.drawImage(base_image, 0, 0, 800, 500);
}
}
function writeMessage(canvas, message) {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(base_image, 0, 0, 800, 500);
context.font = '12pt Calibri';
context.fillStyle = 'red';
context.fillText(message, 25, 25);
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
function init() {
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
}
function mouseDown(e) {
rect.startX = e.pageX - this.offsetLeft;
rect.startY = e.pageY - this.offsetTop;
drag = true;
}
function mouseUp() {
drag = false;
}
function mouseMove(e) {
if(drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY ;
context.clearRect(rect.startX, rect.startY, rect.w, rect.h);
draw();
}
}
function draw() {
context.lineWidth="1";
context.strokeStyle = "blue";
context.beginPath();
context.rect(rect.startX, rect.startY, rect.w, rect.h);
context.stroke();
context.closePath();
}
<canvas id="myCanvas" width="800" height="500" style="border:1px solid #000000;">
</canvas>
Now I can draw multi rectangle on image using mouse.
However, it will be white?
How to draw a rectangle more like select a area?
Best practice.
Rendering
You should never render from mouse or other UI events as they are not synced to the display. This can result in unpleasant shearing and flickering and also cause needless rendering that may only be partially or not at all seen, chewing power and battery life for no reason.
If you are regularly updating elements in the DOM (not just canvas) use render loop called via requestAnimationFrame. This ensures you only present DOM content that can be seen.
Lost input
When getting mouse input that is intended to be dragged you should listen to the document's mouse events rather than the element's. This allows you to follow the dragged input when it moves of the element and page. If you do not do this the drag can get locked if the user drags of the element/page and releases the mouse button.
Both the other answers fail to handle this correctly.
Constants and style
Use constants (const) wherever possible. They are block scoped and help to reduce bugs (though this is arguable).
Get used to adding "use strict"; top the top of your code, it will help you reduce and spot bugs early. Do not add "use strict" once you have completed the code unless you do full testing cycle as it will/may break what was once working code.
Though best practice do not define naming styles it does mean that you use a naming style consistently. If you choose snake case (snake_case) then use it throughout the code, if you choose camel case (camelCase) then only use that. You remember variable as the words, trying to remember which style you used for a variable will slow you down and can result in bugs.
"use strict";
requestAnimationFrame(mainLoop);
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
const storedRects = [];
const baseImage = loadImage("https://www.w3schools.com/css/img_fjords.jpg");
var refresh = true;
const rect = (() => {
var x1, y1, x2, y2;
var show = false;
function fix() {
rect.x = Math.min(x1, x2);
rect.y = Math.min(y1, y2);
rect.w = Math.max(x1, x2) - Math.min(x1, x2);
rect.h = Math.max(y1, y2) - Math.min(y1, y2);
}
function draw(ctx) { ctx.strokeRect(this.x, this.y, this.w, this.h) }
const rect = {x : 0, y : 0, w : 0, h : 0, draw};
const API = {
restart(point) {
x2 = x1 = point.x;
y2 = y1 = point.y;
fix();
show = true;
},
update(point) {
x2 = point.x;
y2 = point.y;
fix();
show = true;
},
toRect() {
show = false;
return Object.assign({}, rect);
},
draw(ctx) {
if (show) { rect.draw(ctx) }
},
show : false,
}
return API;
})();
function loadImage(url) {
const image = new Image();
image.src = url;
image.onload = () => refresh = true;
return image;
}
const mouse = {
button : false,
x : 0,
y : 0,
down : false,
up : false,
element : null,
event(e) {
const m = mouse;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;
const prevButton = m.button;
m.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
if (!prevButton && m.button) { m.down = true }
if (prevButton && !m.button) { m.up = true }
},
start(element) {
mouse.element = element;
"down,up,move".split(",").forEach(name => document.addEventListener("mouse" + name, mouse.event));
}
}
mouse.start(canvas);
function draw() {
ctx.drawImage(baseImage, 0, 0, ctx.canvas.width, ctx.canvas.width);
ctx.lineWidth = 1;
ctx.strokeStyle = "yellow";
storedRects.forEach(rect => rect.draw(ctx));
ctx.strokeStyle = "red";
rect.draw(ctx);
}
function mainLoop() {
if (refresh || mouse.down || mouse.up || mouse.button) {
refresh = false;
if (mouse.down) {
mouse.down = false;
rect.restart(mouse);
} else if (mouse.button) {
rect.update(mouse);
} else if (mouse.up) {
mouse.up = false;
rect.update(mouse);
storedRects.push(rect.toRect());
}
draw();
}
requestAnimationFrame(mainLoop)
}
<canvas id="myCanvas" width="800" height="500" title = "click and drag to add rectangles" style="border:1px solid #000000;cursor:crosshair"></canvas>
Heavily inspired by #Blindman67's excellent answer, this solution accepts touch events as well as mouse events, which I required for my own use.
The idea of the API Object was maintained but called "RectangleMaker".
Animation frames are only requested on the move event.
I found it hard to follow the signature mouse object run through the mainLoop loop function so I pulled that apart into three functions: "start", "move" and "stop". Events are added individually for each event type.
There are a few extra bits:
The code is invoked via a constructor so as to allow many canvases on a page.
Rectangle can preloaded, rectangle can be returned.
function AnnotateImage (canvas) {
const canvasContext = canvas.getContext('2d');
const rectangleMaker = new RectangleMaker(canvasContext);
const mouseObj = { x: 0, y: 0 };
let dragAction = false;
const img1 = new Image();
img1.src = canvas.toDataURL();
attachEvents(canvas);
let rectangles = [];
this.returnRectangles = () => {
return rectangles;
};
this.loadRectangles = (rects) => {
rectangles = rects;
rectangleMaker.drawRectangles();
return this;
};
function RectangleMaker (canvasContext) {
let x1, y1, x2, y2;
const finishedRectangle = { x: 0, y: 0, w: 0, h: 0 };
function makeFinishedRectangle () {
finishedRectangle.x = Math.min(x1, x2);
finishedRectangle.y = Math.min(y1, y2);
finishedRectangle.w = Math.max(x1, x2) - Math.min(x1, x2);
finishedRectangle.h = Math.max(y1, y2) - Math.min(y1, y2);
}
this.setFirstPoint = (point) => {
return setFirstPoint(point);
};
function setFirstPoint (point) {
x2 = x1 = point.x;
y2 = y1 = point.y;
makeFinishedRectangle();
}
this.setSecondPoint = (point) => {
return setSecondPoint(point);
};
function setSecondPoint (point) {
x2 = point.x;
y2 = point.y;
makeFinishedRectangle();
}
this.saveLocal = () => {
const rect = Object.assign({}, finishedRectangle);
rectangles.push(rect);
};
this.getRectangleToSave = () => {
return getRectangleToSave();
};
function getRectangleToSave () {
return Object.assign({}, finishedRectangle);
}
this.drawRectangles = () => {
return drawRectangles();
};
function drawRectangles () {
if (rectangles && rectangles.length > 0) {
rectangles.forEach((rectangle) => {
canvasContext.strokeRect(rectangle.x, rectangle.y, rectangle.w, rectangle.h);
});
}
}
}
function attachEvents (canvas) {
canvas.addEventListener('mousedown', (e) => {
start(e);
});
canvas.addEventListener('touchstart', (e) => {
start(e);
});
function start (e) {
const bounds = canvas.getBoundingClientRect();
mouseObj.x = (e.type.includes('mouse')) ? e.pageX - bounds.left - scrollX : e.targetTouches[0].pageX - bounds.left - scrollX;
mouseObj.y = (e.type.includes('mouse')) ? e.pageY - bounds.top - scrollY : e.targetTouches[0].pageY - bounds.top - scrollY;
rectangleMaker.setFirstPoint(mouseObj);
dragAction = true;
}
canvas.addEventListener('mousemove', (e) => {
move(e);
});
canvas.addEventListener('touchmove', (e) => {
move(e);
});
function move (e) {
e.preventDefault();
if (dragAction) {
// const prior = rectangleMaker.getRectangleToSave();
// canvasContext.strokeRect(prior.x, prior.y, prior.w, prior.h);
const bounds = canvas.getBoundingClientRect();
mouseObj.x = (e.type.includes('mouse')) ? e.pageX - bounds.left - scrollX : e.targetTouches[0].pageX - bounds.left - scrollX;
mouseObj.y = (e.type.includes('mouse')) ? e.pageY - bounds.top - scrollY : e.targetTouches[0].pageY - bounds.top - scrollY;
rectangleMaker.setSecondPoint(mouseObj);
const newRect = rectangleMaker.getRectangleToSave();
window.requestAnimationFrame(() => {
canvasContext.drawImage(img1, 0, 0);
rectangleMaker.drawRectangles();
canvasContext.strokeRect(newRect.x, newRect.y, newRect.w, newRect.h);
});
}
}
canvas.addEventListener('mouseup', (e) => {
stop(e);
});
canvas.addEventListener('touchend', (e) => {
stop(e);
});
function stop (e) {
dragAction = false;
const bounds = canvas.getBoundingClientRect();
mouseObj.x = (e.type.includes('mouse')) ? e.pageX - bounds.left - scrollX : e.changedTouches[0].pageX - bounds.left - scrollX;
mouseObj.y = (e.type.includes('mouse')) ? e.pageY - bounds.top - scrollY : e.changedTouches[0].pageY - bounds.top - scrollY;
rectangleMaker.setSecondPoint(mouseObj);
rectangleMaker.saveLocal();
rectangleMaker.drawRectangles();
}
}
}
/**
* #description code to load the canvas and call the Annotate constructor
*/
const canvas = document.querySelector('canvas');
loadImageFromlink(canvas).then(() => {
const annotate = new AnnotateImage(canvas);
});
function loadImageFromlink (canvas) {
const promise = new Promise((resolve, reject)=>{
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = canvas.dataset.src;
img.crossOrigin = 'Anonymous';
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
resolve('loaded');
};
});
return promise;
}
<canvas data-src="https://www.gannett-cdn.com/-mm-/dcd88fe5876fd549823feb35af14c0ae9f0885b6/c=0-25-492-303/local/-/media/Phoenix/None/2014/10/17/635491415661570024-garfi8.PNG?width=492&height=277&fit=crop&format=pjpg&auto=webp"></canvas>

How to make all drawn (rectangle, circles, lines, polygon) draggable? Pure JS

I have a simple paint canvas html5. Its can draw through a selection option some shapes like lines, circles, rectangles, polygon, but now i want to make all draws draggable if possible resizable, without 3 part library only pure JS.
var canvas,
context,
dragStartLocation,
snapshot;
dragdrop = false;
isDrag = false;
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
function getCanvasCoordinates(event) {
var x = event.clientX - canvas.getBoundingClientRect().left;
y = event.clientY - canvas.getBoundingClientRect().top;
return {x: x, y: y};
}
function takeSnapshot() {
snapshot = context.getImageData(0, 0, canvas.width, canvas.height);
}
function restoreSnapshot() {
context.putImageData(snapshot, 0, 0);
}
function drawLine(position) {
context.beginPath();
context.moveTo(dragStartLocation.x, dragStartLocation.y);
context.lineTo(position.x, position.y );
context.stroke();
}
function drawRect(position) {
context.beginPath();
//context.moveTo(dragStartLocation.x, dragStartLocation.y);
context.fillRect(position.x, position.y, dragStartLocation.x - position.x, dragStartLocation.y - position.y);
//context.stroke();
}
function drawCircle(position) {
var radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
context.beginPath();
context.arc(dragStartLocation.x, dragStartLocation.y, radius, 0, 2 * Math.PI, false);
}
/*
function drawPolygon(position, sides, angle) {
var coordinates = [],
radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2)),
index = 0;
for (index = 0; index < sides; index++) {
coordinates.push({x: dragStartLocation.x + radius * Math.cos(angle), y: dragStartLocation.y - radius * Math.sin(angle)});
angle += (2 * Math.PI) / sides;
}
context.beginPath();
context.moveTo(coordinates[0].x, coordinates[0].y);
for (index = 1; index < sides; index++) {
context.lineTo(coordinates[index].x, coordinates[index].y);
}
context.closePath();
}*/
function draw(position) {
var fillBox = document.getElementById("fillBox"),
shape = document.querySelector('#tools option:checked').value,
/*polygonSides = document.getElementById("polygonSides").value,
polygonAngle = document.getElementById("polygonAngle").value,*/
lineCap = document.querySelector('input[type="radio"][name="lineCap"]:checked').value;
/*composition = document.querySelector('input[type="radio"][name="composition"]:checked').value;*/
context.lineCap = lineCap;
/*context.globalCompositeOperation = composition;*/
if (shape === "circle") {
drawCircle(position);
}
if (shape === "line") {
drawLine(position);
}
if (shape === "rect") {
drawRect(position);
}
if (shape === "polygon") {
drawPolygon(position, polygonSides, polygonAngle * (Math.PI / 180));
}
if (shape !== "line") {
if (fillBox.checked) {
context.fill();
} else {
context.stroke();
}
}
}
function dragStart(event) {
dragging = true;
dragStartLocation = getCanvasCoordinates(event);
takeSnapshot();
}
function drag(event) {
var position;
if (dragging === true) {
restoreSnapshot();
position = getCanvasCoordinates(event);
draw(position);
}
}
function dragStop(event) {
dragging = false;
restoreSnapshot();
var position = getCanvasCoordinates(event);
draw(position);
}
function changeLineWidth() {
context.lineWidth = this.value;
event.stopPropagation();
}
function changeFillStyle() {
context.fillStyle = this.value;
event.stopPropagation();
}
function changeStrokeStyle() {
context.strokeStyle = this.value;
event.stopPropagation();
}
function changeBackgroundColor() {
context.save();
context.fillStyle = document.getElementById("backgroundColor").value;
context.fillRect(0, 0, canvas.width, canvas.height);
context.restore();
}
function eraseCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
function init() {
canvas = document.getElementById("canvas");
context = canvas.getContext('2d');
var lineWidth = document.getElementById("lineWidth"),
fillColor = document.getElementById("fillColor"),
strokeColor = document.getElementById("strokeColor"),
//canvasColor = document.getElementById("backgroundColor"),
clearCanvas = document.getElementById("clearCanvas");
//saveCanvas = document.getElementById("saveCanvas");
context.strokeStyle = strokeColor.value;
context.fillStyle = fillColor.value;
context.lineWidth = lineWidth.value;
/*window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('orientationchange', resizeCanvas, false);
resizeCanvas();*/
canvas.addEventListener('mousedown', dragStart, false);
canvas.addEventListener('mousemove', drag, false);
canvas.addEventListener('mouseup', dragStop, false);
lineWidth.addEventListener("input", changeLineWidth, false);
fillColor.addEventListener("input", changeFillStyle, false);
strokeColor.addEventListener("input", changeStrokeStyle, false);
//canvasColor.addEventListener("input", changeBackgroundColor, false);
clearCanvas.addEventListener("click", eraseCanvas, false);
//saveCanvas.addEventListener("click", salvaCanvas, false);
}
window.addEventListener('load', init, false);
It is an essential feature of canvas that it stores only the raster image formed as a result of all the draw operations. This why canvas is fast and memory efficient.
The downside is that you have to erase the affected rectangle on the canvas (or the whole canvas in the worst case) and redraw all the shapes that need to be resized, moved, etc. The canvas does not store them as objects, so your JS code must take care of storing, modifying and re-drawing them on the canvas. The effort is substantial unless you use some third party library.
The alternative is to use SVG-like features of HTML5 instead of canvas: <line>, <path>, <rect>, etc. They are kept as objects by the browser and accessible via DOM. This is a totally different approach, however, and requires a full rewrite of your code.

how to remove a unconnected points when i mouseout from canvas

i need to remove unconnected points when i mouseout from canvas. I just draw lines when mousemove using moveTo and LineTo. when mouseout from canvas have to omit the unconnected points.
Here is code for jquery:
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/jquery.js"></script>
<script type="text/javascript">
$(function() {
var canvas = $('#canvas');
var context = canvas.get(0).getContext("2d");
var clicked = false;
var b=0;
var storedLines = [];
var storedLine = {};
var mouse = {
x: -1,
y: -1
}
var parentOffset = $('#canvas').offset();
canvas.click(function(e) {
if (b==1)
{
$(this).unbind(e);
}
else
{
clicked = true;
mouse.x = e.pageX - parentOffset.left;
mouse.y = e.pageY - parentOffset.top;
context.moveTo(mouse.x, mouse.y);
if (clicked) {
storedLines.push({
startX: storedLine.startX,
startY: storedLine.startY,
endX: mouse.x,
endY: mouse.y
});
}
storedLine.startX = mouse.x;
storedLine.startY = mouse.y;
$(this).mousemove(function(k) {
context.clearRect(0, 0, 960, 500);
context.beginPath();
context.strokeStyle = "blue";
for (var i = 0; i < storedLines.length; i++) {
var v = storedLines[i];
context.moveTo(v.startX, v.startY);
context.lineTo(v.endX, v.endY);
context.stroke();
}
context.moveTo(mouse.x, mouse.y);
context.lineTo(k.pageX - parentOffset.left, k.pageY - parentOffset.top);
context.stroke();
context.closePath();
});
}
});
$('#canvas').mouseout(function(e){
$(this).unbind("mousemove");
b=1;
});
});
HTML code:
<html>
<body>
<canvas id="canvas" width=600 height=600 ></canvas>
</body>
</html>
First thing to do is to clarify the code : Have one part that deals only with the mouse, and another part that deals only with the lines.
This way you will have a much better view on what will happen on each event.
I started a bit to clarify the code, you should even make a class handling lines (which will be very useful if you handle several of them).
jsbin is here : http://jsbin.com/eseTODo/2/edit?js,output
var canvas = $('#canvas');
var context = canvas.get(0).getContext("2d");
// -----------------------------------------
// Mouse
var clicked = false;
var onCanvas = false;
var mouse = {
x: -1,
y: -1
}
var parentOffset = $('#canvas').offset();
canvas.mousedown(function (e) {
clicked = true;
if (!onCanvas) return;
mouse.x = e.pageX - parentOffset.left;
mouse.y = e.pageY - parentOffset.top;
addPoint(mouse.x, mouse.y);
clearScreen();
drawLines();
});
canvas.mouseup(function (e) {
clicked = false;
if (!onCanvas) return;
});
canvas.mousemove(function (e) {
if (!onCanvas) return;
clearScreen();
drawLines();
drawPendingLine(e.pageX - parentOffset.left,
e.pageY - parentOffset.top);
});
canvas.mouseout(function (e) {
onCanvas = false;
clearScreen();
drawLines();
clearLines();
});
canvas.mouseenter(function (e) {
onCanvas = true;
});
// -----------------------------------------
// Lines
var storedLines = [];
var storedLine = {};
var startedALine = false;
function clearLines() {
storedLines.length = 0;
startedALine = false;
}
function addPoint(x, y) {
if (startedALine) {
storedLines.push({
startX: storedLine.startX,
startY: storedLine.startY,
endX: x,
endY: y
});
}
startedALine = true;
storedLine.startX = x;
storedLine.startY = y
}
function drawLines() {
context.strokeStyle = "blue";
if (!startedALine) return;
if (!storedLines.length) return;
for (var i = 0; i < storedLines.length; i++) {
var v = storedLines[i];
context.beginPath();
context.moveTo(v.startX, v.startY);
context.lineTo(v.endX, v.endY);
context.stroke();
context.closePath();
}
context.stroke();
}
function drawPendingLine(lastX, lastY) {
if (!startedALine) return;
context.beginPath();
context.strokeStyle = "green";
context.moveTo(storedLine.startX, storedLine.startY);
context.lineTo(lastX, lastY);
context.stroke();
context.closePath();
}
function clearScreen() {
context.clearRect(0, 0, 600, 600);
}
Can't you set a flag like
var hasLeftCanvas = false;
and set it to true when you leave the canvas?
canvas.onmouseleave = function() {
hasLeftCanvas = true;
}
and then, in your script:
$(this).mousemove(function(k) {
if(!hasLeftCanvas) {
context.clearRect(0, 0, 960, 500);
context.beginPath();
context.strokeStyle = "blue";
for (var i = 0; i < storedLines.length; i++) {
var v = storedLines[i];
context.moveTo(v.startX, v.startY);
context.lineTo(v.endX, v.endY);
context.stroke();
}
context.moveTo(mouse.x, mouse.y);
context.lineTo(k.pageX - parentOffset.left, k.pageY - parentOffset.top);
context.stroke();
context.closePath();
}
});
remember to set it back to false when the cursor re enters the canvas

Categories