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)
Related
I am building an app using HTML5 in which a grid is drawn. I have some shapes on it that you can move.
What I'm trying to do is to snap objects to some points defined when you hover them while moving a shape.
What I tried is to save anchors points inside an array and when the shape is dropped, I draw the shape on the closest anchor point.
Easeljs is my main js lib so I want to keep it but if needed I can use an other one with easeljs.
Thanks in advance for your help!
This is pretty straightforward:
Loop over each point, and get the distance to the mouse
If the item is closer than the others, set the object to its position
Otherwise snap to the mouse instead
Here is a quick sample with the latest EaselJS: http://jsfiddle.net/lannymcnie/qk1gs3xt/
The distance check looks like this:
// Determine the distance from the mouse position to the point
var diffX = Math.abs(event.stageX - p.x);
var diffY = Math.abs(event.stageY - p.y);
var d = Math.sqrt(diffX*diffX + diffY*diffY);
// If the current point is closeEnough and the closest (so far)
// Then choose it to snap to.
var closest = (d<snapDistance && (dist == null || d < dist));
if (closest) {
neighbour = p;
}
And the snap is super simple:
// If there is a close neighbour, snap to it.
if (neighbour) {
s.x = neighbour.x;
s.y = neighbour.y;
// Otherwise snap to the mouse
} else {
s.x = event.stageX;
s.y = event.stageY;
}
Hope that helps!
I need your advice, I have a html with canvas, in this html you can add circles and then link these to make a figure, I also want to insert a search where you can find the different figures and focussed on it, so I don't know if it'll be better : to overlay the figures or use different canvas for each figure.
(I'm not implement the search function yet.)
What do you think?
Here is the functions that makes de draws
//this function puts the circles on a <table>, and then the other function takes the img coordinates and link it making a figure.
function position(year, mon)
{
$('#' + year + ' .' + mon).prepend('<img class="black_point" src="./images/circle.png"/>');
}
var table = document.getElementById("tabla");
var images = table.getElementsByTagName("img");
var canvas = document.getElementById("miCanvas");
var ctx = canvas.getContext("2d");
var x,y; // Remember coordinates
canvas.width = table.offsetWidth;
canvas.height = table.offsetHeight;
function connect(image, index) { //this function link the images
var tabBcr = table.getBoundingClientRect();
var imgBcr = image.getBoundingClientRect();
x = imgBcr.left + (imgBcr.width / 2) - tabBcr.left;
y = imgBcr.top + (imgBcr.height / 2) - tabBcr.top;
index === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
}
//this function add a new canvas
function figure(){
/*$('#miCanvas').prepend('<canvas id="myCanvas">Su navegador no soporta Canvas.</canvas>');
//this is the property who overlay the figures
cxt.globalCompositeOperation="source-over";/*
//which it will be better to implement a search function?
}
// new path here
ctx.beginPath();
for(var i=0; i<images.length; i++){
connect( images[i], i); // provide index so we can sep. move/line
}
// then at the end:
ctx.fill();
Use 1 html canvas to hold all your connected circles.
This simplifies the event handling when focusing / blurring your figures.
You can test if the mouse is inside one of your circles like this:
// given a circle defined by centerX, centerY, radius
var dx = mouseX - centerX;
var dy = mouseY - centerY;
var isInside = dx*dx+dy*dy <= radius*radius;
Here's an outline of how to focus on a figure:
Create a javascript object defining each circle. If a set of objects makes up a figure, then add a group property to each circle-object representing which group that circle is a member of.
Put all your circle-objects in an array.
In your mouse event handlers, iterate through the circle-objects-array and find which circle is under the mouse. That circle's group has been focused.
I have created some lines connecting with each other on canvas. Now I want to animate these lines while drawing on canvas.
Can anyone please help?
here is my code and fiddle URL:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.moveTo(0,0,0,0);
ctx.lineTo(300,100);
ctx.stroke();
ctx.moveTo(0,0,0,0);
ctx.lineTo(10,100);
ctx.stroke();
ctx.moveTo(10,100,0,0);
ctx.lineTo(80,200);
ctx.stroke();
ctx.moveTo(80,200,0,0);
ctx.lineTo(300,100);
ctx.stroke();
http://jsfiddle.net/s4gWK/1/
I understand that your want the lines to extend incrementally along the points in your path using animation.
A Demo: http://jsfiddle.net/m1erickson/7faRQ/
You can use this function to calculate waypoints along the path:
// define the path to plot
var vertices=[];
vertices.push({x:0,y:0});
vertices.push({x:300,y:100});
vertices.push({x:80,y:200});
vertices.push({x:10,y:100});
vertices.push({x:0,y:0});
// calc waypoints traveling along vertices
function calcWaypoints(vertices){
var waypoints=[];
for(var i=1;i<vertices.length;i++){
var pt0=vertices[i-1];
var pt1=vertices[i];
var dx=pt1.x-pt0.x;
var dy=pt1.y-pt0.y;
for(var j=0;j<100;j++){
var x=pt0.x+dx*j/100;
var y=pt0.y+dy*j/100;
waypoints.push({x:x,y:y});
}
}
return(waypoints);
}
Then you can use requestAnimationFrame to animate each incremental line segment:
// calculate incremental points along the path
var points=calcWaypoints(vertices);
// variable to hold how many frames have elapsed in the animation
// t represents each waypoint along the path and is incremented in the animation loop
var t=1;
// start the animation
animate();
// incrementally draw additional line segments along the path
function animate(){
if(t<points.length-1){ requestAnimationFrame(animate); }
// draw a line segment from the last waypoint
// to the current waypoint
ctx.beginPath();
ctx.moveTo(points[t-1].x,points[t-1].y);
ctx.lineTo(points[t].x,points[t].y);
ctx.stroke();
// increment "t" to get the next waypoint
t++;
}
EDIT: I misunderstood your original post. For your situation you do not need to clear the previous animation, only when the animation is complete to start it all over.
jsfiddle : http://jsfiddle.net/Grimbode/TCmrg/
Here are two websites that helped me understand how animations work.
http://www.williammalone.com/articles/create-html5-canvas-javascript-sprite-animation/
In this article William speaks sprite animations, which of course isn't what you are interested in. What is interesting is that he uses a recursive loop function created by Paul Irish.
http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
This function will attempt to spin 60 times per second (so essentially at 60 fps).
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
So the big question is, how does this work? You pretty much just need to do this:
function gameLoop () {
window.requestAnimationFrame(gameLoop);
renderLine();
}
var counter = 0;
var old_position = {x: 0, y: 0};
var new_position = {x: 0, y: 0};
var width = 10;
var height = 10;
function renderLine(){
/* Here you clear the old line before you draw the new one */
context.clearRect(old_position.x, old_position.y, width, height)
/* you update your new position */
new_position = {x: 100, y: 200};
/* Here you call your normal moveTo and lineTo and stroke functions */
/* update old position with the nwe position */
old_position = new_position;
}
After this step, your question will probably like. "Ok, I have some kind of animation going on, but I don't want my line animation to spin at 60 fps". If you read williams post he uses "ticks".
The websites I linked do a much better job at explaining than I could. I suggest you read up on them. [= I had fun reading them.
AND: Here is your jfiddle :)
http://jsfiddle.net/Grimbode/TCmrg/
The idea is to draw and draw and draw several different lines in a loop to have the illusion that it's "animating". But that is the concept of animation anyway.
So determine what animation you want to do, and find out a way you can draw each frame in a loop.
That's the concept anyway. But I'd advise you use libraries for this.
Fabric.js (http://fabricjs.com/) and KineticJS (http://kineticjs.com/) are the libraries/frameworks I'd point you to.
I do have a fairly complex figure painted on canvas. (~ 1000 polygons). The repainting time for all of them is about 1 sec (very slow). Now I need to let user move over that figure and display a vertical and horizontal lines (cross hairs) from under mouse position. What is the best technique to paint only those 2 lines without going over all polygons and repaint everything at every mouse move.
Thx
This answer is broken.
You want to draw lines and move them without touching the underlying painting.
In the good old days, the method used was to paint with xor composition on top of the drawing, and draw the same pattern (here it would be lines) the same way at the same location to remove it from the screen, again without touching the real painting.
I tried to use this method with the code below to test it out, but it doesn't seem to work. Hopefully, someone with a better knowledge of canvas and js will come forth and fix things up.
function onmousemove(e){
// firt run
var tcanvas = document.getElementById("testCanvas");
var tcontext = tcanvas.getContext("2d");
var pos = {x : e.clientX, y : e.clientY,
w : tcanvas.width, h : tcanvas.height };
var comp = tcontext.globalCompositeOperation;
var paintline = function (p){
tcontext.save();
tcontext.lineWidth = 1;
tcontext.globalCompositeOperation = 'xor';
tcontext.fillStyle = "#000000";
tcontext.moveTo(0,p.y);
tcontext.lineTo(p.w,p.y);
tcontext.moveTo(p.x,0);
tcontext.lineTo(p.x,p.h);
tcontext.stroke();
tcontext.restore();
};
tcontext.save();
paintline(pos);
tcontext.restore();
var next = function(e){
var comp = tcontext.globalCompositeOperation;
paintline(pos);
pos = {x : e.clientX, y : e.clientY,
w : tcanvas.width, h : tcanvas.height };
paintline(pos);
};
document.onmousemove = next;
}
(function draw_stuff(){
// setup canvas
var tcanvas = document.getElementById("testCanvas");
var tcontext = tcanvas.getContext("2d");
// draw square
tcontext.fillStyle = "#FF3366";
tcontext.fillRect(15,15,70,70);
// set composite property
tcontext.globalCompositeOperation = 'xor';
// draw text
tcontext.fillStyle="#0099FF";
tcontext.font = "35px sans-serif";
tcontext.fillText("test", 22, 25);
// draw circle
tcontext.fillStyle = "#0099FF";
tcontext.beginPath();
tcontext.arc(75,75,35,0,Math.PI*2,true);
tcontext.fill();
document.onmousemove = onmousemove;
})();
Also, there are problem with compositing depending on the browser.
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.