Fabricjs - Rendering multiple canvases in one document - javascript

I'm having a problem dynamically creating and setting canvas backgrounds.
I want the user to be able to annotate the images with text and shapes, thats why I am doing stuff this way.
I would like to know why is this code producing such output
The idea
Send a get request to an endpoint which returns json data containing
image urls.
Convert that data into a javascript array.
Dynamically create fabric js canvases based on the length of above
array.
Set the canvas backgrounds to the images using their urls. (i.e each canvas will have a different background taken from the url)
The problem
Only the last canvas has a background image.
code
<!DOCTYPE html>
<html>
<head>
<style>
body {
}
</style>
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous"
></script>
</head>
<body>
<script src="fabric.js"></script>
<!--new start-->
<script>
//procedurally load images from a given source
//endpoint with image links
var end_point = "http://localhost:8080/endpoint.php";
var settings = {
url: "http://localhost:8080/endpoint.php",
method: "GET",
};
//get the images array from end point
$.ajax(settings).done(function (images_json) {
var images = JSON.parse(images_json);
//procedurally create farbric.js canvas for each image element in the html document and set their background as the corresponding image.
//for each item in the images array, create a fabric.js canvas with its background set as the image itself
var canvas_array = [];
for (var i = 0, len = images.length; i < len; i++) {
//console.log(images[i]);
//create canvas and then make it a fabric canvas
document.body.innerHTML += `<canvas
id=${[i]}
width="1000"
height="200"
style="border-style: solid;">
</canvas>`;
//canvases stored in canvas_array
canvas_array[i] = new fabric.Canvas(`${[i]}`);
console.log(canvas_array[i]);
//set canvas background as the image
canvas_array[i].setBackgroundImage(
`${images[i]}`,
canvas_array[i].renderAll.bind(canvas_array[i]),
{
backgroundImageOpacity: 1,
backgroundImageStretch: false,
}
);
}
});
</script>
<!--new end-->
</body>
</html>
result
result
note
The code is generating correct number of canvases. However the background image does not seem to work
Desired result would have 10 canvases with different backgrounds corresponding to the image urls.
I'm new to fabric.js, this may be a dumb mistake.

Using remote source was a strict requirement. I tried several stuff and this finally worked.
code
//c global variable
var n_can_arr = [];
var f_can_arr = [];
var img_arr = [];
//c procedurally load images from a given source
$.ajax({
url: "http://localhost:8080/endpoint.php",
type: "GET",
dataType: "json", //c added data type
success: function (res) {
for (var index = 0; index < res.length; index++) {
//c create canvas
var setHtml = `<canvas
id=${index}
width="1000"
height="800"
style="border-style: solid;">
</canvas>`;
document.body.innerHTML += setHtml;
//c update canvas and image arrays
n_can_arr.push(index);
img_arr.push(res[index]);
}
//c call image set after the loop is over
$(document).trigger("images_set");
},
});
//c on image_set called
$(document).bind("images_set", () => {
//c for each element of normal canvas array, create a fabric js canvas and set its background
for (var i = 0; i < n_can_arr.length; i++) {
create_canvas(i);
}
//c for each element of fabric canvas array, apply the extend canvas function
extend_canvas();
});
function create_canvas(i) {
//c create fabric js canvases with normal canvas id from canvas arrray
var canvas = new fabric.Canvas(`${i}`);
f_can_arr.push(canvas);
//c set canvas background using image array
canvas.setBackgroundImage(img_arr[i], canvas.renderAll.bind(canvas), {
backgroundImageOpacity: 1,
backgroundImageStretch: false,
});
}
function extend_canvas() {
f_can_arr.forEach((canvas) => {
var origX, origY, isDown, mode_rect, mode_uline, mode_free, pointer;
//c setting keypress listener
$(window).on("keypress", (e) => {
console.log(e.key);
//c drawing rectangles
if (e.key == 1) {
var rect;
console.log("Box");
isDown = true;
mode_free = false;
mode_uline = false;
mode_rect = true;
//c canvas event listners
canvas.on("mouse:down", function (o) {
isDown = true;
if (mode_rect) {
pointer = canvas.getPointer(o.e);
origX = pointer.x;
origY = pointer.y;
console.log(origX + "," + origY);
rect = new fabric.Rect({
left: origX,
top: origY,
originX: "left",
originY: "top",
width: pointer.x - origX,
height: pointer.y - origY,
fill: "red",
angle: 0,
fill: "rgba(255,0,0,0.0)",
stroke: "black",
strokeWidth: 1,
});
canvas.add(rect);
}
});
canvas.on("mouse:move", function (o) {
if (mode_rect) {
if (isDown) {
var pointer = canvas.getPointer(o.e);
if (origX > pointer.x) {
rect.set({ left: Math.abs(pointer.x) });
}
if (origY > pointer.y) {
rect.set({ top: Math.abs(pointer.y) });
}
rect.set({ width: Math.abs(origX - pointer.x) });
rect.set({ height: Math.abs(origY - pointer.y) });
canvas.renderAll();
}
}
});
}
//c freehand drawing/Highlighter
if (e.key == 2) {
console.log("freehand");
isDown = true;
mode_free = true;
mode_uline = false;
mode_rect = false;
canvas.isDrawingMode = 1;
canvas.freeDrawingBrush.color = "rgba(255,0,0,0.2)";
canvas.freeDrawingBrush.width = 20;
//c canvas event listners
canvas.on("mouse:down", function (o) {
isDown = true;
if (mode_free) {
canvas.renderAll();
}
});
}
//c line mode
if (e.key == 3) {
var line;
console.log("line");
isDown = true;
mode_free = false;
mode_uline = true;
mode_rect = false;
//c canvas event listners
canvas.on("mouse:down", function (o) {
isDown = true;
var pointer = canvas.getPointer(o.e);
var points = [pointer.x, pointer.y, pointer.x, pointer.y];
if (mode_uline) {
line = new fabric.Line(points, {
strokeWidth: 3,
fill: "red",
stroke: "red",
originX: "center",
originY: "center",
targetFindTolerance: true,
});
canvas.add(line);
}
});
canvas.on("mouse:move", function (o) {
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
if (mode_uline) {
line.set({ x2: pointer.x, y2: pointer.y });
canvas.renderAll();
}
});
}
//c deleting a selected shape
if (e.key == 4) {
var activeObject = canvas.getActiveObject();
if (activeObject) {
canvas.remove(activeObject);
}
}
//c cancling freehand drawing mode
if (e.key == "x") {
console.log("freehand mode cancled");
canvas.isDrawingMode = 0;
}
});
//c removing previous event listeners and resetting some global variables
canvas.on("mouse:up", function (o) {
isDown = false;
mode_free = false;
mode_uline = false;
mode_rect = false;
canvas.off("mouse:down");
canvas.off("mouse:move");
});
});
}
function save_canvas() {}
function load_canvas() {}
solution
I setup a custom trigger event after the ajax call is made. When the trigger event is fired, only then I create fabricjs canvases.
(apparently even with promises, the fabric code was not running in correct order. Which was probably due to bad syntax. Custom triggers solve this issue.)
results
-Each image appear separately in its own fabric.js canvas as background.
-Each canvas is independent.

Have create for you just copy code and then use your own images in local to get actual full result in output div( images will generated) .
let CANVAS_LIST = [];
let imageList = [
'https://homepages.cae.wisc.edu/~ece533/images/airplane.png',
'https://homepages.cae.wisc.edu/~ece533/images/arctichare.png',
'https://homepages.cae.wisc.edu/~ece533/images/baboon.png',
]
imageList.forEach(element => {
let canvasElement = document.createElement("canvas");
canvasElement.width = '300';
canvasElement.height = '300';
$("#canvas_container").append(canvasElement);
let canvas = new fabric.Canvas(canvasElement);
// Adding Example Text here.
canvas.add(new fabric.Text('This text is added', {
fontFamily: 'Delicious_500',
color: 'red',
left: 10,
top: 100
}));
// Setting up Background to dynamic generated Canvas
canvas.setBackgroundImage(
`${element}`,
canvas.renderAll.bind(canvas),
{
backgroundImageOpacity: 1,
backgroundImageStretch: false,
}
);
CANVAS_LIST.push(canvas);
});
setTimeout(() => {
generateOutput();
}, 3000);
function generateOutput() {
$("#output").empty();
CANVAS_LIST.forEach(canvas => {
let image = $("<img>").attr({
width: 200,
height: 200,
src: canvas.toDataURL()
});
image.css({ marginLeft: '20px' });
$("#output").append(image);
})
}
<!DOCTYPE html>
<html>
<head>
<style>
body {}
</style>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.1.0/fabric.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div id="canvas_container">
</div>
<button onclick="generateOutput()"> Show output</button>
<div id="output">
</div>
</div>
</body>
</html>
Thanks

Related

event listener with Pointerup not firing when activated from touchscreen

I'm coding a simple javascript drawing program using an HTML canvas, it will allow you to draw a line, and change the color of said line via a button, and see which color you have selected using a element and some javascript involving innerText, and it works great, when used with a mouse or trackpad, however when it's used on touchscreen it draws a short bit of line then stops. I have tracked the problem to the pointerup event, but I have no idea what exactly is going wrong with it. See for yourself-
<canvas id='dr' width = '640' height = '480' ></canvas>
<div style = 'width: 35%; margin: auto; ' id = 'ClearAndColor'>
<button onclick = 'switchColor()'> Change color</button>
<button onclick = 'clean()' > clear drawing program</button>
<p id = 'color'>black</p>
</div>
<p id = 'p'></p>
<script>
var canvas = document.getElementById('dr');
var ctx = canvas.getContext('2d');
var line = false
function drawType() {
if (line === true) {
line = false;
} else {
line = true;
}
}
var drawing = false;
function Type() {}
//start the drawing if the mouse is down
canvas.addEventListener("pointerdown", () => {
drawing = true;
})
//stop the drawing if the mouse is up
canvas.addEventListener("pointerup", () => {
drawing = false;
});
//add an event listener to the canvas for when the user moves the
//mouse over it and the mouse is down
var colors = ['DarkRed', 'DarkOrange', 'Gold', 'Green',
'MediumBlue', 'Indigo', 'Violet', 'Black']
var displayColors = ['r', 'Red', 'Orange', 'Yellow', 'Green',
'Blue', 'Indigo', 'Violet', 'Black']
var selector = 0
var color = document.getElementById('color')
var selectedColor = colors[selector];
function switchColor() {
if (selector < 8) {
selector = selector + 1;
} else {
selector = 1;
}
color.innerText = displayColors[selector];
selectedColor = colors[selector];
}
canvas.addEventListener('pointermove', (event) => {
// this right here \/ is just for logging purposes.
var p = document.getElementById('p')
p.innerText = drawing;
//if the drawing mode is true (if the mouse button is down)
if (drawing == true) {
//put the rectangle on the canvas at the coordinates of the
//mouse
ctx.fillStyle = colors[selector-1];
ctx.fillRect(event.clientX, event.clientY, 4, 4)
}
});
//onMouseDown onMouseMove onMouseUp onTouchEnd
function clean() {
var canvas = document.getElementById('dr');
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 640, 480);
}
</script>

Fabric.js remnants of old canvas remain

I want to display the content of a canvas as the background of another canvas and draw a bunch of rectangles on there. I need to dynamically change:
the canvas from which to load the background image for my finalcanvas
which rectangles to draw
It's easiest if I start this process from scratch. I have imitated starting from scratch with a simple button. However, after redrawing my canvas, previous information from fabric.js remains present after dragging an item of a canvas a bit. This means that the old canvas was not cleared properly. I tried playing around with .clear() and .depose(), but to no avail.
In case the description is vague, here an image:
And an small reproducible example:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello</title>
</head>
<body>
<canvas id="finalcanvas"></canvas>
<canvas id="backgroundcanvas" width="200" height="100"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.min.js" type="text/javascript"></script>
<script>
function load_plane_onto_active_canvas() {
var c = document.getElementById('backgroundcanvas');
var ctx = c.getContext("2d");
var bg = c.toDataURL("image/png");
var canvas = new fabric.Canvas('finalcanvas', {
width: 333,
height: 333
});
canvas.setBackgroundImage(bg, canvas.renderAll.bind(canvas));
canvas.on("mouse:over", function(e) {
console.log(e.target)
});
// add 100 rectangles
for (i = 0; i < 10; i++) {
for (j = 0; j < 10; j++) {
rect = new fabric.Rect({
width: 10,
height: 10,
left: j * 15,
top: i * 15,
fill: 'green',
})
canvas.add(rect);
}
}
}
window.onload = function() {
// fill the background canvas with red
(function() {
var canvas = document.getElementById("backgroundcanvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0, 0, 150, 75);
}())
load_plane_onto_active_canvas()
}
</script>
<button onclick="load_plane_onto_active_canvas()">click me</button>
</body>
</html>
I hope someone can help me!
Notice that you're creating a new instance of fabric.Canvas in your load_plane_onto_active_canvas() function. So when you're clicking the button, the existing canvases stay intact, and you're actually calling clear() on your freshly created canvas. The DOM becomes messed up at this point, with several nested canvases inside of each other - you can take a peek at the DOM inspector to see that.
What you can do instead is create the canvas instance just once, then work with a reference to it later in your other functions.
const finalCanvas = new fabric.Canvas("finalcanvas", {
width: 333,
height: 333
});
// finalCanvas now exists in the global (window) scope
function load_plane_onto_active_canvas() {
var c = document.getElementById("backgroundcanvas");
var ctx = c.getContext("2d");
var bg = c.toDataURL("image/png");
finalCanvas.clear();
finalCanvas.setBackgroundImage(bg, finalCanvas.renderAll.bind(finalCanvas));
finalCanvas.on("mouse:over", function (e) {
// console.log(e.target);
});
// add 100 rectangles
for (i = 0; i < 10; i++) {
for (j = 0; j < 10; j++) {
rect = new fabric.Rect({
width: 10,
height: 10,
left: j * 15,
top: i * 15,
fill: "green"
});
finalCanvas.add(rect);
}
}
}
window.onload = function () {
// fill the background canvas with red
(function () {
var canvas = document.getElementById("backgroundcanvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0, 0, 150, 75);
})();
load_plane_onto_active_canvas();
};
document.querySelector("#b1").onclick = () => load_plane_onto_active_canvas();
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.js"></script>
<canvas id="finalcanvas"></canvas>
<canvas id="backgroundcanvas" width="200" height="100"></canvas>
<button id="b1">start from scratch</button>

Clip an Image and Show in Another Canvas

I have two canvas on one page. I need to clip an image in first one and transfer it to second one as clipped.
I have a code like below but when i click to send i just see a black clipped image in second canvas. Plus, selectable area is big as before clipping.
public clip() {
var canvas = new fabric.Canvas('cLeft');
let src = this.srcLeft;
loadImage(src, canvas);
// If there is enough width -> clipTo
if (Math.abs(this.firstX - this.lastX) > 20 || Math.abs(this.firstY - this.lastY) > 20) {
canvas.clipTo = (ctx) => {
console.log("-->> clipTo: " + this.firstX + " " + this.firstY);
var shp = new fabric.Rect({
top: this.firstY,
left: this.firstX,
width: Math.abs(this.lastX - this.firstX),
height: Math.abs(this.lastY - this.firstY),
hasControls: false,
selectable: false,
evented: false
});
shp.render(ctx);
};
this.data = canvas.toDataURL();
}
}
public send() {
var canvas = new fabric.Canvas('cRight');
fabric.Image.fromURL(this.data, function (img) {
img.left = 10;
img.top = 10;
canvas.add(img);
canvas.renderAll();
})
}

fabric js Polygon setCoords

I've got a question about fabric.js and the polygon-object.
I have an example of my problem in this fiddle:
Click me
First 4 fabric.Circle subobjects called linePoint are drawn.
The linePoint objects have an extra x(same as left) and y(same as top) coordinate and a reference to which polygon they belong to:
fabric.LinePoint = fabric.util.createClass(fabric.Circle,
{
initialize: function (options) {
options || (options = {});
this.callSuper('initialize', options);
options &&
this.set('type', 'line_point'),
this.set('x', this.left),
this.set('y', this.top),
this.set('polygon', options.polygon)
},
setPointCoordinates: function(new_left, new_top) {
this.set('x', new_left);
this.set('y', new_top);
this.set('left', new_left);
this.set('top', new_top);
}
With the now given x and y coordinates there is a Polygon drawn between the Points.
The problem is now, when you move the Circles, the Polygon is moved correctly, but its border (or I don't know how to exactly call it) will stay the same small rectangle as it was.
I want to update the polygon Coords too, I tried .setCoords(), but nothing happened.
Maybe you can help me. :) Thanks!
In reply also to:
https://groups.google.com/forum/#!topic/fabricjs/XN1u8E0EBiM
This is your modified fiddle:
https://jsfiddle.net/wum5zvwk/2/
fabric.LinePoint = fabric.util.createClass(fabric.Circle,
{
initialize: function (options) {
options || (options = {});
this.callSuper('initialize', options);
this.set('type', 'line_point'),
this.set('x', this.left),
this.set('y', this.top),
this.set('polygon', options.polygon)
},
setPointCoordinates: function(new_left, new_top) {
this.set('x', new_left);
this.set('y', new_top);
this.set('left', new_left);
this.set('top', new_top);
}
});
var canvas = new fabric.Canvas('canvas');
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
document.getElementById("canvas").tabIndex = 1000;
drawPolygonToCanvas();
canvas.on('object:moving', function(event) {
var object = event.target;
switch(object.type) {
case 'line_point':
//move polygon
object.setPointCoordinates(object.left, object.top);
object.polygon.setCoords();
object.polygon.initialize(object.polygon.points);
object.polygon.top += object.polygon.height / 2;
object.polygon.left += object.polygon.width / 2;
canvas.renderAll();
break;
}
});
function drawPolygonToCanvas()
{
//creting end_points and set them
old_position = canvas.getPointer(event.e);
var end_point_1 = createLinePoint(100, 100);
var end_point_2 = createLinePoint(100, 150);
var end_point_3 = createLinePoint(150, 150);
var end_point_4 = createLinePoint(150, 100);
end_points_in_use = [end_point_1, end_point_2, end_point_3, end_point_4];
canvas.add(end_point_1, end_point_2, end_point_3, end_point_4);
drawPoly(end_points_in_use);
canvas.deactivateAll();
canvas.renderAll();
}
function drawPoly(point_array)
{
var poly = new fabric.Polygon(point_array, {
left: (100 + ((150 - 100) /2)),
top: (100 + ((150 - 100) /2)),
fill: 'lightblue',
lockScalingX: true,
lockScalingY: true,
lockMovementX: true,
lockMovementY: true,
perPixelTargetFind: true,
opacity: 0.5,
type: 'polygon'
});
for (var i = 0; i < point_array.length; i++) {
point_array[i].polygon = poly;
}
canvas.add(poly);
poly.sendToBack();
}
function createLinePoint(left, top) {
return new fabric.LinePoint({
left: left,
top: top,
strokeWidth: 2,
radius: 15,
fill: '#fff',
stroke: '#666',
related_poly_point: 0,
lockScalingX: true,
lockScalingY: true
});
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<div id="canvas-wrapper" style="position:relative;width:704px;float:left;">
<canvas id="canvas" width="700" height="600" style="border:1px solid #000000;"></canvas>
</div>
Modifying the polygon points is not enough to have the bounding box adjusted. The easies thing i can think of is to re initialize the polygon with the new points coordinates.

Adding more images to the canvas to drag and drop

I want to be able to spawn a new image to the canvas at the click of a button, rather than having to manually edit the code.
I have the following HTML5/JavaScript code that allows images to be dragged and dropped between multiple canvases and it works perfectly for what I require.
What I am doing:
<canvas style="float: left" height="125" width="400" id="cvs1">[No canvas support]</canvas>
<canvas style="float: left; margin-left: 100px" height="125" width="400" id="cvs2">[No canvas support]</canvas>
<script src="http://www.rgraph.net/libraries/RGraph.common.core.js" ></script>
<script>
window.onload = function ()
{
var canvas1 = document.getElementById("cvs1");
var canvas2 = document.getElementById("cvs2");
var context1 = canvas1.getContext('2d');
var context2 = canvas2.getContext('2d');
var imageXY = {x: 5, y: 5};
/**
* This draws the image to the canvas
*/
function Draw ()
{
//Clear both canvas first
canvas1.width = canvas1.width
canvas2.width = canvas2.width
//Draw a red rectangle around the image
if (state && state.dragging) {
state.canvas.getContext('2d').strokeStyle = 'red';
state.canvas.getContext('2d').strokeRect(imageXY.x - 2.5,
imageXY.y - 2.5,
state.image.width + 5,
state.image.height + 5);
}
// Now draw the image
state.canvas.getContext('2d').drawImage(state.image, imageXY.x, imageXY.y);
}
canvas2.onclick =
canvas1.onclick = function (e)
{
if (state && state.dragging) {
state.dragging = false;
Draw();
return;
}
var mouseXY = RGraph.getMouseXY(e);
state.canvas = e.target;
if ( mouseXY[0] > imageXY.x
&& mouseXY[0] < (imageXY.x + state.image.width)
&& mouseXY[1] > imageXY.y
&& mouseXY[1] < (imageXY.y + state.image.height)) {
state.dragging = true;
state.originalMouseX = mouseXY[0];
state.originalMouseY = mouseXY[1];
state.offsetX = mouseXY[0] - imageXY.x;
state.offsetY = mouseXY[1] - imageXY.y;
}
}
canvas1.onmousemove =
canvas2.onmousemove = function (e)
{
if (state.dragging) {
state.canvas = e.target;
var mouseXY = RGraph.getMouseXY(e);
// Work how far the mouse has moved since the mousedon event was triggered
var diffX = mouseXY[0] - state.originalMouseX;
var diffY = mouseXY[1] - state.originalMouseY;
imageXY.x = state.originalMouseX + diffX - state.offsetX;
imageXY.y = state.originalMouseY + diffY - state.offsetY;
Draw();
e.stopPropagation();
}
}
/**
* Load the image on canvas1 initially and set the state up with some defaults
*/
state = {}
state.dragging = false;
state.canvas = document.getElementById("cvs1");
state.image = new Image();
state.image.src = 'http://www.rgraph.net/images/logo.png';
state.offsetX = 0;
state.offsetY = 0;
state.image.onload = function ()
{
Draw();
}
}
</script>
This can also be seen on this JS Fiddle (Note: you have to click the image before you can drag it)
The problem I am having:
I would like to add more images to the canvases so that any of the images can then be dragged and dropped between however many canvases I choose to create.
I can quite easily add more canvases to the page to drag and drop between, however when it comes to adding/spawning more images to the canvases I cannot get it to work.
The only way I can think of being able to do this is by repeating the Draw() function for every single image that gets added. That would mean if I wanted 30 images to be able to drag and drop between 10 different canvases for example, I would need to repeat the Draw() function 30 times. Surely that cannot be the best way to do this?
Unless I am missing something very obvious I cannot see another way of doing this?
Here’s how to configure your code to create multiple objects and click-drag between canvases.
Demo: http://jsfiddle.net/m1erickson/Bnb6A/
Code an object factory function that creates new draggable objects and put all new objects in an array.
Keep this information about each draggable object:
dragging (true/false) indicating if this object is currently being dragged
image: the image for this object
x,y: the current top,left position of this object
width,height: the size of this object
offsetX,offsetY: indicates where the mouse clicked relative to top,left of this object
draw: (a function) that allows this object to draw itself (surrounded by a red rect if it’s being dragged)
On click:
Get the mouse position
Clear both canvases
Iterate through the object array
If an object is being dragged, unset the dragging flag
If an object is now selected, set the dragging flag and set the offsetX,offsetY for the starting mouse position relative to this object.
Redraw all objects
On mousemove:
Get the mouse position
Clear both canvases
Iterate through the object array
If an object is being dragged, move it to the mouse position (setting x,y adjusted for the offset)
Redraw all objects
Code:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:20px; }
canvas{ border: 1px solid #808080; }
</style>
<script>
$(function(){
var canvas1 = document.getElementById("cvs1");
var canvas2 = document.getElementById("cvs2");
var contexts=[];
contexts.push(canvas1.getContext('2d'));
contexts.push(canvas2.getContext('2d'));
function clearAll(){
//Clear both canvas first
canvas1.width = canvas1.width
canvas2.width = canvas2.width
}
canvas1.onclick=function(e){ handleClick(e,1); };
canvas2.onclick=function(e){ handleClick(e,2); };
//
function handleClick(e,contextIndex){
e.stopPropagation();
var mouseX=parseInt(e.clientX-e.target.offsetLeft);
var mouseY=parseInt(e.clientY-e.target.offsetTop);
clearAll();
for(var i=0;i<states.length;i++){
var state=states[i];
if(state.dragging){
state.dragging=false;
state.draw();
continue;
}
if ( state.contextIndex==contextIndex
&& mouseX>state.x && mouseX<state.x+state.width
&& mouseY>state.y && mouseY<state.y+state.height)
{
state.dragging=true;
state.offsetX=mouseX-state.x;
state.offsetY=mouseY-state.y;
state.contextIndex=contextIndex;
}
state.draw();
}
}
canvas1.onmousemove = function(e){ handleMousemove(e,1); }
canvas2.onmousemove = function(e){ handleMousemove(e,2); }
//
function handleMousemove(e,contextIndex){
e.stopPropagation();
var mouseX=parseInt(e.clientX-e.target.offsetLeft);
var mouseY=parseInt(e.clientY-e.target.offsetTop);
clearAll();
for(var i=0;i<states.length;i++){
var state=states[i];
if (state.dragging) {
state.x = mouseX-state.offsetX;
state.y = mouseY-state.offsetY;
state.contextIndex=contextIndex;
}
state.draw();
}
}
var states=[];
var img=new Image();
img.onload=function(){
states.push(addState(0,0,img));
}
img.src="http://www.rgraph.net/images/logo.png";
function addState(x,y,image){
state = {}
state.dragging=false;
state.contextIndex=1;
state.image=image;
state.x=x;
state.y=y;
state.width=image.width;
state.height=image.height;
state.offsetX=0;
state.offsetY=0;
state.draw=function(){
var context=contexts[this.contextIndex-1];
if (this.dragging) {
context.strokeStyle = 'red';
context.strokeRect(this.x,this.y,this.width+5,this.height+5)
}
context.drawImage(this.image,this.x,this.y);
}
state.draw();
return(state);
}
$("#more").click(function(){
states.push(addState(states.length*100,0,img));
});
}); // end $(function(){});
</script>
</head>
<body>
<button id="more">Add Image</button><br>
<canvas height="125" width="300" id="cvs1">[No canvas support]</canvas><br>
<canvas height="125" width="300" id="cvs2">[No canvas support]</canvas>
</body>
</html>

Categories