Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/okikio/postcss-spring-easing. PostCSS plugin to add that bounce and boogie that only springs can bring
easings linear postcss postcss-plugin spring spring-easing
Something went wrong. Try again.
19 kB · 509 lines
at commit 23712df9
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510"use strict";Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });function isNumberLike(num) { const value = parseFloat(num); return typeof value == "number" && !Number.isNaN(value);}function limit(value, min, max) { return Math.min(Math.max(value, min), max);}function scale(t, start, end) { return start + (end - start) * t;}function toFixed(value, decimal) { return Math.round(value * 10 ** decimal) / 10 ** decimal;}function getUnit(str) { const num = parseFloat(str); return str.toString().replace(num.toString(), "");}function batchInterpolateNumber(arr_t, values, decimal = 3) { const n = values.length - 1; const results = []; let t = 0; let t_index = 0; let len = arr_t.length; for (; t_index < len; t_index++) { t = arr_t[t_index]; const i = limit(Math.floor(t * n), 0, n - 1); const start = values[i]; const end = values[i + 1]; const progress = (t - i / n) * n; results.push(toFixed(scale(progress, start, end), decimal)); } return results;}function batchInterpolateSequence(arr_t, values) { const n = values.length - 1; const results = []; let t = 0; let t_index = 0; let len = arr_t.length; for (; t_index < len; t_index++) { t = limit(arr_t[t_index], 0, 1); const i = Math.round(t * n); results.push(values[i]); } return results;}const batchInterpolateUsingIndex = batchInterpolateSequence;function batchInterpolateString(arr_t, values, decimal = 3) { let units = ""; if (isNumberLike(values[0])) units = getUnit(values[0]); return batchInterpolateNumber( arr_t, values.map((v) => typeof v === "number" ? v : parseFloat(v)), decimal ).map((value) => value + units);}function batchInterpolateComplex(arr_t, values, decimal = 3) { let isNumber = true; let isLikeNumber = true; let i = 0; let v; const len = values.length; for (; i < len; i++) { v = values[i]; if (isNumber) isNumber = typeof v === "number"; if (isLikeNumber) isLikeNumber = isNumberLike(v); } if (isNumber) return batchInterpolateNumber(arr_t, values, decimal); if (isLikeNumber) return batchInterpolateString(arr_t, values, decimal); return batchInterpolateSequence(arr_t, values);}function BatchSpringEasing(values, options = {}, customInterpolate = batchInterpolateComplex) { const optionsObj = EasingOptions(options); const [frames, duration] = GenerateSpringFrames(optionsObj); return [ customInterpolate == null ? void 0 : customInterpolate(frames, values, optionsObj.decimal), duration ];}function toAnimationFrames(customInterpolate) { return (arr_t, values, decimal) => { return arr_t.map((t) => customInterpolate(t, values, decimal)); };}/*! * Based off of https://github.com/jakearchibald/linear-easing-generator * * Changes: * - Added comments and docs top explain logic * - Switched to iterative approach for the `ramerDouglasPeucker` algorithim * - Renamed functions, parameters and variables to improve readability and to better match a library usecase * * Copyright 2023 Jake Archibald [@jakearchibald](https://github.com/jakearchibald) * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */function squaredSegmentDistance(point, lineStart, lineEnd) { let [x, y] = lineStart; let dx = lineEnd[0] - x; let dy = lineEnd[1] - y; if (dx !== 0 || dy !== 0) { let t = ((point[0] - x) * dx + (point[1] - y) * dy) / (dx * dx + dy * dy); if (t > 1) { x = lineEnd[0]; y = lineEnd[1]; } else if (t > 0) { x += dx * t; y += dy * t; } } dx = point[0] - x; dy = point[1] - y; return dx * dx + dy * dy;}function ramerDouglasPeucker(points, tolerance) { const sqTolerance = tolerance * tolerance; if (points.length < 3) return points; let result = [points[0]]; let stack = [[0, points.length - 1]]; while (stack.length > 0) { let [start, end] = stack.pop(); let maxSqDist = 0; let index = 0; for (let i = start + 1; i < end; i++) { const sqDist = squaredSegmentDistance(points[i], points[start], points[end]); if (sqDist > maxSqDist) { index = i; maxSqDist = sqDist; } } if (maxSqDist > sqTolerance) { stack.push([start, index]); stack.push([index, end]); } else { result.push(points[end]); } } return result.sort((a, b) => a[0] - b[0]);}function getOptimizedPoints(fullPoints, simplify, round) { if (!fullPoints) return null; const xRounding = Math.max(round, 2); return ramerDouglasPeucker(fullPoints, simplify).map( ([x, y]) => [ toFixed(x, xRounding), // Round x values toFixed(y, round) // Round y values ] );}/*! * Based off of https://github.com/jakearchibald/linear-easing-generator * * Changes: * - Added comments and docs top explain logic * - Switched to iterative approach for the `ramerDouglasPeucker` algorithim * - Renamed functions, parameters and variables to improve readability and to better match a library usecase * * Copyright 2023 Jake Archibald [@jakearchibald](https://github.com/jakearchibald) * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */function getLinearSyntax(points, round) { if (!points) return []; const xFormat = new Intl.NumberFormat("en-US", { maximumFractionDigits: Math.max(round - 2, 0) }); const yFormat = new Intl.NumberFormat("en-US", { maximumFractionDigits: round }); const pointsValue = points; const valuesWithRedundantX = /* @__PURE__ */ new Set(); const maxDelta = 1 / 10 ** round; for (const [i, value] of pointsValue.entries()) { const [x] = value; if (i === 0) { if (x === 0) valuesWithRedundantX.add(value); continue; } if (i === pointsValue.length - 1) { const previous2 = pointsValue[i - 1][0]; if (x === 1 && previous2 <= 1) valuesWithRedundantX.add(value); continue; } const previous = pointsValue[i - 1][0]; const next = pointsValue[i + 1][0]; const averagePos = (next - previous) / 2 + previous; const delta = Math.abs(x - averagePos); if (delta < maxDelta) valuesWithRedundantX.add(value); } const groupedValues = [[pointsValue[0]]]; for (const value of pointsValue.slice(1)) { if (value[1] === groupedValues.at(-1)[0][1]) { groupedValues.at(-1).push(value); } else { groupedValues.push([value]); } } const outputValues = groupedValues.map((group) => { const yValue = yFormat.format(group[0][1]); const regularValue = group.map((value) => { const [x] = value; let output = yValue; if (!valuesWithRedundantX.has(value)) { output += " " + xFormat.format(x * 100) + "%"; } return output; }).join(", "); if (group.length === 1) return regularValue; const xVals = [group[0][0], group.at(-1)[0]]; const positionalValues = xVals.map((x) => xFormat.format(x * 100) + "%").join(" "); const skipValue = `${yValue} ${positionalValues}`; return skipValue.length > regularValue.length ? regularValue : skipValue; }); return outputValues;}function CSSSpringEasing(options = {}) { const optionsObj = EasingOptions(options); const [frames, duration] = GenerateSpringFrames(optionsObj); const quality = limit(optionsObj.quality ?? 0.85, 0, 1); const simplify = scale(1 - quality, 0, 0.025); const len = frames.length; const pts = frames.map((x, i) => [i / (len - 1), x]); const optimizedPoints = getOptimizedPoints(pts, simplify, optionsObj.decimal); return [ getLinearSyntax(optimizedPoints, optionsObj.decimal).join(", "), duration ];}/*! * Spring solver inspired by Webkit Copyright © 2016 Apple Inc. All rights reserved. https://webkit.org/demos/spring/spring.js * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE.*/const SpringFrame = (t, [mass = 1, stiffness = 100, damping = 10, velocity = 0] = [], duration) => { mass = limit(mass, 1e-4, 1e3); stiffness = limit(stiffness, 1e-4, 1e3); damping = limit(damping, 1e-4, 1e3); velocity = limit(velocity, 1e-4, 1e3); const w0 = Math.sqrt(stiffness / mass); const zeta = damping / (2 * Math.sqrt(stiffness * mass)); const wd = zeta < 1 ? w0 * Math.sqrt(1 - zeta * zeta) : 0; const b = zeta < 1 ? (zeta * w0 + -velocity) / wd : -velocity + w0; let position = duration ? duration * t / 1e3 : t; if (zeta < 1) { position = Math.exp(-position * zeta * w0) * (Math.cos(wd * position) + b * Math.sin(wd * position)); } else { position = (1 + b * position) * Math.exp(-position * w0); } return 1 - position;};const EasingDurationCache = /* @__PURE__ */ new Map();const INFINITE_LOOP_LIMIT = 1e5;function getSpringDuration([mass, stiffness, damping, velocity] = []) { let params = [mass, stiffness, damping, velocity]; let easing = `${params}`; if (EasingDurationCache.has(easing)) return EasingDurationCache.get(easing); const step = 1 / 6; let time = 0; let numPoints = 0; while (++numPoints < INFINITE_LOOP_LIMIT) { if (Math.abs(1 - SpringFrame(time, params)) < 1e-3) { let restStart = time; let restSteps = 1; while (++numPoints < INFINITE_LOOP_LIMIT) { time += step; if (Math.abs(1 - SpringFrame(time, params)) >= 1e-3) break; restSteps++; if (restSteps === 16) { const duration2 = restStart * 1e3; EasingDurationCache.set(easing, [duration2, numPoints]); return [duration2, numPoints]; } } } time += step; } const duration = time * 1e3; EasingDurationCache.set(easing, [duration, numPoints]); return [duration, numPoints];}function EaseOut(frame) { return (t, params = [], duration) => 1 - frame(1 - t, params, duration);}function EaseInOut(frame) { return function(t, params = [], duration) { return t < 0.5 ? frame(t * 2, params, duration) / 2 : 1 - frame(t * -2 + 2, params, duration) / 2; };}function EaseOutIn(frame) { return function(t, params = [], duration) { return t < 0.5 ? (1 - frame(1 - t * 2, params, duration)) / 2 : (frame(t * 2 - 1, params, duration) + 1) / 2; };}const SpringInFrame = SpringFrame;const SpringOutFrame = EaseOut(SpringFrame);const SpringInOutFrame = EaseInOut(SpringFrame);const SpringOutInFrame = EaseOutIn(SpringFrame);function interpolateNumber(t, values, decimal = 3) { const n = values.length - 1; const i = limit(Math.floor(t * n), 0, n - 1); const start = values[i]; const end = values[i + 1]; const progress = (t - i / n) * n; return toFixed(scale(progress, start, end), decimal);}function interpolateSequence(t, values) { const n = values.length - 1; t = limit(t, 0, 1); const i = Math.round(t * n); return values[i];}const interpolateUsingIndex = interpolateSequence;function interpolateString(t, values, decimal = 3) { let units = ""; if (isNumberLike(values[0])) units = getUnit(values[0]); return interpolateNumber( t, values.map((v) => typeof v === "number" ? v : parseFloat(v)), decimal ) + units;}function interpolateComplex(t, values, decimal = 3) { const isNumber = values.every((v) => typeof v === "number"); if (isNumber) return interpolateNumber(t, values, decimal); const isLikeNumber = values.every((v) => isNumberLike(v)); if (isLikeNumber) return interpolateString(t, values, decimal); return interpolateSequence(t, values);}exports.EasingFunctions = { spring: SpringFrame, "spring-in": SpringInFrame, "spring-out": SpringOutFrame, "spring-in-out": SpringInOutFrame, "spring-out-in": SpringOutInFrame};exports.EasingFunctionKeys = Object.keys(exports.EasingFunctions);function registerEasingFunction(key, fn) { exports.EasingFunctions = { ...exports.EasingFunctions, [key]: fn }; exports.EasingFunctionKeys = Object.keys(exports.EasingFunctions);}function registerEasingFunctions(obj) { exports.EasingFunctions = { ...exports.EasingFunctions, ...obj }; exports.EasingFunctionKeys = Object.keys(exports.EasingFunctions);}function parseEasingParameters(str) { const match = /(\(|\s)([^)]+)\)?/.exec(str.toString()); return match ? match[2].split(",").map((value) => { let num = parseFloat(value); return !Number.isNaN(num) ? num : value.trim(); }) : [];}function EasingOptions(options = {}) { const isEasing = typeof options === "string" || Array.isArray(options) && typeof options[0] === "function"; let { easing = [SpringFrame, 1, 100, 10, 0], numPoints = 38, decimal = 3, ...rest } = isEasing ? { easing: options } : options; if (typeof easing === "string") { const frameFunction = exports.EasingFunctions[easing.replace(/(\(|\s).+/, "").toLowerCase().trim()]; const params = parseEasingParameters(easing); easing = [frameFunction, ...params]; } return { easing, numPoints, decimal, ...rest };}const FramePtsCache = /* @__PURE__ */ new Map();function GenerateSpringFrames(options = {}) { let { easing, numPoints } = EasingOptions(options); if (Array.isArray(easing)) { if (typeof easing[0] != "function") { throw new Error( "[spring-easing] A frame function is required as the first element in the easing array, e.g. [SpringFrame, ...]" ); } } else { throw new Error( `[spring-easing] The easing needs to be in the format: * "spring-out(mass, stiffness, damping, velocity)" or * [SpringOutFrame, mass, stiffness, damping, velocity], the easing recieved is "${easing}", [spring-easing] doesn't really know what to do with that.` ); } let [frameFunction, ...params] = easing; const [idealDuration, idealNumPoints = 38] = getSpringDuration(params); if (!numPoints) numPoints = idealNumPoints; const key = `${params},${numPoints}`; if (FramePtsCache.has(key)) { let tempObj2 = FramePtsCache.get(key); if (tempObj2.has(frameFunction)) return tempObj2.get(frameFunction); } const points = []; for (let i = 0; i < numPoints; i++) { points[i] = frameFunction(i / (numPoints - 1), params, idealDuration); } const tempObj = FramePtsCache.has(key) ? FramePtsCache.get(key) : /* @__PURE__ */ new WeakMap(); tempObj.set(frameFunction, [points, idealDuration]); FramePtsCache.set(key, tempObj); return [points, idealDuration];}function SpringEasing(values, options = {}, customInterpolate = interpolateComplex) { const optionsObj = EasingOptions(options); const [frames, duration] = GenerateSpringFrames(optionsObj); return [ frames.map((t) => customInterpolate(t, values, optionsObj.decimal)), duration ];}exports.BatchSpringEasing = BatchSpringEasing;exports.CSSSpringEasing = CSSSpringEasing;exports.EaseInOut = EaseInOut;exports.EaseOut = EaseOut;exports.EaseOutIn = EaseOutIn;exports.EasingDurationCache = EasingDurationCache;exports.EasingOptions = EasingOptions;exports.FramePtsCache = FramePtsCache;exports.GenerateSpringFrames = GenerateSpringFrames;exports.INFINITE_LOOP_LIMIT = INFINITE_LOOP_LIMIT;exports.SpringEasing = SpringEasing;exports.SpringFrame = SpringFrame;exports.SpringInFrame = SpringInFrame;exports.SpringInOutFrame = SpringInOutFrame;exports.SpringOutFrame = SpringOutFrame;exports.SpringOutInFrame = SpringOutInFrame;exports.batchInterpolateComplex = batchInterpolateComplex;exports.batchInterpolateNumber = batchInterpolateNumber;exports.batchInterpolateSequence = batchInterpolateSequence;exports.batchInterpolateString = batchInterpolateString;exports.batchInterpolateUsingIndex = batchInterpolateUsingIndex;exports.default = SpringEasing;exports.getLinearSyntax = getLinearSyntax;exports.getOptimizedPoints = getOptimizedPoints;exports.getSpringDuration = getSpringDuration;exports.getUnit = getUnit;exports.interpolateComplex = interpolateComplex;exports.interpolateNumber = interpolateNumber;exports.interpolateSequence = interpolateSequence;exports.interpolateString = interpolateString;exports.interpolateUsingIndex = interpolateUsingIndex;exports.isNumberLike = isNumberLike;exports.limit = limit;exports.parseEasingParameters = parseEasingParameters;exports.ramerDouglasPeucker = ramerDouglasPeucker;exports.registerEasingFunction = registerEasingFunction;exports.registerEasingFunctions = registerEasingFunctions;exports.scale = scale;exports.squaredSegmentDistance = squaredSegmentDistance;exports.toAnimationFrames = toAnimationFrames;exports.toFixed = toFixed;