How does the easing function for jQuery work? Take for example:
easeInQuad = function (x, t, b, c, d) {
return c*(t/=d)*t + b;
};
How does that work? What does each parameter hold? How would I implement some dumb easing for an animation?
Also how would I attach an easing pattern to jQuery, is loading it into $.easing good enough?
According to the jQuery 1.6.2 source, the meaning of the easing function is as follows. The function is called at various points in time during the animation. At the instants it is called,
x and t both say what the time is now, relative to the start of the animation. x is expressed as a floating point number in the range [0,1], where 0 is the start and 1 is the end. t is expressed in milliseconds since the start of the animation.
d is the duration of the animation, as specified in the animate call, in milliseconds.
b=0 and c=1.
The easing function should return a floating point number in the range [0,1], call it `r`. jQuery then computes `x=start+r*(end-start)`, where `start` and `end` are the start and end values of the property as specified in the call to animate, and it sets the property value to `x`.
As far as I can see, jQuery doesn't give you direct control over when the animation step function is called, it only lets you say "if I am called at time t, then I should be thus far through the entire animation." Therefore you cannot, for example, ask for your object to be redrawn more frequently at times when it is moving faster. Also, I don't know why other people say b is the start value and c is the change -- that's not what jQuery source code says.
If you wanted to define your own easing function to do the same as easeInQuad, for example,
$.extend(jQuery.easing,{myfunc:function(x,t,b,c,d) { return x*x; }})
$('#marker').animate({left:'800px'},'slow','myfunc');
$.extend(jQuery.easing,{myfunc:function(x,t,b,c,d) { return x*x; }})
$('#marker').animate({left:'500px'},'slow','myfunc');
#marker { position: absolute; left: 10px; top: 50px; background: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='marker'>Hello World!</div>
A concrete example,
Suppose our initial value is 1000 and we want to reach 400 in 3s:
var initialValue = 1000,
destinationValue = 400,
amountOfChange = destinationValue - initialValue, // = -600
duration = 3,
elapsed;
Let's go from 0s to 3s:
elapsed = 0
$.easing.easeInQuad(null, elapsed, initialValue, amountOfChange, duration)
//> 1000
elapsed = 1
$.easing.easeInQuad(null, elapsed, initialValue, amountOfChange, duration)
//> 933.3333333333334
elapsed = 2
$.easing.easeInQuad(null, elapsed, initialValue, amountOfChange, duration)
//> 733.3333333333334
elapsed = 3
$.easing.easeInQuad(null, elapsed, initialValue, amountOfChange, duration)
//> 400
So compared to the synopsis:
$.easing.easeInQuad = function (x, t, b, c, d) {...}
we can deduce:
x t b c d
| | | | |
null elapsed initialValue amountOfChange duration
NB1: One important thing is that elapsed(t) and duration(d) should be expressed in the same unit, eg: here 'seconds' for us, but could be 'ms' or whatever. This is also true for initialValue(b) and amountOfChange(c), so to sum-up:
x t b c d
| | | | |
null elapsed initialValue amountOfChange duration
^ ^ ^ ^
+----------|----=unit=----|------------+
+----=unit=----+
NB2: Like #DamonJW, I don't know why x is here. It does not appear in Penner's equations and does not seem to be used in result: let always set him to null
t: current time, b: start value, c: change from the start value to the end value, d: duration.
Here is how it works: http://james.padolsey.com/demos/jquery/easing/ (curve representing when a CSS property is changed).
Here is how I would implement some dumb easing: http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm (math is not my strong suit)
You would extend like any of these: http://code.google.com/p/vanitytools/source/browse/trunk/website/js/custom_easing.js?spec=svn29&r=29 - good enough!
This plugin implements the most common easing functions: http://gsgd.co.uk/sandbox/jquery/easing/
Looked through the 1.11 jquery code. The x parameter seems to mean 'percent', independent from initial time value.
So, x is always (0 <= x <= 1) (means abstract coefficient), and t is x * d (means time elapsed).
Related
I'm making a semi-realistic 'physics' engine in node.js, if you can even call it that and I want to accelerate exponentially. E.g. from 0m/s to 4.5m/s in 2 seconds, then maybe decelerate to 0m/s in 3 seconds. Obviously for the deceleration part I can probably get away with inputting a negative number.
Here's a picture of what I'm thinking of, not sure if what I expect in the graph is the same thing as exponents.
I don't have any code, I thought I could base it off something like setInterval, but that would be linear.
You're right SetInterval can only provide with a fixed speed whereas what you need is dynamic speed.
One way is to make two arrays with an equal number of items. With first array named duration and second name speed. The variable Y will be changed by the speed for the duration corresponding to the # of the speed. See here :
var Y = 0; // The variable that is to be changed
var speed = [4.5, 0, -4.5]; // The acceleration in m/s
var time = [2, 4, 2]; // Duration corresponding to each acceleration in s
var sec = 1000; // 1 second = 1000 milliseconds
var i = 0; // A counter to remember the item number in the arrays
var dur = 0; // A variable to hold the duration for which a particular speed has currently lasted. This variable helps to validate the duration for which the Y is changed.
// The function that holds the logic
function tick() {
// Checking if duration is equal to or has crossed the user specified duration for the speed
if(dur >= time[i]*1000){
// If yes, move on to the next speed and duration
i++;
dur = 0;
if(i > speed.length-1){clearInterval(loop);} // If there are no more items in the array stop the interval
}
else {
// If not, continue increasing Y and dur
Y = Math.round(Y*1000 + (speed[i]/50)*1000)/1000 // A simple workaround to avoid the error in arthimetic calculation that occurs while using floats (decimal numbers) in JS.
console.log(Y); // This line is only for debugging purposes. IT CAN BE REMOVED
dur += sec/50;
}
}
var loop = setInterval(tick, sec/50);
I have a data, Time in milliseconds and position(x,y,z) of object to be at that time.
msec |poz_x |poz_y |poz_z
------------------------------
0 |318 |24 |3
25 |318 |24 |3
49 |318 |23 |3
70 |318 |22 |2
91 |318 |22 |2
113 |318 |21 |1
136 |318 |21 |1
e.t.c
The problem is that the time difference between actual and next data vary (coming from sensor).
I'm looking for a way to do the animation in real time.
If in my data I have 60 second of information, it need to animate in browser during 60 second.
I have read that requestAnimationFrame( animate ); will repeat the function 60 time per second, but if my scene is heavy I imagine the frame rate will go down. In any case this can't solve my problem.
I'm looking for a robust solution that doesn't depend on the current framerate of the browser.
Please help.
There are a couple of ways to solve that, with and without libraries.
And you are right, it is not quite as simple as just counting the number of ticks of the animation-loop as there is no guarantee it happens every 1/60 second. But the animation-frame callback (loop in the code below) will get a timestamp passed as first parameter that can be used to calculate animation-progress.
So, in javascript, that could be something like this:
// these are your keyframes, in a format compatible with THREE.Vector3.
// Please note that the time `t` is expected in milliseconds here.
// (must have properties named x, y and z - otherwise the copy below doesn't work)
const keyframes = [
{t: 0, x: 318, y: 24, z: 3},
{t: 25, x: 318, y: 24, z: 3},
// ... and so on
];
// find a pair of keyframes [a, b] such that `a.t < t` and `b.t > t`.
// In other words, find the previous and next keyframe given the
// specific time `t`. If no previous or next keyframes is found, null
// is returned instead.
function findNearestKeyframes(t) {
let prevKeyframe = null;
for (let i = 0; i < keyframes.length; i++) {
if (keyframes[i].t > t) {
return [prevKeyframe, keyframes[i]];
}
prevKeyframe = keyframes[i];
}
return [prevKeyframe, null];
}
const tmpV3 = new THREE.Vector3();
function loop(t) {
const [prevKeyframe, nextKeyframe] = findNearestKeyframes(t);
// (...not handling cases where there is no prev or next here)
// compute the progress of time between the two keyframes
// (0 when t === prevKeyframe.t and 1 when t === nextKeyframe.t)
let progress = (t - prevKeyframe.t) / (nextKeyframe.t - prevKeyframe.t);
// copy position from previous keyframe, and interpolate towards the
// next keyframe linearly
tmpV3.copy(nextKeyframe);
someObject.position
.copy(prevKeyframe)
.lerp(tmpV3, progress);
// (...render scene)
requestAnimationFrame(loop);
}
// start the animation-loop
requestAnimationFrame(loop);
EDIT: To address one question from the comments about optimizing the findNearestKeyframes-function:
Once you get to several thousands of keyframes it might make sense to optimize that a bit, yes. For something like a few hundred it wouldn't be worth the effort (i'd categorize that as premature optimization).
To optimize, you could create an index-table in order to skip over irrelevant sections of the array. For instance, you could store the index in the keyframes-array for the start of every 10 seconds or something like that, that way - when you're searching for keyframes around t = 12.328s, you could start at a higher index based on precomputed information. There are probably a lot of other algorithms and structures that you could use to speed it up.
Animating with jQuery is straightforward. Here's an example of animating a number from 0 to 1000 over 2 seconds:
var $h1 = $('h1'),
startValue = parseInt($h1.text(), 10),
endValue = parseInt($h1.data('end-value'), 10);
$({ int: startValue })
.animate({
int: endValue
}, {
duration: 2 * 1000,
step: function () {
$h1.text(Math.ceil(this.int));
}
});
Working example: http://codepen.io/troywarr/pen/NpjyJE?editors=1010#0
This animation looks nice, but makes it difficult to read the number at any point during the animation, as the next number replaces it within milliseconds. Ideally, I'd like to animate the number over the same length of time but using fewer steps (increasing the length of the update interval), so that it's easier to read the number at a glance as it animates.
It doesn't appear that jQuery offers direct control over the number of steps in the animation, nor the update interval; it only seems to accept a step function that receives an interpolated value.
Is there a way to adjust the number of steps that jQuery uses to interpolate the animation over its duration?
Here is an approach that I used to simulate this, which probably works well enough for everyday purposes. This isn't ideal from a performance standpoint, though, because jQuery still calls the step function the same number of times as it would have originally.
var $h1 = $('h1'),
startValue = parseInt($h1.text(), 10),
endValue = parseInt($h1.data('end-value'), 10),
stepCounter = 0,
showNthStep = 8;
$({ int: startValue })
.animate({
int: endValue
}, {
duration: 2 * 1000,
step: function () {
if (stepCounter++ === showNthStep) {
$h1.text(Math.ceil(this.int));
stepCounter = 0;
}
},
complete: function () {
$h1.text(Math.ceil(this.int));
}
});
Working example: http://codepen.io/troywarr/pen/NpjYWo?editors=1010#0
This uses the variable stepCounter to count the calls to the step function and only update the number every 8th time (per showNthStep).
Note that the complete function is necessary to update the number one final time if the step function isn't called a multiple of 8 times.
Just use in front of your code
jQuery.fx.interval = 1000;
Default is 13 millisecounds
But the disadvantage is a global setting for all JQuery animations.
I have some function:
function calc_degree(a,b,c,cnt) {
if(cnt==0) {
return a;
}
b = b+c;
a = a-b;
return calc_degree(a,b,c,cnt-1);
}
Shortly, it calcs degree of rotation some cicle, which rotation speed increase smoothly. Function returns summary degrees of the rotation. For example:
calc_degree(0,0,1.5,6*1000/time_out);
//a - start angle; b-value of increasing ratoation degree every tick.
//c-increase value; time_out - interval of rotation.
In this example, function returns summary degrees of rotation by 6 seconds.
So, how can I calc the "c" param, if I know the "a" and "cnt"? I need to get the increase value, knowing the summary degrees of rotation and time/tick. If my "a" value is 2790, I need to decrease it every time by "c" value and the last value of "a" must be zero.
Make it a proper recursion, with indices and all:
b[n] = b[n-1] + c => b[n] = b[0] + n*c
a[n] = a[n-1] - b[n] = a[n-1] - b[0] - n*c
results in
a[n] = a[0] - n*b[0] - n*(n+1)/2 * c
This shows you how to get c if a[0]=b[0]=0.
To get a[n]=b[n]=0, you would first need c=-b[0]/n and then c=-a[n]/(n*(n-1)/2). This only works if in the beginning 2*a[0] == (n-1)*b[0].
I cannot comment yet, so I'll add it here. LutzL answer is correct. The problem is that if you add more than one constraint to the problem (in your case requiring both a and b to go to 0) you are reducing the degrees of freedom of the problem. In your case you cannot make a go to zero smoothly (with both a and b 0) if there's not the relation stated by LutzL in the beginning. You can solve it by adding another degree of smoothness (ex: c[n] = c[n-1] + d.) But then you wont be able to make a, b and c tend to 0 without extra constraints.
I'd like to create a random boolean in JavaScript, but I want to take the previous value into account. If the previous value was true, I want it to be more likely for the next value to be true. At the moment I've got this (this is in the context of a closure - goUp and lastGoUp are locals to the containing scope):
function setGoUp() {
goUp = getRandomBoolean();
if(lastGoUp) {
goUp = getRandomBoolean() || goUp;
}
else {
goUp = getRandomBoolean() && goUp;
}
lastGoUp = goUp;
}
So, the algorithm goes:
Get a random boolean
If the random boolean from the previous call was True:
a) get another random boolean, and or these two together
b) else get another random boolean and and these together.
I'm sure this algorithm could be simplified. I wondered about doing:
if(lastGoUp && goUp) {
goUp = goUp * (getRandomBoolean() || goUp);
}
but that seems really dirty.
There's also a problem with this algorithm which means that I can only double the chance of getting the same boolean again - I can't tweak it easily. Any ideas?
You should define the distribution you want, but maybe you are looking for the following?
if (lastGoUp) {
goUp = Math.random() < 0.8;
} else {
goUp = Math.random() < 0.2;
}
Instead of getting a random boolean, get a random number, say between 0 and 99. Keep a threshold value instead of the last number, and adjust the threshold according to the result:
var threshold = 50;
function setGoUp() {
goUp = getRandomNumber() < threshold;
threshold += goUp ? -10 : 10;
}
This would keep a running tab, so if you get consecutive results that are the same, the probability would keep falling for that result.
If you only want to consider the last result, you would instead set the threshold to a specific value:
threshold = goUp ? 40 : 60;
If you only want the probability of the next event to depend on the current value, and not the history of values up til now, what you want is called a Markov process. Often these are implemented with a 2D table of probabilities that you look up (prob of each next outcome given current one), but for a simple bool-valued event, an if statement is sufficient (see meriton's answer; note that it corresponds to a table of probabilities [0.8 0.2; 0.2 0.8]).
If you want something that gets more likely, say, the more successes you get in a row, then you need to devise a sequence of probabilities for success that perhaps approaches, but does not exceed, 1. There are any number of formulas which can do this, depending on how strong you want the bias to become and how quickly you want it to get there.
I would just make the probability of getting value true be an explicit float variable p. Then I could tweak it easily, by increasing p in some way if I got true last time or by doing nothing with it if I got 'false'.
Can replace Math.random for a better randomizer.
var setGoUp = (function(){
var last;
return function(){
// if last 66% chance for true else 50% chance of true.
return !!(last ? Math.random()*3 : Math.random()*2);
}
}());
!! converts anything to a boolean, 0 = false.