I'm trying to do a simple animation in Raphael.js in which a paper.text object is moved from its current position to another position. Here is some of my code (there is far too much to post all of it):
songPos = getSongPosition(this, charIndex);
letter.path.animate({x: songPos.x, y: songPos.y, "font-size": this.correctFontSize}, 500, onAnimationComplete);
Here is the Letter object being referenced in the above code:
function Letter(args)
{
this.letter = args.letter || "A";
this.correct = args.correct || false;
this.transformation = args.transformation || "";
this.initX = args.x || 0;
this.initY = args.y || 0;
this.path = null;
}
Letter.prototype.buildPath = function()
{
this.path = paper.text(this.initX, this.initY, this.letter);
if(this.transformation)
{
this.path.transform(this.transformation);
}
return this;
};
The problem is I'm printing the x and y values returned by getSongPosition, and they're correct, but the animate method is sending my text object somewhere off-screen. I've also tried just setting the animation attributes to {x: 0, y: 0}, but I still get the same results. I can post more of the code if it is necessary.
Any help would be greatly appreciated.
UPDATE 1:
Part of my program requires I be able move objects to specific coordinates. Some of the objects will be rotated and others will not, so I wrote this:
Letter.prototype.getMoveTransform = function(x, y)
{
var bbox = this.path.getBBox(true);
var dx = x - bbox.x;
var dy = y - bbox.y;
return "T" + dx + "," + dy + (this.transformation == null ? "" : this.transformation);
};
I believe this is the root of my problem. This function is supposed to generate the transformation required to move a rotated object to (x, y). I'm not sure why I have to re-rotate the object on every animation.
UPDATE 2:
I have somehow solved my problem. I would post my solution, but I don't understand why any of this works/didn't work in the first place anymore.
this.path.getBBox(true); should be this.path.getBBox() or this.path.getBBox(false);
you need get transformed position every time to calculate the dx and dy
Related
I want to animate a curved motion (no rotation) of an object by using svg.js. But I can't find any easy solution for this problem. I wrote two little functions which work fine, but it isn't working like a normal animation, and it doesn't run perfectly in the background.
I would prefer some solution like this:
var draw = SVG("drawing").size(500,500);
var rect = draw.rect(50,50);
rect.animate().curvedmove(100,100);
The two functions I made:
function animateJump(object,start,end,ampl,y,i=0){
var speed = 25;
var pos = 0;
pos = start+i*((end-start)/speed);
object.animate(1).move(pos,y+bounceFunction(start,end,ampl,pos));
if (i <= speed){
animateJump(object,start,end,ampl,y,i+1)
}
}
function bounceFunction(a,b,c,x){
return -1 * (x-a)*(x-b) * c * (4/((a-b)*(b-a)));
}
Is there some easy solution?
Thanks for any help!
The animate method establish a new animation context in which runs the timer you specified (1 sec by default). So if you do el.animate().move(100,100) the element will move to position 100,100 in 1 second.
However, if you want to use your own function you need to listen to the during event which gives you the current position from 0-1 in time.
el.animate().during(function(pos, morphFn, easedPos) {
this.move(pos, bounceFunction(pos))
})
Note that pos is a value between 0 and 1 so setting it directly as coordinate does not make that much sense. You need to figure our the start and end value of the move and calculate it yourself (or use the morphFn like morphFn(start, end))
Example:
var startX = 100
var endX = 300
var startY = 100
var endY = 300
el.animate().during(function(pos, morphFn, easedPos) {
var x = morphFn(startX, endX)
var y = SVG.morph(bounceFunction(pos))(startY, endY)
this.move(x, y)
})
the morphFn is by default bound to the current position. So if you have your own position (like when using your custom bounce function) you need to create a new morph function which you can do with the SVG.morph method. This method expects a position and gives back a morph function bound to this positon.
So this would be the same:
var x = SVG.Morph(pos)(startX, endX)
var y = SVG.Morph(bounceFunction(pos))(startY, endY)
I'm trying Snap to manipulate SVG but I'm having some issues when dragging a group of elements along a path.
I modified this code that I found here.
start = function () {
this.data("ox", +this.getBBox().cx);
this.data("oy", +this.getBBox().cy);
},
move = function (dx, dy) {
var tmpPt = {
x: this.data('ox') + dx,
y: this.data('oy') + dy
};
// move will be called with dx and dy
l = gradSearch(l, tmpPt);
pt = path.getPointAtLength(l);
if (!isNaN(pt.x) && !isNaN(pt.y)) {
this.transform('t' + (pt.x - this.data("ox")) + ',' + (pt.y - this.data("oy")));
};
},
end = function () {
console.log('End of drag');
}
I can drag the group, it stays where I drop it, but when I start to drag again, the group goes back to it's origin point and if I go crazy it starts to be really buggy.
I'm new to SVG, I tried some tutorials to learn but this stuff seams to be out of my league, I'd really appreciate some insights from you
Edit : It's better with my code, thanks for pointing that out Ian.
I think the problem is that you are resetting ox each time. In the previous examples, they used attr('cx') not ox, so they were always referencing the very original position of the shape to offset from. As you are resetting this each time, the difference will always be zero, so it resets to scratch. So you need a couple of modifications.
So lets all 'ox' the 'original position' before we do anything as our base.
And call 'sx' the 'starting position' that we figure out every drag start. We will need this to add 'dx' (the delta difference to add in a drag).
We can store the original position intitially...and then change the transform later to reference that original position, rather than the start position.
group.drag(move, start, end);
group.data("ox", +group.getBBox().cx);
group.data("oy", +group.getBBox().cy);
start = function () {
this.data("sx", +this.getBBox().cx);
this.data("sy", +this.getBBox().cy);
},
move = function (dx, dy) {
var tmpPt = {
x: this.data('sx') + dx,
y: this.data('sy') + dy
};
l = gradSearch(l, tmpPt);
pt = path.getPointAtLength(l);
// We change this!!!
if (!isNaN(pt.x) && !isNaN(pt.y)) {
this.transform('t' + (pt.x - this.data("ox")) + ',' + (pt.y - this.data("oy")) );
};
},
jsfiddle
I saw that you have helped David with his mirroring canvas problem before. Canvas - flip half the image
I have a similar problem and hope that maybe you could help me.
I want to apply the same mirror effect on my webcam-canvas, but instead of the left side, I want to take the RIGHT half of the image, flip it and apply it to the LEFT.
This is the code you've posted for David. It also works for my webcam cancas. Now I tried to change it, so that it works for the other side, but unfortunately I'm not able to get it.
for(var y = 0; y < height; y++) {
for(var x = 0; x < width / 2; x++) { // divide by 2 to only loop through the left half of the image.
var offset = ((width* y) + x) * 4; // Pixel origin
// Get pixel
var r = data[offset];
var g = data[offset + 1];
var b = data[offset + 2];
var a = data[offset + 3];
// Calculate how far to the right the mirrored pixel is
var mirrorOffset = (width - (x * 2)) * 4;
// Get set mirrored pixel's colours
data[offset + mirrorOffset] = r;
data[offset + 1 + mirrorOffset] = g;
data[offset + 2 + mirrorOffset] = b;
data[offset + 3 + mirrorOffset] = a;
}
}
Even if the accepted answer you're relying on uses imageData, there's absolutely no use for that.
Canvas allows, with drawImage and its transform (scale, rotate, translate), to perform many operations, one of them being to safely copy the canvas on itself.
Advantages is that it will be way easier AND way way faster than handling the image by its rgb components.
I'll let you read the code below, hopefully it's commented and clear enough.
The fiddle is here :
http://jsbin.com/betufeha/2/edit?js,output
One output example - i took also a mountain, a Canadian one :-) - :
Original :
Output :
html
<canvas id='cv'></canvas>
javascript
var mountain = new Image() ;
mountain.onload = drawMe;
mountain.src = 'http://www.hdwallpapers.in/walls/brooks_mountain_range_alaska-normal.jpg';
function drawMe() {
var cv=document.getElementById('cv');
// set the width/height same as image.
cv.width=mountain.width;
cv.height = mountain.height;
var ctx=cv.getContext('2d');
// first copy the whole image.
ctx.drawImage(mountain, 0, 0);
// save to avoid messing up context.
ctx.save();
// translate to the middle of the left part of the canvas = 1/4th of the image.
ctx.translate(cv.width/4, 0);
// flip the x coordinates to have a mirror effect
ctx.scale(-1,1);
// copy the right part on the left part.
ctx.drawImage(cv,
/*source */ cv.width/2,0,cv.width/2, cv.height,
/*destination*/ -cv.width/4, 0, cv.width/2, cv.height);
// restore context
ctx.restore();
}
I have a rect rotated by 45degrees when I try and move it in a straight line using the normal move drag functionality it moves with the 45degree rotation. I've seen a lot of posts regarding this and that this is intended to work like this but I haven't found a simple way to fix this.
I know about the raphael.free_transform.js plugin but I don't need 90% of the script.
From other posts I know I'm supposed to use Math.atan2 but alas my Math skills aren't that great.
My current move function looks like this:
function (dx, dy) {
var att = this.type == "rect" ? {x: this.ox + dx, y: this.oy + dy} : {cx: this.ox + dx, cy: this.oy + dy};
this.attr(att);
for (var i = connections.length; i--;) {
r.connection(connections[i]);
}
r.safari();
}
You have to use "transform" method instead of simply changing x-y attrs.
var data = {};
var R = Raphael('raphael', 500, 500);
var rect = R.rect(100, 100, 100, 50).attr({fill: "#aa5555"}).transform('r45');
var default_transform = rect.transform();
var onmove = function (dx, dy) {
rect.transform(default_transform + 'T' + dx + ',' + dy);
};
var onstart = function () {};
var onend = function () {
default_transform = rect.transform();
};
rect.drag(onmove, onstart, onend);
I have created a live demo for you on JSfiddle: http://jsfiddle.net/pybBq/
Please note that you have to use big T letter in transform string (not small t) to make transformation absolute and not relative. Please read Raphael's docs on transform for more info: http://raphaeljs.com/reference.html#Element.transform
I want to animate a path (actually a set of paths, but I'll get to that) along a curved path.
RaphaelJS 2 removed the animateAlong method, for reasons I haven't been able to discern. Digging into the Raphael documentation's gears demo as abstracted by Zevan, I have got this far:
//adding a custom attribute to Raphael
(function() {
Raphael.fn.addGuides = function() {
this.ca.guide = function(g) {
return {
guide: g
};
};
this.ca.along = function(percent) {
var g = this.attr("guide");
var len = g.getTotalLength();
var point = g.getPointAtLength(percent * len);
var t = {
transform: "t" + [point.x, point.y]
};
return t;
};
};
})();
var paper = Raphael("container", 600, 600);
paper.addGuides();
// the paths
var circ1 = paper.circle(50, 150, 40);
var circ2 = paper.circle(150, 150, 40);
var circ3 = paper.circle(250, 150, 40);
var circ4 = paper.circle(350, 150, 40);
var arc1 = paper.path("M179,204c22.667-7,37,5,38,9").attr({'stroke-width': '2', 'stroke': 'red'});
// the animation
// works but not at the right place
circ3.attr({guide : arc1, along : 1})
.animate({along : 0}, 2000, "linear");
http://jsfiddle.net/hKGLG/4/
I want the third circle to animate along the red path. It is animating now, but at a distance from the red path equal to the third circle's original coordinates. The weird thing is that this happens whether the transform translate in the along object is relative (lowercase "t") or absolute (uppercase "T"). It also always animates in the same spot, even if I nudge it with a transform translation just before the animate call.
Any help very appreciated. I just got off the boat here in vector-land. Pointers are helpful--a working fiddle is even better.
You're just a hop, skip, and jump away from the functionality that you want. The confusion here concerns the interaction between transformations and object properties -- specifically, that transformations do not modify the original object properties. Translating simply adds to, rather than replaces, the original coordinates of your circles.
The solution is extremely straightforward. In your along method:
this.ca.along = function(percent) {
var box = this.getBBox( false ); // determine the fundamental location of the object before transformation occurs
var g = this.attr("guide");
var len = g.getTotalLength();
var point = g.getPointAtLength(percent * len);
var t = {
transform: "...T" + [point.x - ( box.x + ( box.width / 2 ) ), point.y - ( box.y + ( box.height / 2 ) )] // subtract the center coordinates of the object from the translation offset at this point in the guide.
};
return t;
Obviously, there's some room for optimization here (i.e., it might make sense to create all your circles at 0,0 and then translate them to the display coordinates you want, avoiding a lot of iterative math). But it's functional... see here.
One other caveat: the ...T translation won't effect any other transforms that have already been applied to a given circle. This implementation is not guaranteed to play nicely with other transforms.