bezier-easing

Bezier Curve based easing functions for Javascript animations

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greatest.deepsurf.us/scripts/7108/29098/bezier-easing.js

  1. // ==UserScript==
  2. // @name bezier-easing
  3. // @version 0.4.4
  4. // @description Bezier Curve based easing functions for Javascript animations
  5. // @license MIT (https://github.com/gre/bezier-easing/blob/master/LICENSE)
  6. // ==/UserScript==
  7.  
  8. /**
  9. * BezierEasing - use bezier curve for transition easing function
  10. * by Gaëtan Renaudeau 2014 – MIT License
  11. *
  12. * Credits: is based on Firefox's nsSMILKeySpline.cpp
  13. * Usage:
  14. * var spline = BezierEasing(0.25, 0.1, 0.25, 1.0)
  15. * spline(x) => returns the easing value | x must be in [0, 1] range
  16. *
  17. */
  18. (function (definition) {
  19. if (typeof exports === "object") {
  20. module.exports = definition();
  21. } else if (typeof define === 'function' && define.amd) {
  22. define([], definition);
  23. } else {
  24. window.BezierEasing = definition();
  25. }
  26. }(function () {
  27. var global = this;
  28.  
  29. // These values are established by empiricism with tests (tradeoff: performance VS precision)
  30. var NEWTON_ITERATIONS = 4;
  31. var NEWTON_MIN_SLOPE = 0.001;
  32. var SUBDIVISION_PRECISION = 0.0000001;
  33. var SUBDIVISION_MAX_ITERATIONS = 10;
  34.  
  35. var kSplineTableSize = 11;
  36. var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
  37.  
  38. var float32ArraySupported = 'Float32Array' in global;
  39.  
  40. function BezierEasing (mX1, mY1, mX2, mY2) {
  41. // Validate arguments
  42. if (arguments.length !== 4) {
  43. throw new Error("BezierEasing requires 4 arguments.");
  44. }
  45. for (var i=0; i<4; ++i) {
  46. if (typeof arguments[i] !== "number" || isNaN(arguments[i]) || !isFinite(arguments[i])) {
  47. throw new Error("BezierEasing arguments should be integers.");
  48. }
  49. }
  50. if (mX1 < 0 || mX1 > 1 || mX2 < 0 || mX2 > 1) {
  51. throw new Error("BezierEasing x values must be in [0, 1] range.");
  52. }
  53.  
  54. var mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);
  55. function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
  56. function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
  57. function C (aA1) { return 3.0 * aA1; }
  58. // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
  59. function calcBezier (aT, aA1, aA2) {
  60. return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
  61. }
  62. // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
  63. function getSlope (aT, aA1, aA2) {
  64. return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
  65. }
  66.  
  67. function newtonRaphsonIterate (aX, aGuessT) {
  68. for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
  69. var currentSlope = getSlope(aGuessT, mX1, mX2);
  70. if (currentSlope === 0.0) return aGuessT;
  71. var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
  72. aGuessT -= currentX / currentSlope;
  73. }
  74. return aGuessT;
  75. }
  76.  
  77. function calcSampleValues () {
  78. for (var i = 0; i < kSplineTableSize; ++i) {
  79. mSampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
  80. }
  81. }
  82.  
  83. function binarySubdivide (aX, aA, aB) {
  84. var currentX, currentT, i = 0;
  85. do {
  86. currentT = aA + (aB - aA) / 2.0;
  87. currentX = calcBezier(currentT, mX1, mX2) - aX;
  88. if (currentX > 0.0) {
  89. aB = currentT;
  90. } else {
  91. aA = currentT;
  92. }
  93. } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
  94. return currentT;
  95. }
  96.  
  97. function getTForX (aX) {
  98. var intervalStart = 0.0;
  99. var currentSample = 1;
  100. var lastSample = kSplineTableSize - 1;
  101.  
  102. for (; currentSample != lastSample && mSampleValues[currentSample] <= aX; ++currentSample) {
  103. intervalStart += kSampleStepSize;
  104. }
  105. --currentSample;
  106.  
  107. // Interpolate to provide an initial guess for t
  108. var dist = (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample+1] - mSampleValues[currentSample]);
  109. var guessForT = intervalStart + dist * kSampleStepSize;
  110.  
  111. var initialSlope = getSlope(guessForT, mX1, mX2);
  112. if (initialSlope >= NEWTON_MIN_SLOPE) {
  113. return newtonRaphsonIterate(aX, guessForT);
  114. } else if (initialSlope === 0.0) {
  115. return guessForT;
  116. } else {
  117. return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize);
  118. }
  119. }
  120.  
  121. var _precomputed = false;
  122. function precompute() {
  123. _precomputed = true;
  124. if (mX1 != mY1 || mX2 != mY2)
  125. calcSampleValues();
  126. }
  127.  
  128. var f = function (aX) {
  129. if (!_precomputed) precompute();
  130. if (mX1 === mY1 && mX2 === mY2) return aX; // linear
  131. // Because JavaScript number are imprecise, we should guarantee the extremes are right.
  132. if (aX === 0) return 0;
  133. if (aX === 1) return 1;
  134. return calcBezier(getTForX(aX), mY1, mY2);
  135. };
  136.  
  137. f.getControlPoints = function() { return [{ x: mX1, y: mY1 }, { x: mX2, y: mY2 }]; };
  138.  
  139. var args = [mX1, mY1, mX2, mY2];
  140. var str = "BezierEasing("+args+")";
  141. f.toString = function () { return str; };
  142.  
  143. var css = "cubic-bezier("+args+")";
  144. f.toCSS = function () { return css; };
  145.  
  146. return f;
  147. }
  148.  
  149. // CSS mapping
  150. BezierEasing.css = {
  151. "ease": BezierEasing(0.25, 0.1, 0.25, 1.0),
  152. "linear": BezierEasing(0.00, 0.0, 1.00, 1.0),
  153. "ease-in": BezierEasing(0.42, 0.0, 1.00, 1.0),
  154. "ease-out": BezierEasing(0.00, 0.0, 0.58, 1.0),
  155. "ease-in-out": BezierEasing(0.42, 0.0, 0.58, 1.0)
  156. };
  157.  
  158. return BezierEasing;
  159.  
  160. }));
  161.