How to drag rotated shapes using Raphael js library - javascript

I have done lots of examples on dragging an object created by Raphael's library. Now I am working with sets and was also able to write a code to drag them.
Now my problem appeared when I rotate an object and then drag it.
Check out this code example: demo
var paper = Raphael('stage', 300, 300);
var r = paper.rect(50,100,30,50).attr({fill:"#FFF"}).rotate(45),
t = paper.text(30, 140, "Hello");
var p = paper.set(r, t);
r.set = p, t.set = p;
p.newTX=0,p.newTY=0,p.fDx=0,p.fDy=0,p.tAddX,p.tAddY,p.reInitialize=false,
start = function () {
},
move = function (dx, dy) {
var a = this.set;
a.tAddX=dx-a.fDx,a.tAddY=dy-a.fDy,a.fDx=dx,a.fDy=dy;
if(a.reInitialize)
{
a.tAddX=0,a.fDx=0,a.tAddY=0;a.fDy=0,a.reInitialize=false;
}
else
{
a.newTX+=a.tAddX,a.newTY+=a.tAddY;
a.attr({transform: "t"+a.newTX+","+a.newTY});
}
},
up = function () {
this.set.reInitialize=true;
};
p.drag(move, start, up);
By examining the DEMO you can see that the set is created with rotated rectangle, but as soon you drag it, it goes back to the 0 degree state. Why? Any solutions?

The problem is that whenever an element is transformed by applying a string containing instructions to move, rotate, scale etc, it resets the transformation object, and hence previous transformations get lost. To avoid this, add "..." at the beginning of the transformation string. Like,
var el = paper.rect(10, 20, 300, 200);
// translate 100, 100, rotate 45°, translate -100, 0
el.transform("t100,100r45t-100,0");
// NOW, to move the element further by 50 px in both directions
el.transform("...t50,50");
If "t50,50" is used instead of "...t50,50", then transformation effect for "t100,100r45t-100,0" is lost and transformation effect for "t50,50" rules.
Raphael reference for further study: http://raphaeljs.com/reference.html#Element.transform
Hope this helps.

I found an easy solution to this problem. Since I need to have a diamond instead of rectangle, I have created a path that represents that diamond. Then this path becomes just like a square 45 degree rotated.
This turned out to be easy because dragging functionality I had for my program works perfectly with paths.

Related

How to check if mouse position is inside HTML5 Canvas path?

I'm new to HTML5 Canvas and Javascript. I'm currently working on a HTML5 Canvas project (I wrote a separate question about it here and here).
The logic of the game is pretty simple: track the mouse position and if the mouse position exits the blue region, the game resets. The user starts on level 1 (function firstLevel). If their mouse position enters the red box region, they advance onto the next level (return secondLevel()). I did this previously by a hefty list of if statements that compared the mouse's x and y coordinates.
I've corrected the code to create a global constructor function I can reference in each of the level functions:
function Path(array, color) {
let path = new Path2D();
path.moveTo(array[0][0], array[0][1]);
for (i = 1; i < array.length; i++) {
path.lineTo(array[i][0], array[i][1]);
}
path.lineTo(array[0][0], array[0][1]);
return path;
}
And its referenced in the level functions as such:
// In the individual levels, put code that describes the shapes locally
var big = [[350,200], [900,200], [900,250], [700,250], [600,250], [600,650], [350,650]];
var blue = Path(big);
var small = [[900,200], [900,250], [850,250], [850, 200], [900, 200]];
var red = Path(small);
// 2. create cursor
c.beginPath();
c.rect(mouseX, mouseY, mouseWidth, mouseHeight);
c.fillStyle = "#928C6F";
c.fill();
// Local code that draws shapes:
c.beginPath()
c.fillStyle = '#C1EEFF';
c.fill(blue);
c.beginPath()
c.fillStyle = "#FF4000";
c.fill(red);
I want to know how can I check for the mouse position so it can run those conditional statements using the isPointInPath method? I now have a reference (refs. blue and red) for the Canvas shape, so I'm hoping there's a way I can check if the mouse's x and y position is a point that is inside the shape/path.
Link to my project: https://github.uconn.edu/pages/ssw19002/dmd-3475/final-project/maze-page-1.html
Source code: https://github.uconn.edu/ssw19002/dmd-3475/tree/master/final-project
You could use the MouseEvent.offsetX and MouseEvent.offsetY properties of a mouse event such as mousemove. (You would need to add an event listener for mouse events to the canvas element of course.)
Then use the x and y offset values obtained to call isPointInPath like
ctx.isPointInPath(path, x, y)
where path is a return value from function Path.
While MDN lists the offset properties as "experimental" they seem to have been around since IE9 at least.

Sprite animations in paper.js

I'm working on my project in Paper.js.
In the part of It, I need to use sprite with animation.
To examplain It, I've got a space ship that can be everywhere on the screen, and there is an effect of disortion that happens sometimes.
I got prepared a spritesheet with 10 frames, and all I want is use Paper.js RASTER class to load It and animate on every frame.
The problem is in the positions, that I don't know how to calculate them...
When I load a raster
let slide = new Raster({
source: 'assets/sprite.png',
position: [0, 0]
});
I see center of a very long image, when I need to see the first frame.
My idea was to use group with containts mask (square)
let mask = new Rectangle({
position: [220, 100],
size: [186, 154],
});
That I can change position dynamically and animate the spread at the same time.
Is It possible that way?
It would be cool, If I cant calculate the position of raster against the mask, but for me now It seems impossible.
Anyone have idea how to attain this in a simple way?
Cheers.
I've looked into this in my project. The key is using a group with a pivot point. This code is admittedly unfinished and in raw javascript but it should give you a good idea:
var Sprite = paper.Group.extend({
_class: 'Sprite',
initialize: function Sprite(url, size) {
var maskSize = size || new paper.Size(256, 256);
var that = this;
this._raster = new paper.Raster(url);
this._raster.pivot = new paper.Point();
this._raster.on('load', function () {
that._spriteSheetWidth = Math.floor(this.size.width / maskSize.width);
that.setIndex(that._spriteIndex || 0);
});
this._clipRect = new paper.Path.Rectangle(new paper.Point(), maskSize);
Sprite.base.call(this, [this._clipRect, this._raster]);
this.clipped = true;
// Just use a blank point if you want the position to be in the corner
this.pivot = new paper.Point(maskSize.divide(2));
},
setIndex: function (index) {
if (typeof this._spriteSheetWidth !== "undefined") {
// TODO: FINISH SPRITE SHEET IMPLEMENTATION
}
this._spriteIndex = index;
}
});
I'm not actually using sprites in my project anymore so I never finished the implementation. But the complicated concepts should be completed above. Namely the way that paper.js implements pivot points and clipping masks. The position of an object is the center of it's bounds by default... this is kind of unweildy for a lot of reasons, like an images position will appear to change when it loads etc... or when the contents of a path change. So I like to set a pivot of 0,0 immediately after making any object. The next key section is that clipping masks only work on Groups. And finally you can extend the Group class to make a standard Sprite class.
Normal sprite shifting of this._raster.position.x and this._raster.position.y should finish this implementation off.
Edit: Finished my implementation... https://jsfiddle.net/willstott101/vgxq9kak/

How can you keep rotated draggable objects inside the Raphael paper?

I have an application with many draggable objects that can also be rotated in 90 degree increments. I'm trying to figure out how to stop the user from dragging the objects outside the Raphael paper (canvas).
This is fairly simple for unrotated objects. I can simply see if the current x and y coordinates are less than 0 and set them to 0 instead. I can adjust similarly by checking if they are outside the canvas width and height.
However, a problem arises when the object is rotated because for some odd reason the coordinate plane rotates as well. Is there an easy way to keep objects inside the canvas? Or is there an example of some this somewhere?
I have spent many hours fiddling with this and I can't seem to make sense of the rotated coordinate plane in order to adjust my calculations. Even when debugging the current coordinates, they seem to shift oddly if I drag an object, release it, and then drag the object again.
Any help is greatly appreciated.
Thanks,
Ryan
I had a similar problem, I needed to move a shape within the boundaries of another shape, so what I did was:
element.drag(onstart, onmove, onend);
...
onStart: function(x,y,e){
// Initialize values so it doesn't recalculate per iteration
// this allows to resume dragging from the point it were left
App.oldX = 0;
App.oldY = 0;
App.currentCircleX = App.fingerPath.attr('cx');
App.currentCircleY = App.fingerPath.attr('cy');
},
onMove: function(dx,dy,x,y,e){
App.setDirection(dx,dy);
},
onEnd: function(e){
// nothing to do here for now
},
// this function tells the element to move only if it's within the bound area
setDirection: function(dx, dy){
var isXYinside;
this.newX = this.currentCircleX - (this.oldX - dx);
this.newY = this.currentCircleY - (this.oldY - dy);
// HERE is the key, this method receives your bounding path and evaluates the positions given and then returns true or false
isXYinside = Raphael.isPointInsidePath(this.viewportPath, this.newX, this.newY);
this.oldX = dx;
this.oldY = dy;
// so if it is within the bound area, will move, otherwise will just stay there
if (isXYinside) {
this.fingerPath.attr({
"cx": this.newX,
"cy": this.newY
});
this.currentCircleX = this.newX;
this.currentCircleY = this.newY;
}
}
I know this is an old one, but I stumbled upon this question when trying to figure out a way to do it. So here's my 2 cents in case someone has this problem.
Reference:
Raphael.isPointInsidePath
Have you tried Element.getBBox()
There Are 2 flavones which give the result before rotation and after rotation
You should toggle the Boolean argument and test it

HTML5 Canvas Mouse

Something like I am have:
function mouseClick(event) {
...
}
canvas.addEventListener("mousedown", mouseClick, false);
function drawRect(x, y) {
context.fillRect(x, y, 16, 16);
};
drawRect(10, 10);
How to do something like if I am click on my Rect in Canvas get something? something like If I am click on Rect get alert;
Sorry for my English language.
A canvas is nothing more than a bitmap. There is no record kept that there was a rectangle drawn on the canvas, so if you want to detect that the click was inside an area that you drew a rectangle on, you have to keep a record of the areas you've drawn and test against them. eg:
var rects= [];
function mouseClick(event) {
// Get position of click relative to canvas. This is not reliable! Requires
// standards mode, and the canvas not being nested in other offsetParents.
// Getting page-relative co-ordinates reliably in all cases is outside the
// scope of this question...
//
var x= event.clientX-document.documentElement.scrollLeft-canvas.offsetLeft;
var y= event.clientY-document.documentElement.scrollTop-canvas.offsetTop;
// Hit-test each rectangle in the list of those drawn
//
for (var i= rects.length; i-->0;) {
var x0= rects[i][0], y0= rects[i][1], x1= rects[i][2], y1= rects[i][3];
if (x0<=x && x<x1 && y0<=y && y<y1) {
alert('you clicked on a rectangle!');
}
}
}
function drawRect(x, y) {
rects.push([x, y, x+16, y+16])
context.fillRect(x, y, 16, 16);
};
drawRect(10, 10);
If you are doing a lot of this sort of thing you are likely to be better off using a retained-mode drawing system like SVG instead of the pure-bitmap Canvas. In SVG you can listen for click events directly on a rectangle object, move the rectangle, re-stack it and so on.
$(canvas).mousedown(function myDown(e)
{
var position = $(canvas).position();
var x = e.pageX-position.left;
var y = e.pageY-position.top;
...
});
This is much better way to do this!
I think what you're saying is that you want events to occur when you click on objects you have drawn on a canvas. I have written a few tutorials on how to make persistent shapes and move them around with mouse clicks. That should give you a good starting point for this.
You could also try out Concrete.js http://www.concretejs.com which is a lightweight canvas framework that adds interactivity. You would just do something like this:
var key = canvas.getIntersection(x,y);
if x,y intersects a graphic you've drawn with a given key, it returns the key, and you know what was hovered/clicked on.
This is a much better solution because it will work on anything you draw, including circles, lines, curves, polygons, arbitrary blobs, etc.
addEventListener is tricky. What I would try is
canvas.addEventListener.apply(canvas, ["mousedown", mouseClick, false]);
or use an anonymous function.
canvas.addEventListener.apply(canvas, ["mousedown", function(){ mouseClick(); }, false]);
Using the apply function can help make sure the eventListener is applied to the right element. You can read about it here.

Raphael JS : how to move/animate a path object?

Somehow this doesn't work...
var paper = Raphael("test", 500, 500);
var testpath = paper.path('M100 100L190 190');
var a = paper.rect(0,0,10,10);
a.attr('fill', 'silver');
a.mousedown( function() {
testpath.animate({x: 400}, 1000);
});
I can move rects this way but not paths, why is that, and how do I move a path object then?!
With the latest version of Raphael, you can do this:
var _transformedPath = Raphael.transformPath('M100 100L190 190', 'T400,0');
testpath.animate({path: _transformedPath}, 1000);
This saves you from the trouble of having to clone a temp object.
It seems a path object doesn't get a x,y value - so your animation probably still runs, but does nothing. Try instead animating the path function:
testpath.animate({path:'M400 100L490 190'},1000);
It makes it a bit trickier to write the animation, but you have the benefit of getting rotation and scaling for free!
BTW: I'm sure this is just an example, but in your above code testpath gets put in the global scope because you don't initialize as var testpath
Solved, with thanx to Rudu!
You need to create a new path to animate to. You can do this with clone() and then apply the transformations to that clone. Seems very complex for a simple move like this, but it works...
var paper = Raphael("test", 500, 500);
var testpath = paper.path('M100 100L190 190');
var a = paper.rect(0,0,10,10);
a.attr('fill', 'silver');
a.mousedown( function() {
var temp = testpath.clone();
temp.translate(400,0);
testpath.animate({path: temp.attr('path')}, 1000);
temp.remove();
});
TimDog answer was best solution.
In addition, just remember, transform string in this case means, that it will add 400 points to every path point/line X coordinate, and 0 points to every Y coordinate.
That means, M100 100L190 190 will turn into M500 100L590 190.
So, if you need to move a path element to another position, the difference between current position and new position coordinates should be calculated. You can use first element to do that:
var newCoordinates = [300, 200],
curPos = testpath.path[0],
newPosX = newCoordinates[0] - curPos[1],
newPosY = newCoordinates[1] - curPos[2];
var _transformedPath = Raphael.transformPath(testpath.path, "T"+newPosX+","+newPosY);
testpath.animate({path: _transformedPath});
Hope this will help someone.
Here's some code that generalises the best of the above answers and gives Raphael paths a simple .attr({pathXY: [newXPos, newYPos]}) attribute similar to .attr({x: newXPosition}) and .animate({x: newXPosition}) for shapes.
This lets you move your path to a fixed, absolute position or move it by a relative amount in a standard way without hardcoding path strings or custom calculations.
Edit: Code below works in IE7 and IE8. An earlier version of this failed in IE8 / VML mode due to a Raphael bug that returns arrays to .attr('path') in SVG mode but strings to .attr('path') in VML mode.
Code
Add this code (Raphael customAttribute, and helper function) after defining paper, use as below.
paper.customAttributes.pathXY = function( x,y ) {
// use with .attr({pathXY: [x,y]});
// call element.pathXY() before animating with .animate({pathXY: [x,y]})
var pathArray = Raphael.parsePathString(this.attr('path'));
var transformArray = ['T', x - this.pathXY('x'), y - this.pathXY('y') ];
return {
path: Raphael.transformPath( pathArray, transformArray)
};
};
Raphael.st.pathXY = function(xy) {
// pass 'x' or 'y' to get average x or y pos of set
// pass nothing to initiate set for pathXY animation
// recursive to work for sets, sets of sets, etc
var sum = 0, counter = 0;
this.forEach( function( element ){
var position = ( element.pathXY(xy) );
if(position){
sum += parseFloat(position);
counter++;
}
});
return (sum / counter);
};
Raphael.el.pathXY = function(xy) {
// pass 'x' or 'y' to get x or y pos of element
// pass nothing to initiate element for pathXY animation
// can use in same way for elements and sets alike
if(xy == 'x' || xy == 'y'){ // to get x or y of path
xy = (xy == 'x') ? 1 : 2;
var pathPos = Raphael.parsePathString(this.attr('path'))[0][xy];
return pathPos;
} else { // to initialise a path's pathXY, for animation
this.attr({pathXY: [this.pathXY('x'),this.pathXY('y')]});
}
};
Usage
For absolute translation (move to fixed X,Y position) - Live JSBIN demo
Works with any path or set of paths including sets of sets (demo). Note that since Raphael sets are arrays not groups, it moves each item in the set to the defined position - not the centre of the set.
// moves to x=200, y=300 regardless of previous transformations
path.attr({pathXY: [200,300]});
// moves x only, keeps current y position
path.attr({pathXY: [200,path.pathXY('y')]});
// moves y only, keeps current x position
path.attr({pathXY: [path.pathXY('x'),300]});
Raphael needs to handle both x and y co-ordinates together in the same customAttribute so they can animate together and so they stay in sync with each other.
For relative translation (move by +/- X,Y) - Live JSBIN demo
// moves down, right by 10
path.attr({pathXY: [ path.pathXY('x')+10, path.pathXY('y')+10 ]},500);
This also works with sets, but again don't forget that Raphael's sets aren't like groups - each object moves to one position relative to the average position of the set, so results may not be what are expected (example demo).
For animation (move a path to relative or absolute positions)
Before animating the first time, you need to set the pathXY values, due to a bug/missing feature up to Raphael 2.1.0 where all customAttributes need to be given a numeric value before they are animated (otherwise, they will turn every number into NaN and do nothing, failing silently with no errors, or not animating and jumping straight to the final position).
Before using .animate({pathXY: [newX,newY]});, run this helper function:
somePath.pathXY();
Yet another way is to use "transform" attribute:
testpath.animate({transform: "t400,0"}, 1000);
to move the path to the right by 400px, relative to the original position.
This should work for all shapes, including paths and rectangles.
Note that:
"transform" attribute is independent of x, y, cx, cy, etc. So these attributes are not updated by the animation above.
The value of "transform" attribute is always based on the original position, not the current position. If you apply the animation below after the animation above, it will move it 800px to the left relatively, instead of moving it back to its original position.
testpath.animate({transform: "t-400,0"}, 1000);

Categories

Resources