UNPKG

@popmotion/popcorn

Version:

Utility functions for animation and interactions.

406 lines (373 loc) 14.9 kB
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 };