根据简易功能以变化的速率调用功能?

时间:2017-09-22 14:20:12

标签: javascript jquery

我希望能够以一定的速率运行函数,根据数学函数(如曲线)可以增加或减少函数......就像放宽easeIn和{easeOut等函数一样。 {1}}在CSS和JQuery中工作。

以下是“easeInOut”类型场景的粗略说明。该行代表时间,o是函数调用。

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

实施可能类似于:

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

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

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

是否有针对此的Javascript / JQuery解决方案?如果没有,这怎么可能实现呢?

编辑1:

时序图本身的一些灵感:

http://easings.net/

编辑2:

对于那些实际使用我在描述中使用的小ASCII图作为功能输入的创意人,我印象深刻!我正在寻找一个更加数学解决方案,但是......

这是我正在思考的更好的例子。假设我们在20秒的持续时间内使用二次公式(ax ^ 2 + bx + c)。

传递函数一些参数并让它以正确的时间间隔触发回调真是太酷了这样做:

超过20秒的10次呼叫

enter image description here

超过20秒的20次呼叫

enter image description here

编辑3

我正在玩的一些完全未经测试的,背面的伪造的伪代码:

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!"); 
}

4 个答案:

答案 0 :(得分:3)

action中,你应该在你提到的曲线上设置你需要做的任何事情。



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();




修改 与上次编辑相关,计算一些函数将被调用的区间,一些更正:

所以你看到的时间轴是这样的:

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

并且您说在时间间隔duration(= tN-t0)上,您将调用函数numberOfTimes

因此函数将在[t0,...,ti,.. tx]中调用,其中x = numberOfTimes,并且每个时间都是用某个函数计算的

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);
    }
}

虽然我不知道如何将持续时间强制为特定的持续时间,但这将有效。除非它以某种方式作为函数的参数。

答案 1 :(得分:1)

如果您能够使用外部库,那么我建议您使用像xstream这样的反应式库。这通常是反应式编程解决的问题!

在observables上有一个很好的方法,允许你根据图表发出的东西(这正是你所拥有的)。方法是fromDiagram

所以在你的情况下,你可以做到

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
})

唯一要做的就是根据动画的持续时间和步数计算timeUnit选项。

添加一个对象,它将一个缓动函数的名称与一个图表相关联,并将所有内容包装在一个函数中,你可以去:)

希望它有所帮助。

答案 2 :(得分:1)

假设

  • 保持函数名称和签名与给定示例中的名称和签名相同
  • 没有外部图书馆
  • 所有缓动函数都具有相同的签名f(current time, start value, change in value, duration)
  • 调用率准确度可在几毫秒内波动

Google很快回答了Flash时代的宽松方程式。我从Robert Penner借了一些。

const ease = {
  inQuad: (t, b, c, d) => {
    t /= d
    return c * t * t + b
  },
  outQuad: (t, b, c, d) => {
    t /= d
    return -c * t * (t - 2) + b
  }
}

let triggerWithEase = (duration, ease, callback) => {
  const rate = 12 // Lower value makes easing more distinguishable to naked eye
  const totalFrames = Math.floor((duration / 1000) * rate)

  for (let currentFrame = 0; currentFrame < totalFrames; currentFrame += 1) {
    const delay = ease(currentFrame, 0, duration, totalFrames)
    setTimeout(callback, delay)
  }
}

triggerWithEase(
  10000,
  ease.outQuad,
  () => console.log(Date.now())
)

修改:刚刚意识到我是另一个Why Can't Programmers Read示例。进行了编辑以纠正此问题。

答案 3 :(得分:1)

我喜欢这个问题以及它是如何呈现的。所以只是为了好玩,我试着以同样的方式回答。

我从here获得了一堆缓动公式,并将其应用于此案例。

这很简单。我设置easing对象来保存所有公式。所有单选按钮'"change"事件侦听器都分配有drawScale函数。一旦"change"事件被触发,它就会查找相应的函数并执行它的显示。代码被评论,希望更容易理解。

最好以全屏模式观看它。

function drawScale(e) {

  // inserting span elements at appropriate time
  function insertSpan() {
    window.requestAnimationFrame(function() {
      se = document.createElement("span");
      se.textContent = ">";
      se.classList.add("tick");
      scale.appendChild(se);
    });
  }

  // recursively registers functions at the event looop according to their corresping delay
  // returns setTimeout ids array (stoids) to be cleared if invoked before previous finishes
  function registerSetTimeOut(t, ...ts) {
    return ts.length ? registerSetTimeOut(...ts).concat(setTimeout(insertSpan, t)) :
      [setTimeout(insertSpan, t)];
  }

  var datar = base.map((v, i) => easing[e.currentTarget.value](v, 0, 5000, 5000));
  stoids.forEach(stoid => clearTimeout(stoid)); // clear all awaiting setTimeouts if any
  stoids = []; // resets setTimeout ids array
  stoids = registerSetTimeOut(...datar); // stoids array gets populated with new setTimeout ids

  // clears previously appended span elements at appropriate time
  window.requestAnimationFrame(function() {
    while (scale.firstChild) scale.removeChild(scale.firstChild);
  });
}

var easing = {
    "linear": (t, b, c, d) => c * t / d + b,
    "inQuad": (t, b, c, d) => (t /= d, c * t * t + b),
    "outQuad": (t, b, c, d) => (t /= d, -c * t * (t - 2) + b),
    "inOutQuad": (t, b, c, d) => (t /= d / 2, t < 1 ? c / 2 * t * t + b : (t--, -c / 2 * (t * (t - 2) - 1) + b)),
    "inCubic": (t, b, c, d) => (t /= d, c * t * t * t + b),
    "outCubic": (t, b, c, d) => (t /= d, t--, c * (t * t * t + 1) + b),
    "inOutCubic": (t, b, c, d) => (t /= d / 2, t < 1 ? c / 2 * t * t * t + b : (t -= 2, c / 2 * (t * t * t + 2) + b)),
    "inQuart": (t, b, c, d) => (t /= d, c * t * t * t * t + b),
    "outQuart": (t, b, c, d) => (t /= d, t--, -c * (t * t * t * t - 1) + b),
    "inOutQuart": (t, b, c, d) => (t /= d / 2, t < 1 ? c / 2 * t * t * t * t + b : (t -= 2, -c / 2 * (t * t * t * t - 2) + b)),
    "inQuint": (t, b, c, d) => (t /= d, c * t * t * t * t * t + b),
    "outQuint": (t, b, c, d) => (t /= d, t--, c * (t * t * t * t * t + 1) + b),
    "inOutQuint": (t, b, c, d) => (t /= d / 2, t < 1 ? c / 2 * t * t * t * t * t + b : (t -= 2, c / 2 * (t * t * t * t * t + 2) + b)),
    "inSine": (t, b, c, d) => -c * Math.cos(t / d * (Math.PI / 2)) + c + b,
    "outSine": (t, b, c, d) => c * Math.sin(t / d * (Math.PI / 2)) + b,
    "inOutSine": (t, b, c, d) => -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b,
    "inExpo": (t, b, c, d) => c * Math.pow(2, 10 * (t / d - 1)) + b,
    "outExpo": (t, b, c, d) => c * (-Math.pow(2, -10 * t / d) + 1) + b,
    "inOutExpo": (t, b, c, d) => (t /= d / 2, t < 1 ? c / 2 * Math.pow(2, 10 * (t - 1)) + b : (t--, c / 2 * (-Math.pow(2, -10 * t) + 2) + b)),
    "inCirc": (t, b, c, d) => (t /= d, -c * (Math.sqrt(1 - t * t) - 1) + b),
    "outCirc": (t, b, c, d) => (t /= d, t--, c * Math.sqrt(1 - t * t) + b),
    "inOutCirc": (t, b, c, d) => (t /= d / 2, t < 1 ? -c / 2 * (Math.sqrt(1 - t * t) - 1) + b : (t -= 2, c / 2 * (Math.sqrt(1 - t * t) + 1) + b))
  },
  base = Array.from({
    length: 21
  }).map((_, i) => i * 250),
  stoids = [],
  scale = document.getElementById("scale"),
  radios = document.querySelectorAll('td [type="radio"]');

radios.forEach(r => r.addEventListener("change", drawScale));
table {
  width: 33.3vw;
  font-size: 0.8vw;
  margin: auto;
}

#scale {
  margin-left: 33vw;
  display: inline-block;
  border: solid 1px black;
  overflow: hidden;
}

th {
  vertical-align: top;
  padding: 0.5em;
  border: solid black 1px;
  border-radius: 0.5em;
  background-color: #d7c0c0;
}

tr:nth-child(2n+1){
  background-color: #ccc;
}

td:nth-child(1) {
  text-align: right;
  font-weight: bold;
  padding: 0 0.5em;
}

td:nth-child(n+2) {
  text-align: center;
  width: 25%;
}

.desc {
  display: block;
  font-family: monospace;
  font-size: 0.8em;
}

.tick {
  float:left;
  width: 1.57vw;
  text-align: center;
  font-weight: bold;
  background-color: #d7c0c0;
}
<div id="container">
  <table>
    <tr>
      <th>Math</th>
      <th>Ease In<span class="desc">Accelerating from zero velocity</span></th>
      <th>Ease Out<span class="desc">Decelerating to zero velocity</span></th>
      <th>Ease In/Out<span class="desc">Acceleration until halfway, then deceleration</span></th>
    </tr>
    <tr>
      <td>Linear</td>
      <td colspan=3><input type="radio" name="math" value="linear"></td>
    </tr>
    <tr>
      <td>Quadratic</td>
      <td><input type="radio" name="math" value="inQuad"></td>
      <td><input type="radio" name="math" value="outQuad"></td>
      <td><input type="radio" name="math" value="inOutQuad"></td>
    </tr>
    <tr>
      <td>Cubic</td>
      <td><input type="radio" name="math" value="inCubic"></td>
      <td><input type="radio" name="math" value="outCubic"></td>
      <td><input type="radio" name="math" value="inOutCubic"></td>
    </tr>
    <tr>
      <td>Quartic</td>
      <td><input type="radio" name="math" value="inQuart"></td>
      <td><input type="radio" name="math" value="outQuart"></td>
      <td><input type="radio" name="math" value="inOutQuart"></td>
    </tr>
    <tr>
      <td>Quintic</td>
      <td><input type="radio" name="math" value="inQuint"></td>
      <td><input type="radio" name="math" value="outQuint"></td>
      <td><input type="radio" name="math" value="inOutQuint"></td>
    </tr>
    <tr>
      <td>Sinusoidal</td>
      <td><input type="radio" name="math" value="inSine"></td>
      <td><input type="radio" name="math" value="outSine"></td>
      <td><input type="radio" name="math" value="inOutSine"></td>
    </tr>
    <tr>
      <td>Exponential</td>
      <td><input type="radio" name="math" value="inExpo"></td>
      <td><input type="radio" name="math" value="outExpo"></td>
      <td><input type="radio" name="math" value="inOutExpo"></td>
    </tr>
    <tr>
      <td>Circular</td>
      <td><input type="radio" name="math" value="inCirc"></td>
      <td><input type="radio" name="math" value="outCirc"></td>
      <td><input type="radio" name="math" value="inOutCirc"></td>
    </tr>
  </table>
  <div id="scale"></div>
</div>