Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a function at a changing rate according to a ease function?

I'd like to have the ability to run functions at a certain rate, which can increase or decrease according to a mathematical function such as a curve... much the same way easing functions like easeIn and easeOut work in CSS and JQuery.

Here's a crude illustration of an "easeInOut" type scenario. The line represents time, the os a function call.

o-o--o---o-----o----------o-----o---o--o-o

Implementation could look something like:

trigger(5000, "easeInOut", callback); // Over five seconds, "callback()" is called with an easeInOut ease.

function triggerWithEase(duration, ease, callback){
  // ???
}

function callback(){
  console.log("Heyo!");
}

Is there a Javascript / JQuery solution for this already? If not, how could this be accomplished?

Edit 1:

Some inspiration for the timing graph itself:

http://easings.net/

Edit 2:

I'm impressed with you creative folks who have actually used the little ASCII graph I used in the description as an input to the function! I'm looking for a more mathematical solution to this, though...

Here's a better illustration of what I'm thinking. This assumes we're using a quadratic formula (ax^2 + bx + c) over a duration of 20 seconds.

It'd be really cool to pass a function some parameters and have it trigger callbacks at the correct intervals doing something like this:

10 calls over 20 seconds

enter image description here

20 calls over 20 seconds

enter image description here

Edit 3

Some completely untested, back-of-the-napkin pseudocode I'm playing with:

function easeTrigger(callback, functionType, functionOptions, duration, numberOfCalls){

    switch("functionType"){
        case "quadratic":
            quadtratic(functionOptions);
            break;
        case "sine":
            sine(functionOptions);
            break;
        /* ... */
        default:
            linear(functionOptions);
            break;
    }

    function quadratic(options){

        var x = numberOfCalls;
        var result;
        var delay;
        var timer;

        for (var i = x - 1; i >= 0; i--) {
            result = a * Math.pow(x, 2) + (b * x) + c;
            delay = result * 1000; // using the result to calculate the time between function calls
        }

        // ???

        

        for (var s = numberOfCalls - 1; s >= 0; s--) {
            delay = result * 1000; // probably need to chain this somehow, a for loop might not be the best way to go about this.
            timer = setTimeout(result, caller);
        }

        // ???

        // PROFIT!

    }

    function caller(duration){
        clearTimeout(timer);
        timer = setTimeout(delay, callback);
    }

}

// How this might be implemented...

easeTrigger(callback, "quadratic", {a: 1, b: 2, c:0}, 5000, 20);
easeTrigger(callback, "linear", {a: 1, b: 2}, 5000, 10);

// Callback to call at the end of every period

function callback(){
    console.log("Yo!"); 
}
like image 569
kocytean Avatar asked Sep 22 '17 14:09

kocytean


2 Answers

In the action you should set whatever is that you need to do on that curve tick you have mentioned.

function TimeLine(unit, curve, action) {
  this.unit = unit;
  this.curve = curve;
  this.action = action;
  this.tick = 0;

}
TimeLine.prototype.start = function() {
  var me = this;
  console.log('Start.');
  me.id = setInterval(function() {
    me.onRun();
  }, me.unit);
  me.onRun();
};
TimeLine.prototype.stop = function() {
  var me = this;
  console.log('Stop.');
  clearInterval(me.id);
  delete me.id;
};
TimeLine.prototype.onRun = function() {
  var me = this;

  if (me.curve.charAt(me.tick++) === 'o') {
    me.action && me.action();
  } else {
    console.log('Idle...');
  }
  if (me.tick > me.curve.length) {
    me.stop();
  }
}

var log = function() {
    console.log('Ping:', (new Date).getTime());
  },
  t = new TimeLine(200, 'o----o----o--o-o-o-o-ooo', log);

t.start();

EDIT Related to your last edit, calculating the intervals some function will be called, some corrections:

So you are seeing the axis of time like this:

`t0 -------------- tx --------------------------------------- tN`

and you say that on a time interval duration (=tN-t0) you will call a function a numberOfTimes

so the function will be called in [t0,...,ti,..tx] where x = the numberOfTimes and each of these times is calculated with some function

function quadratic(options){
    var x = numberOfCalls, 
        delay = 0,
        result, , timer;

    for (var i = x - 1; i >= 0; i--) {
        result = a * Math.pow(x, 2) + (b * x) + c;
        delay += result * 1000; // cumulate these time values so you can lunch the call already
        // so you call them in a loop but they will start at the precalculated times
        setTimeout(caller, delay);
    }
}

This will work though I don t see how you will force the duration to be a specific one, unless it is a parameter of the function somehow.

like image 186
bluehipy Avatar answered Oct 06 '22 01:10

bluehipy


If you are able to use an external lib, then I would suggest you use a reactive lib like xstream. This is typically the kind of problems reactive programming addresses!

There is a great method on observables that allow you to emit things according to a diagram (which is exactly what you have). The method is fromDiagram

So in your case, you could do

import fromDiagram from 'xstream/extra/fromDiagram'

const stream = fromDiagram('o-o--o---o-----o----------o-----o---o--o-o', {
  timeUnit: 20 // the number of ms, would have to be computed manually
})

stream.addListener({
  next: callback
})

The only thing left to do is to compute the timeUnit option according to the duration of your animation and the number of steps.

Add to this an object that associate a name of an easing function to a diagram and wrap everything up in a function, and you are good to go :)

Hope it helps.

like image 37
atomrc Avatar answered Oct 05 '22 23:10

atomrc