KineticJS draggable image stuck after drag - javascript

I am using KineticJS and to this point it has been a great experience. One question I have though is about dragging. I have the image in this example set to 'draggable: true', but after I drag it the first time, it cannot be moved again.
var stage = new Kinetic.Stage({
container: 'container',
width: 2754,
height: 1836
});
var layer = new Kinetic.Layer();
var scale = '0.16339869281045751633986928104575';
var imageObj = new Image();
imageObj.onload = function () {
var img = new Kinetic.Image({
x: 0,
y: 0,
height:612,
width:612,
image: imageObj,
draggable: true,
dragBoundFunc: function (pos) {
console.log(img.getAttrs());
return {
x: Math.floor((pos.x/scale)/306)*306,
y: Math.floor((pos.y/scale)/306)*306
};
}
});
// add the shape to the layer
layer.add(img);
// add the layer to the stage
stage.add(layer);
};
imageObj.src = 'http://www.html5canvastutorials.com/demos/assets/yoda.jpg';
http://jsfiddle.net/7UEC6/
Any help is appreciated. For extra credit you can suggest ways to make the dragging smoother in general.
Thanks!
edit: I have noticed that after the image is dragged the initial time, the dragBoundFunc is no longer called when the user tries to drag the image
it also works if i drag the image back to 0,0 but no other time

This is not possible with current KineticJS.
Let me try to explain why.
When you use transform:scale, which is CSS3 element(not standard yet), the visual position of actual elements gets also scaled. However the relative mouse position of MouseEvent only passes visual position(not scaled position).
The mouse click event for dragging, which KineticJS uses, only reads MouseEvent position, which is visual one(not scaled one), and it does not consider transformation of canvas element.
When you set your image x/y to following, you cannot even do the first drag
x: 612,
y: 612,
However when you set it as 0 and 0, it respond to it because the scaled and unscaled position is the same.
For the above example x/y 612 and 612, you cannot click to drag,
because KineticJS understand it as you are not clicking 612 and 612,
but it think it as 100 and 100
The reason is simple as I explained.
MouseEvent does not pass scaled X/Y, but visual position of X/Y.
Who knows when CSS3 become standard, it might be well supported :)
Meanwhile, I would not recommend to use transformation with canvas. Unless you build your own framework, it is pretty hard to make all things correctly with canvas transformed.

Related

CreateJS/EaselJS Collision trigger from PNG image

I am a complete novice in CreateJS and I am using it to try and make a simple canvas game.
The user clicks a point on the screen and the sprite moves and stops. I have different display containers in this order (Stage children: path mesh, background, sprite container). Ideally the user clicks anywhere on the canvas and the sprite will move as far as possible until it reaches the edge of the path mesh png.
I just can't figure out how to achieve this. It would be great if someone could point me in the right direction to approach this problem. Many thanks.
Path Mesh png
Current snippet of code used.
client.walkmesh.addEventListener("click", function(evt) {
let pc = player.container
let timeline = new createjs.Timeline();
timeline.addTween(
// Bounce
createjs.Tween.get(playerChild, {loop:true})
.wait(1).to({
y:0,
}).wait(1).to({
y: -4,
}).wait(1).to({
y: -6,
}).wait(1).to({
y: -4,
}).wait(1).to({
y: 0,
}),
// Move
createjs.Tween.get(pc).to({x:evt.localX, y:evt.localY},
calculateSpeed(evt.localX, evt.localY, pc.x, pc.y),
createjs.Ease.linear)
);
});
If you are new at this, as you say, you could get the help of a framework. For instance, ZIM at https://zimjs.com has a MotionController() where you pass in the object you want to move, and the type of motion - for instance moving to a press, or following the mouse, or a key or gamepad. Then you can add a boundary as well which would make what you are trying to do quite simple.
var sprite = new Sprite().center();
var boundary = new Boundary(mesh.x, mesh.y, mesh.width-sprite.width, mesh.height-sprite.height);
new MotionController({target:sprite, boundary:boundary});
Otherwise, you can code it manually in a Ticker function. Move the x and y towards the mouse location. If the x and y is past the side of the bounds then set it to the bound. Basically, that is what the MotionController class is doing for you. Don't do it with Tween. And because you are not using a tween, you might then want to add damping... again, the MotionController does that for you. Otherwise, ZIM also has a Damp class to do the damping calculation for you.
If you need help - you are welcome to join us at https://zimjs.com/slack - cheers.

Pan Zoom behaviour using EaselJS

I’m having some trouble incorporating pan/zoom behaviour with the ability to also drag-move some shapes around on the canvas, using EaselJS.
I want to be able to move the shape ONLY if I mousedown on it, but if I mousedown on the stage (i.e. not on a shape), then I want to be able to pan the stage.
This behaviour needs to be consistent regardless of the ‘zoom’ level (which is altered by the mousewheel).
I have read this: How to stop the event bubble in easljs? Which suggests that the stage mousedown events will fire regardless of whether I have clicked on a shape or empty space, so it would be better to create a ‘background’ shape to capture my mousedown events that are not on a ‘proper’ shape.
This fiddle is how I have set it up: https://jsfiddle.net/hmcleay/mzheuLbg/
var stage = new createjs.Stage("myCanvas");
console.log('stage.scaleX: ', stage.scaleX);
console.log('stage.scaleY: ', stage.scaleY);
function addCircle(r,x,y){
var g=new createjs.Graphics().beginFill("#ff0000").drawCircle(0,0,r);
var s=new createjs.Shape(g)
s.x=x;
s.y=y;
s.on('pressmove', function(ev) {
var localpos = stage.globalToLocal(ev.stageX, ev.stageY)
s.x = localpos.x;
s.y = localpos.y;
stage.update();
});
stage.addChild(s);
stage.update();
}
// create a rectangle 'background' Shape object to cover the stage (to allow for capturing mouse drags on anything except other shapes).
bg = new createjs.Shape();
bg.graphics.beginFill("LightGray").drawRect(10, 10, stage.canvas.width - 20, stage.canvas.height - 20); //deliberately smaller for debugging purposes (easier to see if it moves).
bg.x = 0;
bg.y = 0;
stage.addChild(bg);
stage.update();
//create a rectangle frame to represent the position of the stage.
stageborder = new createjs.Shape();
stageborder.graphics.beginStroke("Black").drawRect(0, 0, stage.canvas.width, stage.canvas.height);
stageborder.x = 0;
stageborder.y = 0;
stage.addChild(stageborder);
stage.update();
// MOUSEWHEEL ZOOM LISTENER - anywhere on canvas.
var factor
canvas.addEventListener("wheel", function(e){
if(Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)))>0){
factor = 1.1;
} else {
factor = 1/1.1;
}
var local = stage.globalToLocal(stage.mouseX, stage.mouseY);
stage.regX=local.x;
stage.regY=local.y;
stage.x=stage.mouseX;
stage.y=stage.mouseY;
stage.scaleX = stage.scaleX * factor;
stage.scaleY = stage.scaleY * factor;
//re-size the 'background' shape to be the same as the canvas size.
bg.graphics.command.w = bg.graphics.command.w / factor;
bg.graphics.command.h = bg.graphics.command.h / factor;
// re-position the 'background' shape to it's original position of (0,0) in the global space.
var localzero = stage.globalToLocal(0, 0);
bg.x = localzero.x;
bg.y = localzero.y;
stage.update();
});
// listener to add circles to the canvas.
canvas.addEventListener('dblclick', function(){
var localpos = stage.globalToLocal(stage.mouseX, stage.mouseY);
addCircle(10, localpos.x, localpos.y);
});
bg.addEventListener("mousedown", function(ev1){
// purpose of this listener is to be able to capture drag events on the 'background' to pan the whole stage.
// it needs to be a separate 'shape' object (rather than the stage itself), so that it doesn't fire when other shape objects are drag-moved around on the stage.
// get the initial positions of the stage, background, and mousedown.
var mousedownPos0 = {'x': ev1.stageX, 'y': ev1.stageY};
var stagePos0 = {'x': stage.x, 'y': stage.y};
var bgPos0 = {'x': bg.x, 'y': bg.y};
bg.addEventListener('pressmove', function(ev2){
//logic is to pan the stage, which will automatically pan all of it's children (shapes).
// except we want the 'background' shape to stay where it is, so we need to offset it in the opposite direction to the stage movement so that it stays where it is.
stageDelta = {'x': ev2.stageX - mousedownPos0.x, 'y': ev2.stageY - mousedownPos0.y};
//adjust the stage position
stage.x = stagePos0.x + stageDelta.x;
stage.y = stagePos0.y + stageDelta.y;
// return the 'background' shape to global(0,0), so that it doesn't move with the stage.
var localzero = stage.globalToLocal(0,0);
bg.x = localzero.x;
bg.y = localzero.y;
stage.update();
});
});
The grey box is my background shape. I have deliberately made it slightly smaller than the canvas, so that I can see where it is (useful for debugging).
Double click anywhere on the canvas to add some red circles.
If you drag a circle, it only moves that circle.
If you drag on the grey ‘background’ area in between circles, it moves the whole stage (and therefore all the child shapes belonging to the stage).
Because the grey background is also a child of the stage, it wants to move with it. So I have included some code to always return that grey box back to where it started.
The black border represents the position of the ‘stage’, I just added it to help visualise where the stage is.
The mousewheel zoom control is based on the answer to this question: EaselJS - broken panning on zoomed image
Similar to drag-panning, when zooming I have to adjust the size and position of the grey ‘background’ box so that it renders in the same position on the canvas.
However, it doesn’t stay exactly where I want it to… it seems to creep up towards the top left corner of the canvas when I zoom out.
I’ve spent quite some time trying to diagnose this behaviour and can’t find out why it’s happening. I suspect it may have something to do with rounding.. but I’m really not sure.
Can anyone explain why my grey box isn't staying stationary when I zoom in and out?
An alternative method would be to scrap the ‘background’ shape used for capturing mousedown events that aren’t on a ‘proper’ shape.
Instead, it might be possible to use the ‘stage’ mousedown event, but prevent it from moving the stage if the mouse is over a ‘shape’.
Would this be a better way of handling this behaviour? Any suggestions how to prevent it from moving the stage?
Thanks in advance,
Hugh.
Ok,
So as usually happens, after finally asking for help, I managed to work out the problem.
The issue was caused by making the background shape (grey rectangle) 10px smaller than the canvas, so that I could see its position more clearly (to assist with debugging). How ironic that this offset was causing the issue.
The 10px offset was not being converted into the 'local' space when the zoom was applied.
By making the grey rectangle's graphic position at (0,0) with width and height equal to that of the canvas, the problem went away!
Hope this is of use to someone at some point in time.
Cheers,
Hugh.

Resize a fabricjs rect to maintain border size

I would like to resize a fabricjs rect instead of scale it, but I'm seeing weird behaviour while dragging the resize handles. The border starts to disappear or duplicate. I've tried both the stable version 1.7.19 and the beta 2.0.0 of fabricjs.
Here is the essence of the code I'm using:
canvas.on('object:scaling', function(){
var obj = canvas.getActiveObject(),
width = obj.width,
height = obj.height,
scaleX = obj.scaleX,
scaleY = obj.scaleY;
obj.set({
width : width * scaleX,
height : height * scaleY,
scaleX: 1,
scaleY: 1
});
});
Working example here: https://codepen.io/bramchi/pen/GMLYEV/
Try scaling it up and down a bit by dragging the resize handles.
Screenshot of scaling up and down issues
What I would expect to happen is the rectangle growing and shrinking while dragging the handles, and the border size to stay the same. But somehow rendering starts to go bonkers if you cross 270px or so. When the mouse button is released, it renders properly again.
What am I doing wrong? Who knows a fix? Or could this be a bug in the library I can report?
Disable object caching to avoid this rendering behaviour:
fabric.Object.prototype.objectCaching = false;
For performance reasons Fabric.js caches objects by default during drag rotate skew and scale operations. One of the moderators of the fabric.js repo pointed me in the right direction, hooray for him!
as described here:
http://fabricjs.com/fabric-object-caching ( really last line )
disabling noScaleCache is enough.
That gives you caching anyway, just it invalidates cache everytime you scale the object.
Not that caching a rect is that usefull, but if you have the same behaviour for complex paths, that is still a good thing to have.
new fabric.Rect({
left: 50,
top: 50,
width: 250,
height: 250,
stroke: 'gray',
fill: 'lightgray',
strokeWidth: 10,
noScaleCache: false,
})

Canvas with moving bubbles over a blurred div

I have a canvas which has bubbles moving randomly, now when i put it on to a div which has a filter
-webkit-filter: blur(50px);
then the canvas bubble movement fps drops down and smoothness reduces.
I am not getting what is the issue.
Version with blurred background : http://freakengineers.com/bubbles/index.php
Version with normal background : http://freakengineers.com/bubbles/index2.php
You can load and blur the image (but I don't know if you can use a built in blur algorithm - I've used StackBlur) in a separate canvas as a sort of buffer, then copy it to your main canvas on each frame. JSFiddle: http://jsfiddle.net/1m8rbt7j/
Add a new <canvas> element to the HTML, load your image into it and then blur the new canvas:
var blurimg = new Image();
blurimg.onload = function() {
var blurcv = document.getElementById('blurcanvas');
blurcv.getContext('2d').drawImage(blurimg,0,0);
stackBlurCanvasRGB('blurcanvas', 0, 0, 1580, 730, 50);
};
blurimg.src = 'http://i.imgur.com/WaMsYBC.jpg';
(I had to use a different image to get around cross-origin rules.)
Then just draw the content of the new canvas to your main canvas at the start of each frame:
context.clearRect(0,0, w,h);
context.drawImage(document.getElementById('blurcanvas'),0,0);
With your versions I get 30 FPS without the blur and 20 FPS with it. I get 30 FPS with the blur in my version.

KineticJS - Moving 4000 tiles with Stage.draggable

(Sorry if this is a duplicate, but I don't think it is)
Just so you know, I'm using Google Chrome 29.0.1547.66 m.
I have a KineticJS Project going on at the moment which builds a tiled "staggered" map 40 x 100 tiles. The map takes about 500ms to render which is fine. All 4000 tiles have been put onto one layer.
Once that has completed, I try to drag the stage but I am getting very poor performance as it tries to move all 4000 tiles at once. We are talking about 160ms of CPU time.
I have tried breaking each row up into its own separate layer as others suggested, but that made it even worse (620ms CPU time) as it had to move each layer separately.
I wouldn't say I'm very good at JavaScript but I can't see a way to improve the performance of the drag and I have done some descent research.
Maybe caching the entire layer or something could work?
The project so far has quite a lot of code, so I'm just going to show snippets:
//Stage Object
window.stage = new Kinetic.Stage({
container: element,
width: 800,
height: 600,
draggable: true
});
//Map Loop for create and position tiles
var tempLayer = map.addLayer(); //makes a new layer and adds it to stage etc.
for (var y = 0; y < height; y++) { //100 tiles high
for (var x = width-1; x > -1; x--) { //40 tiles wide
var tileImg = map._tiles[mapArray[x][y]-1]; //map._tiles is just an array of Kinetic.Image objects
if (typeof(tileImg) != "undefined"){
var tileClone = tileImg.clone();
map.place(x, y, 0, tileClone, tempLayer); //places tile in world scope positions
}
}
}
tempLayer.draw();
If I can't work out how to improve the performance, there is no way anyone will be able to run this and the project is going to have to be binned, so all thoughts are appreciated! Thanks!
Have a look at this SO Question: How to cache a whole layer right before dragstart and revert it back on dragend?
The question and my answer describes a similar issue and I think the solution I came up with may help.
Basically what I was suggesting (although I haven't tried it completely so I don't know if it will work well):
Cache the layer using toImage
Drag the image on a different layer while hiding the original layer.
Calculate dx and dy (the distance that you moved)
Update the original layer with dx and dy
Hide image layer, show shapes layer.
I managed to create a quick example JSFIDDLE to work with so check it out. One thing I noticed is that the stage size really affected the performance of the drag, even if it was just 1 rectangle instead of 4000. So, if the stage is really big, even with this image caching thing it didn't really help. But when I decrease the stage size it seems to help
UPDATE:
I found the reason for that "stretched/scaled" image when dragging. It's because I had the image size set statically like so:
var image = new Kinetic.Image({
image: img,
x: 0,
y: 0,
width: 2000,
height: 5000
});
This caused the image to stretch since the image was larger than the stage. If we remove the width and height properties like so:
var image = new Kinetic.Image({
image: img,
x: 0,
y: 0
});
You'll see that the image doesn't stretch anymore.
The other good news is that I reduced the stage dimensions by half (although the number of rectangles, area taken by rectangles and size of image remains the same) and the performance has improved greatly. Hopefully your stage dimension isn't as large (2000x5000) as I had it before right? Check the JSFIDDLE now and see it in action!

Categories

Resources