passively tween a property with tweenlite - javascript

I'd like to be able to passively tween a property on my object, so that during the tween I can update this object and TweenLite will carry on.
For example, the following code will tween coordinates in an object from 0 to 15 over 15 seconds. Whilst this is happening, I also want to update the x and y variables of target.position, and I cannot do this as TweenLite seems to "hog" the object until it's done (as in, until 15 seconds have passed).
// target.position starts at { x:0, y: 0 }
TweenLite.to(target.position, 15, {
x: 15,
y: 15,
ease: Power4.easeOut
})
// for examples sake i'd like to do the following, yet it does not have an effect
setTimeout(function(){ target.position.x += 10 }, 1000)
setTimeout(function(){ target.position.y += 15 }, 2500)
setTimeout(function(){ target.position.x -= 17 }, 7500)

I solved my question by using the ModifiersPlugin that Tahir Ahmed recommended.
The ModifiersPlugin gives you two values in the callback, it's current value and the running total of the tween, I have named this cX and rT. What is returned in the callback will used by TweenLite in the next call, and given again as rT. This is handy so I can let ModifiersPlugin look after it's own running total, tween to x and y yet not actually update the target.position.. pretty useful.
All I do is work out the change needed, so the delta, which I call dX and add that to my target position, and passively tweening a variable is possible!
My code now looks something like this:
// just some dummy example data
target.position.x = 200
target.position.y = 300
x = 300
y = 400
TweenLite.to({ x: target.position.x, y: target.position.y }, 0.3, {
x: x,
y: y,
ease: Power4.easeOut,
modifiers: {
x: function(cX, rT) {
// get delta (current change in) value from running total - current
const dX = cX - rT.x
target.position.x += dX
// update running total with current delta
rT.x += dX
return rT.x
},
y: function(cY, rT) {
const dY = cY - rT.y
target.position.y += dY
rT.y += dY
return rT.y
}
}
})

Related

svg.js animated rotation of elements gives unexpected results (visible "jiggling")

I am using svg.js to create an animation of a bicyle rider. Semi-complete version here: https://pedalfuriously.neocities.org/. I'm running in to a bit of a problem with moving and rotating svg elements during animation created with requestAnimationFrame (rather than the svg.js built in animation).
If you take a look at the link, and use the cadence slider to make the rider pedal very fast, and then flip the slider quickly all the way back to zero, you can see that his lower leg "jiggles" in a disconnected way. What's really doing my head in is that the postion of the legs are determined in each frame based on an absolute relation to the rotation of the cranks (rather than taking some delta time value to determine movement over that frame).
I think I've been able to confirm what aspect of my code is causing the problem. Here is a minimal example that doesn't exhibit the exact behaviour, but I think illustrates the kind of thing I think is responsible:
var draw = SVG("drawing").viewbox(0, 0, 400, 400)
var origin = {
x: 70,
y: 70
}
var length = 60
var blueLine = draw.group()
blueLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
.stroke({
color: "#00f",
width: 4
})
blueLine.angle = 0
var greenLine = draw.group()
greenLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
.stroke({
color: "#0f0",
width: 4
})
greenLine.angle = 0
var previous = 0
var dt = 0
var step = function(timestamp) {
dt = timestamp - previous
previous = timestamp
blueLine.angle += 0.18 * dt
blueLine.rotate(blueLine.angle, origin.x, origin.y)
var endX = Math.cos(toRad(blueLine.angle)) * length
var endY = Math.sin(toRad(blueLine.angle)) * length
// Comment out this line, and rotation works fine
greenLine.move(endX, endY)
greenLine.angle = blueLine.angle - 10
// Comment out this line, and movement works fine
greenLine.rotate(greenLine.angle, origin.x, origin.y)
// But they don't work together. If I both move and rotate
// the green line, it goes in this crazy huge arc, rather
// than rotating neatly around the end of the blue line
// as expected.
window.requestAnimationFrame(step)
}
window.requestAnimationFrame(step)
function toRad(deg) {
return deg * (Math.PI / 180)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.4/svg.js"></script>
<div id="drawing"></div>
Something else I noticed with my actual code is that if I move the position of the legs, it changes the severity of the problem, or even stops it altogether. If the hips are positioned all the way near the front of the bicycle, the problem is not nearly as bad. Also, if I disable rotation on the lower legs, there is no jiggling. In some positions, the lower leg will just rotate out of the screen instantly on load, even before any motion has been started.
I'm hoping for some guidance on wether I'm misunderstanding the way manipulating elements works, either in svg.js in particular, or SVG in general.
Thank you kind vector graphics experts!
Here is the actual code for the legs. The step() function would probably be the most relevant. Not sure if it will be helpful:
Rider.Leg = function(foot, front, xOffset, yOffset) {
var upper = front ? SVGE.upperLeg : SVGE.upperLegBack
var lower = front ? SVGE.lowerLeg : SVGE.lowerLegBack
this.foot = foot
this.draw = foot.draw
this.geo = {
upper: {
x: this.foot.pedal.gear.x + 150,
y: this.foot.pedal.gear.y - 750,
length: 396
},
lower: {
length: 390
}
}
this.upper = this.draw.group().svg(upper).move(this.geo.upper.x, this.geo.upper.y)
.transform({ scale: 0.95, cx: 0, cy: 0 })
this.lower = this.draw.group().svg(lower).move(this.geo.upper.x, this.geo.upper.y)
}
// Step function does not take in a time argument. Positioning of legs is based only on
// the absolute position of other elements, none of which jiggle.
Rider.Leg.prototype.step = function () {
var angle = this.pedalAngle() - Math.PI
var ha = this.scaleneAngle(this.geo.lower.length, this.geo.upper.length, this.pedalDistance())
var ka = this.scaleneAngle(this.pedalDistance(), this.geo.lower.length, this.geo.upper.length)
var x = this.geo.upper.length * Math.cos(ha + angle)
var y = this.geo.upper.length * Math.sin(ha + angle)
this.upper.rotate(Drive.toDeg(angle + ha), 0, 0)
this.lower.move(this.geo.upper.x + x, + this.geo.upper.y + y)
this.lower.rotate(Drive.toDeg(angle + ha + ka - Math.PI), 0, 0)
}
// Gets the distance between the hip joint and the pedal
Rider.Leg.prototype.pedalDistance = function () {
var pos = this.foot.getPos()
var xDist = this.geo.upper.x - pos.x
var yDist = this.geo.upper.y - pos.y
return Math.hypot(xDist, yDist)
}
// Gets the angle between the hip joint and the pedal
Rider.Leg.prototype.pedalAngle = function () {
var pos = this.foot.getPos()
var xDist = this.geo.upper.x - pos.x
var yDist = this.geo.upper.y - pos.y
return Math.atan2(yDist, xDist)
}
Rider.Leg.prototype.scaleneAngle = function (a, b, c) {
return Math.acos(((b * b) + (c * c) - (a * a)) / (2 * b * c))
}
When you call move() on a group it is internally represented as a translation. svg.js figures out crazy ways to translate the object to the new place without changing any other transformations. That often does not work out. Especially not, when you rotate.
Thats why you should avoid these absolute transformations and go with relative ones. Just call untransform before every move and go from zero. Then you can do:
greenLine.transform({x:endX, y:endY, relative: true})
To move the line by a certain amount. That should work way better.

$localStorage value got change with fabric js

I saving polygon value inside angular $localStorage .
Once fabric js draw the object . my $localStorage changed.
var arr = [{
x: 81,
y: 58
}, {
x: 221,
y: 23
}, {
x: 247,
y: 158
}, {
x: 100,
y: 219
}, {
x: 81,
y: 58
}];
if(!$localStorage.mask)
$localStorage.mask = arr;
Is it a bug ?
Here the plunker
Indeed, FabricJS seems to modify the given points array for its internal purposes (i.e. offset), specifically in this piece of code (from https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.js):
...
//misko321: only for reference of what minX, minY, width and height are
_calcDimensions: function() {
var points = this.points,
minX = min(points, 'x'),
minY = min(points, 'y'),
maxX = max(points, 'x'),
maxY = max(points, 'y');
this.width = (maxX - minX) || 0;
this.height = (maxY - minY) || 0;
this.minX = minX || 0,
this.minY = minY || 0;
},
_applyPointOffset: function() {
// change points to offset polygon into a bounding box
// executed one time
this.points.forEach(function(p) {
p.x -= (this.minX + this.width / 2);
p.y -= (this.minY + this.height / 2);
}, this);
},
...
Together with the fact that ngStorage observes for any changes to the added objects, it updates those modified (internally by Fabric.js) coordinates.
The cleanest solution that comes to my mind at the moment is to copy the arr object (ref. Most elegant way to clone a JavaScript object) and send one of the objects to ngStorage and the other one to FabricJS.
Nevertheless, it would be nice, if ngStorage supported some sort of freeze() method, that would disable the AngularJS watch on this object, thus preventing it from further modifications.

How calculate a 1/4 circle arc to move along (bezier curve)?

I'm using JQuery.path to move an object along a bezier curve. When the item is clicked, I can determine the start and end points. How do I calculate the angle and length to make the element move from point A to point B on an arc that's 1/4 of a circle intersecting the start and end point?
I essentially want it to move along a curve that never dips lower than the starting y position and never to the left of the end x position.
var path = {
start: {
x: currentLeft,
y: currentTop,
angle: ????, //Don't know how to calculate this
length: ???? //Don't know how to calculate this
},
end: {
x: endLeft,
y: endTop,
angle: ????, //Don't know how to calculate this
length: ???? //Don't know how to calculate this
}
};
jQuery(myElement).animate(
{
path: new jQuery.path.bezier(path)
}
);
Approx. what I want:
Approx what I'm getting (they're dipping too low):
A generalised solution is slightly tricky because it must handle diagonal movements in each of four diagonal directions, and horizontal, and vertical.
First, you need a couple of utility functions :
function r2d(x) {
/* radians to degrees */
return x * 180 / Math.PI;
}
function smaller(x, y) {
/* returns the closer of x|y to zero */
var x_ = Math.abs(x);
var y_ = Math.abs(y);
return (Math.min(x_, y_) === x_) ? x : y;
}
Now a main function, anim, accepts a jQuery object (containing the element of interest) and an end object (with properties .left and .top ).
function anim($el, end) {
var current = $el.position();
var slope1 = (end.top - current.top) / (end.left - current.left);
var slope2 = 1 / slope1;
var endAngle = r2d(Math.atan(smaller(slope1, slope2)));
var startAngle = -endAngle;
var length = 1/3; //Vary between 0 and 1 to affect the path's curvature. Also, try >1 for an interesting effect.
//For debugging
$("#endAngle").text(endAngle);
$("#startAngle").text(startAngle);
$("#length").text(length);
var path = {
start: {
x: current.left,
y: current.top,
angle: startAngle,
length: length
},
end: {
x: end.left,
y: end.top,
angle: endAngle,
length: length
}
};
$el.animate({ path: new jQuery.path.bezier(path) });
}
The calculation of endAngle is pretty simple for each individual case (the four diagonals, horizontal and vertical) but slightly tricky for a generalised solution. It took me a while to develop something that worked in all cases.
DEMO
If the "what you want" is really what you need, i.e. 90 degree departure and arrivals, then we can solve this problem pretty much instantly:
p_start = { X:..., Y:... }
p_end = { X:..., Y:... }
dx = p_end.X - p_start.X
dy = p_end.Y - p_start.Y
control_1 = { X: p_start.X, Y: p_start.Y + 0.55228 * dy }
control_2 = { X: p_end.X - 0.55228 * dx, Y: p_end.Y }
And done. What we've basically done is pretend that the start and end points lie on a circle, and computer the control points such that the resulting Bezier curve has minimal error wrt the quarter circular arc.
In terms of angles: The departure from start is always at angle π/2, and the arrival at the end points is always at angle 0.

How to make my conditional statement less ugly [closed]

Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 10 years ago.
Improve this question
I am trying to make the following codes less ugly and not sure what to do. Are there any suggestions from you guys? Thanks
a lot.
if($element.is('builder') || $element.is('#options') ){
tooltipYPos=yPos + 35;
}
if($element.is('#button')){
tooltipXPos=xPos - 240;
}
if($element.is('#addn')){
tooltipXPos=xPos - 295;
tooltipYPos=yPos - 80;
}
if($element.is('#count')){
tooltipXPos=xPos + 180;
tooltipYPos=yPos - 90;
}
if($element.is('label')){
tooltipXPos=xPos + 80;
tooltipYPos=yPos - 90;
}
The general cure is to move the problem from code to data. One solution (demonstrated below) is to set up a JavaScript associative array keyed on the various ID tags and the values are value pairs X and Y offsets (in some cases one or the other is 0). At usage time, loop through the keys of the associative array, looking for matches. If so, add the X and Y offsets from the associative array onto toolTipXPos and toolTipYPos.
This will keep your offsets in one place, out of the way, and the code to manipulate them short and simple.
(untested, naturally)
// This can be stashed away anywhere.
var a = {
'#te_classbuilder': { X: 0, Y: 35 },
'#te_options': { X: 0, Y: 35 },
'#lesson-details-extend-button': { X: -240, Y: 0 },
'#asset-list-asset-add-button': { X: 295, Y: -80 },
'#asmnt_option_label_q_count': { X: 180, Y: -90 },
"label": { X: 80, Y: -90 }
}
// Put this where you need the actual evaluation to happen
jQuery.each(data, function(key, value) {
if ( $element.is(key) ) {
tooltipXPos = xPos + value.X;
tooltipYPos = yPos + value.Y;
}
});
Edit: changed to loop, so that label could be tested for, and not #label.
Another option would be to make use of jQuery's .data function to store the appropriate X and Y values on the elements themselves, like so:
$('label').data({ x: 80, y: -90 });
$('#te_classbuilder, #te_options').data({ x: 0, y: 35 });
$('#lesson-details-extend-button').data({ x: -240, y: 0 });
$('#asset-list-asset-add-button').data({ x: -295, y: -80 });
$('#asmnt_option_label_q_count').data({ x: 180, y: -90 });
Then, when it comes time to modify your tooltip position values, no conditional statements are required. Simply retrieve the x and y data attributes from $element.
tooltipXPos = xPos + $element.data('x');
tooltipYPos = yPos + $element.data('y');
This, of course, assumes that any element that may be assigned to $element will have previously had .data called on it with appropriate x and y values.
Can you work out the height/width of $element and then alter tooltipXPos/tooltipYPos accordingly, instead of hard-coding the deltas?
In first if you can combine conditions:
if ($element.is('#te_classbuilder, #te_options')) {
tooltipYPos = yPos + 35;
}
If we could not make some big changes then I would write it this way:
if ($element.is('#te_classbuilder, #te_options')) tooltip = { x: 0, y: yPos + 35 };
if ($element.is('#lesson-details-extend-button')) tooltip = { x: xPos - 240, y: 0 };
if ($element.is('#asset-list-asset-add-button')) tooltip = { x: xPos - 295, y: yPos - 80 };
if ($element.is('#asmnt_option_label_q_count')) tooltip = { x: xPos + 180, y: yPos - 90 };
if ($element.is('label')) tooltip = { x: xPos + 80; y: yPos - 90 };

How to animate both rotation and transformation in Raphaël

I'm trying to do something I thought would be rather simple. I've an object that I move around stepwise, i.e. I receive messages every say 100 milliseconds that tell me "your object has moved x pixels to the right and y pixels down". The code below simulates that by moving that object on a circle, but note that it is not known in advance where the object will be heading in the next step.
Anyway, that is pretty simple. But now I want to also tell the object, which is actually a set of subobjects, that it is being rotated.
Unfortunately, I am having trouble getting Raphaël to do what I want. I believe the reason is that while I can animate both translation and rotation independently, I have to set the center of the rotation when it starts. Obviously the center of the rotation changes as the object is moving.
Here's the code I'm using and you can view a live demo here. As you can see, the square rotates as expected, but the arrow rotates incorrectly.
// c&p this into http://raphaeljs.com/playground.html
var WORLD_SIZE = 400,
rect = paper.rect(WORLD_SIZE / 2 - 20, 0, 40, 40, 5).attr({ fill: 'red' }),
pointer = paper.path("M 200 20 L 200 50"),
debug = paper.text(25, 10, ""),
obj = paper.set();
obj.push(rect, pointer);
var t = 0,
step = 0.05;
setInterval(function () {
var deg = Math.round(Raphael.deg(t));
t += step;
debug.attr({ text: deg + '°' });
var dx = ((WORLD_SIZE - 40) / 2) * (Math.sin(t - step) - Math.sin(t)),
dy = ((WORLD_SIZE - 40) / 2) * (Math.cos(t - step) - Math.cos(t));
obj.animate({
translation: dx + ' ' + dy,
rotation: -deg
}, 100);
}, 100);
Any help is appreciated!
If you want do a translation and a rotation too, the raphael obj should be like that
obj.animate({
transform: "t" + [dx , dy] + "r" + (-deg)
}, 100);
Check out http://raphaeljs.com/animation.html
Look at the second animation from the top on the right.
Hope this helps!
Here's the code:
(function () {
var path1 = "M170,90c0-20 40,20 40,0c0-20 -40,20 -40,0z",
path2 = "M270,90c0-20 40,20 40,0c0-20 -40,20 -40,0z";
var t = r.path(path1).attr(dashed);
r.path(path2).attr({fill: "none", stroke: "#666", "stroke-dasharray": "- ", rotation: 90});
var el = r.path(path1).attr({fill: "none", stroke: "#fff", "stroke-width": 2}),
elattrs = [{translation: "100 0", rotation: 90}, {translation: "-100 0", rotation: 0}],
now = 0;
r.arrow(240, 90).node.onclick = function () {
el.animate(elattrs[now++], 1000);
if (now == 2) {
now = 0;
}
}; })();

Categories