@popmotion/popcorn
Version:
Utility functions for animation and interactions.
406 lines (373 loc) • 14.9 kB
JavaScript
import { hsla, rgba, hex, color, complex } from 'style-value-types';
import { invariant } from 'hey-listen';
import { getFrameData } from 'framesync';
import { createAnticipateEasing, createBackIn, createExpoIn, cubicBezier, linear, easeIn, easeOut, easeInOut, circIn, circOut, circInOut, backIn, backOut, backInOut, anticipate, reversed, mirrored } from '@popmotion/easing';
export { createAnticipateEasing, createBackIn, createExpoIn, cubicBezier, linear, easeIn, easeOut, easeInOut, circIn, circOut, circInOut, backIn, backOut, backInOut, anticipate, reversed, mirrored } from '@popmotion/easing';
var zeroPoint = {
x: 0,
y: 0,
z: 0
};
var isNum = function (v) { return typeof v === 'number'; };
var radiansToDegrees = (function (radians) { return (radians * 180) / Math.PI; });
var angle = (function (a, b) {
if (b === void 0) { b = zeroPoint; }
return radiansToDegrees(Math.atan2(b.y - a.y, b.x - a.x));
});
var applyOffset = (function (from, to) {
var hasReceivedFrom = true;
if (to === undefined) {
to = from;
hasReceivedFrom = false;
}
return function (v) {
if (hasReceivedFrom) {
return v - from + to;
}
else {
from = v;
hasReceivedFrom = true;
return to;
}
};
});
var curryRange = (function (func) { return function (min, max, v) { return (v !== undefined ? func(min, max, v) : function (cv) { return func(min, max, cv); }); }; });
var clamp = function (min, max, v) {
return Math.min(Math.max(v, min), max);
};
var clamp$1 = curryRange(clamp);
var conditional = (function (check, apply) { return function (v) {
return check(v) ? apply(v) : v;
}; });
var degreesToRadians = (function (degrees) { return (degrees * Math.PI) / 180; });
var isPoint = (function (point) {
return point.hasOwnProperty('x') && point.hasOwnProperty('y');
});
var isPoint3D = (function (point) {
return isPoint(point) && point.hasOwnProperty('z');
});
var distance1D = function (a, b) { return Math.abs(a - b); };
var distance = (function (a, b) {
if (b === void 0) { b = zeroPoint; }
if (isNum(a) && isNum(b)) {
return distance1D(a, b);
}
else if (isPoint(a) && isPoint(b)) {
var xDelta = distance1D(a.x, b.x);
var yDelta = distance1D(a.y, b.y);
var zDelta = isPoint3D(a) && isPoint3D(b) ? distance1D(a.z, b.z) : 0;
return Math.sqrt(Math.pow(xDelta, 2) + Math.pow(yDelta, 2) + Math.pow(zDelta, 2));
}
return 0;
});
var progress = (function (from, to, value) {
var toFromDifference = to - from;
return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
});
var mix = (function (from, to, progress) {
return -progress * from + progress * to + from;
});
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var mixLinearColor = function (from, to, v) {
var fromExpo = from * from;
var toExpo = to * to;
return Math.sqrt(Math.max(0, v * (toExpo - fromExpo) + fromExpo));
};
var colorTypes = [hex, rgba, hsla];
var getColorType = function (v) {
return colorTypes.find(function (type) { return type.test(v); });
};
var notAnimatable = function (color$$1) {
return "'" + color$$1 + "' is not an animatable color. Use the equivalent color code instead.";
};
var mixColor = (function (from, to) {
var fromColorType = getColorType(from);
var toColorType = getColorType(to);
invariant(!!fromColorType, notAnimatable(from));
invariant(!!toColorType, notAnimatable(to));
invariant(fromColorType.transform === toColorType.transform, 'Both colors must be hex/RGBA, OR both must be HSLA.');
var fromColor = fromColorType.parse(from);
var toColor = toColorType.parse(to);
var blended = __assign({}, fromColor);
var mixFunc = fromColorType === hsla ? mix : mixLinearColor;
return function (v) {
for (var key in blended) {
if (key !== 'alpha') {
blended[key] = mixFunc(fromColor[key], toColor[key], v);
}
}
blended.alpha = mix(fromColor.alpha, toColor.alpha, v);
return fromColorType.transform(blended);
};
});
var combineFunctions = function (a, b) { return function (v) { return b(a(v)); }; };
var pipe = (function () {
var transformers = [];
for (var _i = 0; _i < arguments.length; _i++) {
transformers[_i] = arguments[_i];
}
return transformers.reduce(combineFunctions);
});
function getMixer(origin, target) {
if (isNum(origin)) {
return function (v) { return mix(origin, target, v); };
}
else if (color.test(origin)) {
return mixColor(origin, target);
}
else {
return mixComplex(origin, target);
}
}
var mixArray = function (from, to) {
var output = from.slice();
var numValues = output.length;
var blendValue = from.map(function (fromThis, i) { return getMixer(fromThis, to[i]); });
return function (v) {
for (var i = 0; i < numValues; i++) {
output[i] = blendValue[i](v);
}
return output;
};
};
var mixObject = function (origin, target) {
var output = __assign({}, origin, target);
var blendValue = {};
for (var key in output) {
if (origin[key] !== undefined && target[key] !== undefined) {
blendValue[key] = getMixer(origin[key], target[key]);
}
}
return function (v) {
for (var key in blendValue) {
output[key] = blendValue[key](v);
}
return output;
};
};
function analyse(value) {
var parsed = complex.parse(value);
var numValues = parsed.length;
var numNumbers = 0;
var numRGB = 0;
var numHSL = 0;
for (var i = 0; i < numValues; i++) {
if (numNumbers || typeof parsed[i] === 'number') {
numNumbers++;
}
else {
if (parsed[i].hue !== undefined) {
numHSL++;
}
else {
numRGB++;
}
}
}
return { parsed: parsed, numNumbers: numNumbers, numRGB: numRGB, numHSL: numHSL };
}
var mixComplex = function (origin, target) {
var template = complex.createTransformer(target);
var originStats = analyse(origin);
var targetStats = analyse(target);
invariant(originStats.numHSL === targetStats.numHSL &&
originStats.numRGB === targetStats.numRGB &&
originStats.numNumbers >= targetStats.numNumbers, "Complex values '" + origin + "' and '" + target + "' too different to mix. Ensure all colors are of the same type.");
return pipe(mixArray(originStats.parsed, targetStats.parsed), template);
};
var mixNumber = function (from, to) { return function (p) { return mix(from, to, p); }; };
function detectMixerFactory(v) {
if (typeof v === 'number') {
return mixNumber;
}
else if (typeof v === 'string') {
if (color.test(v)) {
return mixColor;
}
else {
return mixComplex;
}
}
else if (Array.isArray(v)) {
return mixArray;
}
else if (typeof v === 'object') {
return mixObject;
}
}
function createMixers(output, ease, customMixer) {
var mixers = [];
var mixerFactory = customMixer || detectMixerFactory(output[0]);
var numMixers = output.length - 1;
for (var i = 0; i < numMixers; i++) {
var mixer = mixerFactory(output[i], output[i + 1]);
if (ease) {
var easingFunction = Array.isArray(ease) ? ease[i] : ease;
mixer = pipe(easingFunction, mixer);
}
mixers.push(mixer);
}
return mixers;
}
function fastInterpolate(_a, _b) {
var from = _a[0], to = _a[1];
var mixer = _b[0];
return function (v) { return mixer(progress(from, to, v)); };
}
function slowInterpolate(input, mixers) {
var inputLength = input.length;
var lastInputIndex = inputLength - 1;
return function (v) {
var mixerIndex = 0;
var foundMixerIndex = false;
if (v <= input[0]) {
foundMixerIndex = true;
}
else if (v >= input[lastInputIndex]) {
mixerIndex = lastInputIndex - 1;
foundMixerIndex = true;
}
if (!foundMixerIndex) {
var i = 1;
for (; i < inputLength; i++) {
if (input[i] > v || i === lastInputIndex) {
break;
}
}
mixerIndex = i - 1;
}
var progressInRange = progress(input[mixerIndex], input[mixerIndex + 1], v);
return mixers[mixerIndex](progressInRange);
};
}
function interpolate(input, output, _a) {
var _b = _a === void 0 ? {} : _a, _c = _b.clamp, clamp = _c === void 0 ? true : _c, ease = _b.ease, mixer = _b.mixer;
var inputLength = input.length;
invariant(inputLength === output.length, 'Both input and output ranges must be the same length');
invariant(!ease || !Array.isArray(ease) || ease.length === inputLength - 1, 'Array of easing functions must be of length `input.length - 1`, as it applies to the transitions **between** the defined values.');
if (input[0] > input[inputLength - 1]) {
input = [].concat(input);
output = [].concat(output);
input.reverse();
output.reverse();
}
var mixers = createMixers(output, ease, mixer);
var interpolator = inputLength === 2
? fastInterpolate(input, mixers)
: slowInterpolate(input, mixers);
return clamp
? pipe(clamp$1(input[0], input[inputLength - 1]), interpolator)
: interpolator;
}
var pointFromVector = (function (origin, angle, distance) {
angle = degreesToRadians(angle);
return {
x: distance * Math.cos(angle) + origin.x,
y: distance * Math.sin(angle) + origin.y
};
});
var toDecimal = (function (num, precision) {
if (precision === void 0) { precision = 2; }
precision = Math.pow(10, precision);
return Math.round(num * precision) / precision;
});
var smoothFrame = (function (prevValue, nextValue, duration, smoothing) {
if (smoothing === void 0) { smoothing = 0; }
return toDecimal(prevValue +
(duration * (nextValue - prevValue)) / Math.max(smoothing, duration));
});
var smooth = (function (strength) {
if (strength === void 0) { strength = 50; }
var previousValue = 0;
var lastUpdated = 0;
return function (v) {
var currentFramestamp = getFrameData().timestamp;
var timeDelta = currentFramestamp !== lastUpdated ? currentFramestamp - lastUpdated : 0;
var newValue = timeDelta
? smoothFrame(previousValue, v, timeDelta, strength)
: previousValue;
lastUpdated = currentFramestamp;
previousValue = newValue;
return newValue;
};
});
var snap = (function (points) {
if (typeof points === 'number') {
return function (v) { return Math.round(v / points) * points; };
}
else {
var i_1 = 0;
var numPoints_1 = points.length;
return function (v) {
var lastDistance = Math.abs(points[0] - v);
for (i_1 = 1; i_1 < numPoints_1; i_1++) {
var point = points[i_1];
var distance = Math.abs(point - v);
if (distance === 0)
return point;
if (distance > lastDistance)
return points[i_1 - 1];
if (i_1 === numPoints_1 - 1)
return point;
lastDistance = distance;
}
};
}
});
var identity = function (v) { return v; };
var springForce = function (alterDisplacement) {
if (alterDisplacement === void 0) { alterDisplacement = identity; }
return curryRange(function (constant, origin, v) {
var displacement = origin - v;
var springModifiedDisplacement = -(0 - constant + 1) * (0 - alterDisplacement(Math.abs(displacement)));
return displacement <= 0
? origin + springModifiedDisplacement
: origin - springModifiedDisplacement;
});
};
var springForceLinear = springForce();
var springForceExpo = springForce(Math.sqrt);
var velocityPerFrame = (function (xps, frameDuration) {
return isNum(xps) ? xps / (1000 / frameDuration) : 0;
});
var velocityPerSecond = (function (velocity, frameDuration) {
return frameDuration ? velocity * (1000 / frameDuration) : 0;
});
var wrap = function (min, max, v) {
var rangeSize = max - min;
return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
};
var wrap$1 = curryRange(wrap);
var clampProgress = clamp$1(0, 1);
var steps = (function (steps, direction) {
if (direction === void 0) { direction = 'end'; }
return function (progress) {
progress =
direction === 'end' ? Math.min(progress, 0.999) : Math.max(progress, 0.001);
var expanded = progress * steps;
var rounded = direction === 'end' ? Math.floor(expanded) : Math.ceil(expanded);
return clampProgress(rounded / steps);
};
});
export { angle, applyOffset, clamp$1 as clamp, conditional, degreesToRadians, distance, interpolate, isPoint, isPoint3D, mix, mixArray, mixColor, mixComplex, mixObject, pipe, pointFromVector, progress, radiansToDegrees, smooth, smoothFrame, snap, springForce, springForceExpo, springForceLinear, steps, toDecimal, velocityPerFrame, velocityPerSecond, wrap$1 as wrap };