After ImportJson Path is not editable in paper.js - javascript

Here is the below code for export and import of paper js layouts.
$('#btnSave').click(function () {
var json = paper.project.activeLayer.exportJSON();
localStorage.setItem('oncjson', json);
alert('Data has been save successfully;');
});
$('#btnGet').click(function () {
var json = localStorage.getItem('oncjson');
paper.project.activeLayer.importJSON(json);
paper.project.view.update();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-full.js"></script>
It is loading data as per expectations but once the import has done, I am not able to alter or drag segments from the path. it is like hardcode design rendered on the canvas.
Any help highly appreciated.

From what you are describing, I think the problem is that you are expecting that event handlers will be imported along with the serialized data.
Unfortunately, that is not the case because custom callbacks are hardly serializable (see this issue for more details).
Here is an example simulating export/import in 2 separated canvas.
You can see that, after importing, we need to attach event handlers manually.
// create one scope per canvas
var scope1 = new paper.PaperScope();
scope1.setup(document.getElementById('canvas1'));
var scope2 = new paper.PaperScope();
scope2.setup(document.getElementById('canvas2'));
// draw a circle in canvas 1
new paper.Path.Circle({
center: scope1.view.center,
radius: 50,
fillColor: 'orange',
parent: scope1.project.activeLayer,
// name it so we can easily find it in hierarchy
name: 'circle'
});
// draw instructions
new paper.PointText({
content: 'Drag the circle around',
justification: 'center',
point: scope1.view.center.subtract(0, 80),
parent: scope1.project.activeLayer
});
// bind custom drag handler to circle (using its name to find it)
scope1.project.activeLayer.children['circle'].onMouseDrag = customDragHandler;
// export canvas 1 content
var json = scope1.project.exportJSON();
// import it into canvas 2
scope2.project.importJSON(json);
// we need to manually attach event handler for canvas 2 as it wasn't serialized
scope2.project.activeLayer.children['circle'].onMouseDrag = customDragHandler;
// this position the item under the mouse pointer when it is dragged
function customDragHandler(event) {
this.position = event.point;
}
html,
body {
margin: 0;
overflow: hidden;
height: 100%;
}
main {
display: flex;
height: 100vh;
}
canvas {
flex: 1;
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-core.min.js"></script>
<main>
<canvas id="canvas1" resize></canvas>
<canvas id="canvas2" resize></canvas>
</main>

Related

Draw a textbox on canvas that contains an image and some drawing over it using fabric js

I am trying to draw a textbox on canvas that contains an image and some drawing over it. now I am trying to draw a textbox over it.
When I am trying to initialize canvas object using fabricJS it's making canvas empty first. like..
var canvas = new fabric.Canvas('mycanvas')
after this code, it makes canvas empty. but if I initialize this code in the very beginning just after canvas initialized and then I try to add a textbox to that canvas object like...
var text = new fabric.Textbox('A Computer Science Portal',
{
width: 450,
height: 10,
top: 100,
left: 100,
});
this.canvas.add(text);
Then the textbox is not visible. I think it went in the background of the image.
I have already written lots of code for drawing over the canvas not want to draw textbox on the canvas after with all existing drawing and image that is developed in javascript only.
On Fabric you have bringToFront use that to keep the text on top.
Here is the official documentation:
http://fabricjs.com/docs/fabric.Canvas.html#bringToFront
Sample code:
var image;
var canvas = new fabric.Canvas('canvas');
var url = "http://swagger-net-test.azurewebsites.net/api/SvgImage?"
var text = new fabric.Textbox('TEST', { width: 90, height: 10, top: 30, left: 50 });
this.canvas.add(text);
function loadSVG(url) {
fabric.loadSVGFromURL(url, function(objects, options) {
if (image) canvas.remove(image);
image = fabric.util.groupSVGElements(objects, options);
image.name = 'sticker';
canvas.add(image);
image.scaleToHeight(100);
image.center();
image.setCoords();
canvas.renderAll();
canvas.bringToFront(text)
});
}
loadSVG(url + "color=red")
setTimeout(function() {
loadSVG(url + "color=blue")
}, 3000);
<canvas id="canvas" width="200" height="200"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.4/fabric.min.js"></script>

Adding canvas inside another canvas: obj.setCoords is not a function( fabric js)

Started using fabric.js and trying to add a canvas inside another canvas, so that the top canvas stays constant and I'll add objects to inner canvas.
Here is the snippet of adding a canvas to another canvas.
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
canvas.add(innerCanvas);
and my html looks like this
<canvas id="artcanvas" width="500" height="500"></canvas>
<canvas id="innerCanvas" width="200" height="200" ></canvas>
Once adding these successfully, what I am going to do is , add the coordinates to the inner canvas, so that it looks like one on another to the end user.
However, ran into the below error for the tried code
Uncaught TypeError: obj.setCoords is not a function
at klass._onObjectAdded (fabric.js:6894)
at klass.add (fabric.js:231)
at main.js:60
at fabric.js:19435
at HTMLImageElement.fabric.util.loadImage.img.onload (fabric.js:754)
_onObjectAdded # fabric.js:6894
add # fabric.js:231
(anonymous) # main.js:60
(anonymous) # fabric.js:19435
fabric.util.loadImage.img.onload # fabric.js:754
Looking at the error message, just went to the line of error and here is what I found in chrome console
Can someone point the mistake in my codes ?
After going through no.of discussions and internet solutions, for time being I am using Fabric Rectangle as a clipper and setting it's boundaries so user can be able to drop/play with in that particular clipper.
Dotted red(image below) is my clipper and now I can bound the dropping and below is the code to add an image with a clipper.
function addImageToCanvas(imgSrc) {
fabric.Object.prototype.transparentCorners = false;
fabric.Image.fromURL(imgSrc, function(myImg) {
var img1 = myImg.set({
left: 20,
top: 20,
width: 460,
height: 460
});
img1.selectable = false;
canvas.add(img1);
var clipRectangle = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 150,
top: 150,
width: 200,
height: 200,
fill: 'transparent',
/* use transparent for no fill */
strokeDashArray: [10, 10],
stroke: 'red',
selectable: false
});
clipRectangle.set({
clipFor: 'layer'
});
canvas.add(clipRectangle);
});
}
Now while appending any image/layer to the canvas, I bind that image/layer/text to the clipper I created.
function addLayerToCanvas(laImg) {
var height = $(laImg).height();
var width = $(laImg).width();
var clickedImage = new Image();
clickedImage.onload = function(img) {
var pug = new fabric.Image(clickedImage, {
width: width,
height: height,
left: 150,
top: 150,
clipName: 'layer',
clipTo: function(ctx) {
return _.bind(clipByName, pug)(ctx)
}
});
canvas.add(pug);
};
clickedImage.src = $(laImg).attr("src");
}
And the looks like, after restriction of bounds,
Here is the fiddle I have created with some static image url.
https://jsfiddle.net/sureshatta/yxuoav39/
So I am staying with this solution for now and I really feel like this is hacky and dirty. Looking for some other clean solutions.
As far as I know you can't add a canvas to another canvas - you're getting that error as it tries to call setCoords() on the object you've added, but in this case it's another canvas and fabric.Canvas doesn't contain that method (see docs). I think a better approach would be to have two canvases and position them relatively using CSS - see this simple fiddle
HTML
<div class="parent">
<div class="artcanvas">
<canvas id="artcanvas" width="500" height="500"></canvas>
</div>
<div class="innerCanvas">
<canvas id="innerCanvas" width="200" height="200" ></canvas>
</div>
</div>
CSS
.parent {
position: relative;
background: black;
}
.artcanvas {
position: absolute;
left: 0;
top: 0;
}
.innerCanvas {
position: absolute;
left: 150px;
top: 150px;
}
JS
$(document).ready(function() {
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
var rect = new fabric.Rect({
fill: 'grey',
width: 500,
height: 500
});
canvas.add(rect);
var rect2 = new fabric.Rect({
fill: 'green',
width: 200,
height: 200
});
innerCanvas.add(rect2);
})
To handle the object serialization, you can do something like this:
var innerObjs = innerCanvas.toObject();
console.dir(innerObjs);
var outerObjs = canvas.toObject();
innerObjs.objects.forEach(function (obj) {
obj.left += leftOffset; // offset of inner canvas
obj.top += topOffset;
outerObjs.objects.push(obj);
});
var json = JSON.stringify(outerObjs);
This will then give you the JSON for all objects on both canvases
I have no understanding why you want to do this thing, but to put a canvas inside another canvas, you have one simple way:
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
imageContainer = new fabric.Image(innerCanvas.lowerCanvasEl);
canvas.add(imageContainer);
Then depending what you want to do, you may need additional tweaks, but this should work out of the box.
Don't create a canvas
Most objects in fabric (from my limited experience) are at some point converted to a canvas. Creating an additional fabric canvas to manage a group of objects is kind of pointless as you are just adding overhead and mimicking fabrics built in groups object.
Fabric objects are basically DOM canvases wrappers.
The following example shows how fabric uses a canvas to store the content of a group. The demo creates a group and adds it to the fabric canvas, then gets the groups canvas and adds it to the DOM. Clicking on the group's canvas will add a circle. Note how it grows to accommodate the new circles.
const radius = 50;
const fill = "#0F0";
var pos = 60;
const canvas = new fabric.Canvas('fCanvas');
// create a fabric group and add two circles
const group = new fabric.Group([
new fabric.Circle({radius, top : 5, fill, left : 20 }),
new fabric.Circle({radius, top : 5, fill, left : 120 })
], { left: 0, top: 0 });
// add group to the fabric canvas;
canvas.add(group);
// get the groups canvas and add it to the DOM
document.body.appendChild(group._cacheContext.canvas);
// add event listener to add circles to the group
group._cacheContext.canvas.addEventListener("click",(e)=>{
group.addWithUpdate(
new fabric.Circle({radius, top : pos, fill : "blue", left : 60 })
);
canvas.renderAll();
pos += 60;
});
canvas {
border : 2px solid black;
}
div {
margin : 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js"></script>
<div>Fabric's canvas "canvas = new fabric.Canvas('fCanvas');"</div>
<canvas id="fCanvas" width="256" height="140"></canvas>
<div>Fabric group canvas below. Click on it to add a circle.</div>
Use a group rather than a new instance of a fabric canvas.
As you can see a canvas is generated for you. Adding another fabric canvas (Note that a fabric canvas is not the same as a DOM canvas) will only add more work for fabric to do, which already has a lot of work to do.
You are best of to use a group and have that hold the content of the other fabric object you wish to shadow. That would also contain its content in a group.
Just an image
And just a side not, a DOM canvas is an image and can be used by fabric just as any other image. It is sometimes better to do the rendering directly to the canvas rather than via fabric so you can avoid rendering overheads that fabric needs to operate.
To add a DOM canvas to fabric just add it as an image. The border and text are not fabric object, and apart from the code to render them take up no memory, and no additional CPU overhead that would be incurred if you used a fabric canvas and objects.
const canvas = new fabric.Canvas('fCanvas');
// create a standard DOM canvas
const myImage = document.createElement("canvas");
// size it add borders and text
myImage.width = myImage.height = 256;
const ctx = myImage.getContext("2d");
ctx.fillRect(0,0,256,256);
ctx.fillStyle = "white";
ctx.fillRect(4,4,248,248);
ctx.fillStyle = "black";
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("The DOM canvas!",128,128);
// use the canvas to create a fabric image and add it to fabrics canvas.
canvas.add( new fabric.Image(myImage, {
left: (400 - 256) / 2,
top: (400 - 256) / 2,
}));
canvas {
border : 2px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js"></script>
<canvas id="fCanvas" width="400" height="400"></canvas>
innerCanvas.setCoords(); ìs a function, but you need it only after you set the coordinates. Or more precise, set these four elements:
innerCanvas.scaleX = 1;
innerCanvas.scaleY = 1;
innerCanvas.left = 150;
innerCanvas.top = 150;
innerCanvas.setCoords();
canvas.renderAll();

Keep background image when using Eraser

I need to build a drawing board with erase function using PaperJS. The drawing board need to have a background image and people can draw on the canvas or erase existing path.
//erase function
function onMouseDown(event) {
path = new paper.Path();
path.strokeColor = 'red';
path.strokeWidth = 10;
path.strokeCap = 'round';
path.blendMode = 'destination-out';
}
function onMouseDrag(event) {
path.add(event.point);
}
//setup background image
var bgimage = "./cat.jpg";
var raster = new paper.Raster({
source: bgimage,
position: paper.view.center
});
The problem of using this method is that the background image will also be erased.
Erase Result: http://i.stack.imgur.com/0D4TU.png
JSFiddle: https://jsfiddle.net/9kaffsg4/2/
Is there any way to erase the existing path, without erase the background image?
If your background image does not change: you can put it in an <img> bellow your <canvas>?
If you want to be able to edit your image: you can redraw the image, then your drawing (treated separately), every frame.
I have solved the problem by using two canvas (one is for basic drawing function, the other is the background canvas). And then overlap the two canvas.
I expanded on your initial code example so that I could demonstrate using layers as a solution. Basically, three layers are created. They are created in the order you see them in (1:background, 2:foreground, 3:clearBtn). The layers created earlier are visualy "underneath" the layers created later.
In this way, you can keep your static raster graphic on the bottom-most layer (background), create your "eraser" paths on the middle layer (foreground), and you can remove/clear these "eraser" paths by clicking on the "clear" button. The clear button is on the top-most layer so that it doesn't become obscured by the "eraser paths". I just added the clear button to emphasize the added organizational control that layers provide.
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.9.12/paper.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<style type="text/css">
canvas {
border: 1px solid #000;
}
</style>
<script type="text/paperscript" canvas="myCanvas">
var layers = {
"background": new Layer(),
"foreground": new Layer(),
"clearBtn": new Layer()
};
var paths = [];
//erase function
function onMouseDown(event) {
layers.foreground.activate();
path = new paper.Path();
path.strokeColor = 'red';
path.strokeWidth = 10;
path.strokeCap = 'round';
path.blendMode = 'destination-out';
paths.push(path);
}
function onMouseDrag(event) {
layers.foreground.activate();
path.add(event.point);
}
//setup background image
layers.background.activate();
var bgimage = "http://placehold.it/300x150";
var raster = new paper.Raster({
source: bgimage,
position: paper.view.center
});
// Build a "clear button"
// Clicking this will remove all children
// from the foreground layer
layers.clearBtn.activate();
var clearBtn = new Path.Rectangle({
point: new Point(250, 2),
size: new Size(50, 20),
style: {
fillColor: 'white',
strokeColor: 'black'
}
});
var txt = new PointText({
point: clearBtn.position + new Point(0,4),
content: 'Clear',
justification: 'center'
});
var clearBtnGrp = new Group({
children: [clearBtn, txt]
});
clearBtnGrp.on('mousedown', function () {
for ( var i = 0; i < paths.length; i++ ) {
paths[i].remove();
}
});
</script>
</head>
<body>
<canvas id="myCanvas"></canvas>
</body>
</html>
Edit I updated this to cache paths in an array because the previous example did not always clear all layer children.

Get screen coordinates of an Kinetic image in Javascript?

I'm writing an app and using Kinetic JS library to load image from client's computer. The image is contained in a photo object and is initialised as below
var photoObj = new Image();
photoObj.onload = function() {
var photoImage = new Kinetic.Image({
x: 0,
y: 0,
image: photoObj,
width: photoObj.width,
height: photoObj.height,
name: "photo",
id: "photo"
});
photoGroup.removeChildren();
photoGroup.add(photoImage);
The object is included in a Kinetic Group object, named photoGroup, which is created earlier.
photoGroup = new Kinetic.Group({
x : 0,
y : 0,
draggable : true,
id : "photoGroup"
});
photoLayer = new Kinetic.Layer({
drawBorder: true
});
photoLayer.add(photoGroup);
stage = new Kinetic.Stage({
container : "kinetic-kard-preview",
width : 320,
height : 480
});
stage.add(photoLayer);
stage.draw();
The group object is included in a Kinetic Layer object, named photoLayer, which is included in a Kinetic Stage object, named stage.
Anyway, my question is, now I would like to get the screen coordinates of the image. I successfully got the coordinates of the stage by
var containerOffset=$("#kinetic-kard-preview").offset();
var offsetX=containerOffset.left;
var offsetY=containerOffset.top;
console.log(offsetX);//455.859375
console.log(offsetY);//218
but I can't seem to do the same thing for the image. When I viewed source code of the web page, I've noticed that the generated html code of the canvas that contains the image is something like this.
<canvas width="320" height="480" style="padding: 0px; margin: 0px; border: 0px; background-color: transparent; width: 320px; height: 480px; position: absolute; background-position: initial initial; background-repeat: initial initial;"></canvas>
How can I get the screen coordinates of the image after it's loaded from client's pc to the canvas? Please help!!! Thank you.
thanks for your answer but it can't seem to get what I need. I just added the link of the screenshot here:
As you can see in the picture, the coordinates of the image are not the same as the coordinates of the stage that contains it. I wrote a mousemove event and calculated
- the distance between the x screen coordinate of the stage (ie. offsetX) and x screen coordinate of the image.
- the distance between the y screen coordinate of the stage (ie. offsetY) and y screen coordinate of the image.
They all have the same number....24 (as shown on the screen). By using the mousemove event, I manually got the values of offsetX, offsetY and the x and y screen coordinates of the image are 455, 218, 479 and 242.
However, my problem is how to get those numbers automatically, especially the screen coordinates of the image, after the image is loaded from my pc. I printed the values of gPos to console.log and got (0,0) values. So when I added offsetX and offsetY to these values, they are the same (455,218), not (479,242). Please let me know what should I do now? Thanks again for your help.
Your Kinetic.Image is at position [0,0] in a draggable Kinetic.Group.
So you can get your image position by adding:
The #kinetic-kard-preview offsets
Plus the current group position: group.position()
[ Added code for KineticJS version 4.5.1 ]
Here is example code for v4.5.1 and a Demo: http://jsfiddle.net/m1erickson/NXH9X/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.5.1.min.js"></script>
<style>
body{padding:20px; background:ivory;}
#kinetic-kard-preview{
margin-top: 10px;
width:350px;
height:350px;
border:1px solid gray;
}
#target{position:absolute;top:200px;left:150px;opacity:0.50;}
</style>
<script>
$(function(){
//
$pos=$("#results");
//
var containerOffset=$("#kinetic-kard-preview").offset();
var offsetX=containerOffset.left;
var offsetY=containerOffset.top;
//
var stage = new Kinetic.Stage({
container: 'kinetic-kard-preview',
width: 350,
height: 350
});
//
photoLayer = new Kinetic.Layer({
drawBorder: true
});
stage.add(photoLayer);
//
photoGroup = new Kinetic.Group({
x : 0,
y : 0,
draggable : true,
id : "photoGroup"
});
photoGroup.on("dragmove",function(){
var gPos=photoGroup.getPosition();
var x=parseInt(offsetX+gPos.x);
var y=parseInt(offsetY+gPos.y);
$pos.text("photoImage: screenX="+x+", screenY="+y);
});
photoLayer.add(photoGroup);
//
var photoObj = new Image();
photoObj.onload = function() {
var photoImage = new Kinetic.Image({
x: 0,
y: 0,
image: photoObj,
width: photoObj.width,
height: photoObj.height,
name: "photo",
id: "photo"
});
photoGroup.add(photoImage);
photoLayer.draw();
}
photoObj.src="https://dl.dropboxusercontent.com/u/139992952/multple/norwayFlag.jpg";
}); // end $(function(){});
</script>
</head>
<body>
<h4>Semi-transparent flag is at screen position 150,200</h4>
<p id=results>Drag the opaque flag and see screen position</p>
<img id=target src='https://dl.dropboxusercontent.com/u/139992952/multple/norwayFlag.jpg'>
<div id="kinetic-kard-preview"></div>
</body>
</html>

Diagram creater using KineticJS

I'm working on a simple diagram editor using KineticJS. I would like to use two separate canvases for the palette area (that contains a number of Kinetic.Groups that represent the different nodes of the network I might create), and the diagramming area where I can add nodes from the palette via drag and drop, and then add connections between the various nodes as specific anchor points. I'm having trouble figuring out the drag and drop process from the palette canvas of (stationary) Kinetic.Groups over to the other canvas containing diagramming area. I'm guessing that I need to fire off of the dragstart event for the palette objects (although I don't want these themselves to be draggable), and then do something like create an opague copy of the palette object that can be dragged around, and finally dropped into the diagramming area (with full opacity).
Can groups be dragged outside of a the staging canvases boundaries? Maybe I need to generate an image when I start to drag from the palette, drag that image over, and then create another group when dropping into the diagramming area.
Does any one know of any examples that might point me in the right direction, or who can offer some insight (even code) into the required process. I've searched the KineticJS examples but can't quite find enough to get me going.
Here’s one way to drag nodes from a source palette into a destination group:
A Fiddle: http://jsfiddle.net/m1erickson/xtVyL/
Network nodes are represented by small icons (which are really small kinetic image objects).
The user can drag any icon from the source palette to any destination group.
The groups are just defined areas on the canvas, but could be Kinetc.Groups for more flexibility.
During the dragend event, a new duplicate copy of the dragged icon is created in the destination group.
After the dragend event is complete, the original palette icon is automatically moved from
The newly created duplicate icon can be dragged around the destination group (but not outside that group).
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/xtVyL/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.5.5.min.js"></script>
<style>
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:350px;
height:350px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 350
});
var layer = new Kinetic.Layer();
stage.add(layer);
// image loader
var imageURLs=[];
var imagesOK=0;
var imgs=[];
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempPC.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempServer.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempRouter.png");
loadAllImages();
function loadAllImages(callback){
for (var i = 0; i < imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK==imageURLs.length ) {
start();
}
};
img.src = imageURLs[i];
}
}
// top icon positions
var nextIconX=20;
var nextIconY=20;
// define groups
var groups=[];
groups.push({x:0,y:100,w:175,h:250,fill:"skyblue"});
groups.push({x:175,y:100,w:175,h:250,fill:"cornsilk"});
// add boundary info to each group
// draw colored rect to show group area
for(var i=0;i<groups.length;i++){
var g=groups[i];
g.left=g.x;
g.right=g.x+g.w;
g.top=g.y;
g.bottom=g.y+g.h;
var rect=new Kinetic.Rect({
x:g.x,
y:g.y,
width:g.w,
height:g.h,
fill:g.fill,
stroke:"gray"
});
layer.add(rect);
}
// hittest for each group
function groupHit(x,y){
for(var i=0;i<groups.length;i++){
var g=groups[i];
if(x>g.left && x<g.right && y>g.top && y<g.bottom){return(i);}
}
return(-1);
}
function start(){
makePaletteIcon(imgs[0]);
makePaletteIcon(imgs[1]);
makePaletteIcon(imgs[2]);
layer.draw();
}
function makePaletteIcon(img){
// make an icon that stays in the pallette tray
var fixedIcon=newImage(nextIconX,nextIconY,img,false);
layer.add(fixedIcon);
// make an icon that is dragged from the tray to a group
var dragIcon=makeDraggableIcon(nextIconX,nextIconY,img);
layer.add(dragIcon);
// calc the next icon position
nextIconX+=(img.width+20);
}
function makeDraggableIcon(x,y,img){
var i=newImage(x,y,img,true);
//
i.trayX=x;
i.trayY=y;
//
i.setOpacity(0.50);
i.on("dragend",function(){
var x=this.getX();
var y=this.getY();
// if this pallette icon was not dropped in a group
// put the icon back in the tray and return
var hit=groupHit(x,y);
if(hit==-1){
this.setPosition(this.trayX,this.trayY);
return;
}
// add a copy of this icon to the drop group
var component=newImage(x,y,this.getImage(),true);
// set drag limits
var group=groups[hit];
component.maxDragLeft=group.left;
component.maxDragRight=group.right;
component.maxDragTop=group.top;
component.maxDragBottom=group.bottom;
// limit component dragging to inside the assigned group
component.setDragBoundFunc(function(pos) {
var xx=pos.x;
var yy=pos.y;
var w=this.getWidth();
var h=this.getHeight();
if(pos.x<this.maxDragLeft){xx=this.maxDragLeft;}
if(pos.x+w>this.maxDragRight){xx=this.maxDragRight-w;}
if(pos.y<this.maxDragTop){yy=this.maxDragTop;}
if(pos.y+h>this.maxDragBottom){yy=this.maxDragBottom-h;}
return{ x:xx, y:yy };
});
layer.add(component);
// move the dragIcon back into the pallette tray
this.setPosition(this.trayX,this.trayY);
layer.draw();
});
return(i);
}
// make a new Kinetic.Image
function newImage(x,y,img,isDraggable){
var i=new Kinetic.Image({
image:img,
x: x,
y: y,
width: img.width,
height: img.height,
draggable:isDraggable
});
return(i);
}
}); // end $(function(){});
</script>
</head>
<body>
<p>Drag any icon from top into blue or yellow group</p>
<div id="container"></div>
</body>
</html>

Categories