dom-mini.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var motionDom = require('motion-dom');
  4. var motionUtils = require('motion-utils');
  5. const wrap = (min, max, v) => {
  6. const rangeSize = max - min;
  7. return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
  8. };
  9. const isEasingArray = (ease) => {
  10. return Array.isArray(ease) && typeof ease[0] !== "number";
  11. };
  12. function getEasingForSegment(easing, i) {
  13. return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing;
  14. }
  15. /*
  16. Value in range from progress
  17. Given a lower limit and an upper limit, we return the value within
  18. that range as expressed by progress (usually a number from 0 to 1)
  19. So progress = 0.5 would change
  20. from -------- to
  21. to
  22. from ---- to
  23. E.g. from = 10, to = 20, progress = 0.5 => 15
  24. @param [number]: Lower limit of range
  25. @param [number]: Upper limit of range
  26. @param [number]: The progress between lower and upper limits expressed 0-1
  27. @return [number]: Value as calculated from progress within range (not limited within range)
  28. */
  29. const mixNumber = (from, to, progress) => {
  30. return from + (to - from) * progress;
  31. };
  32. function fillOffset(offset, remaining) {
  33. const min = offset[offset.length - 1];
  34. for (let i = 1; i <= remaining; i++) {
  35. const offsetProgress = motionUtils.progress(0, remaining, i);
  36. offset.push(mixNumber(min, 1, offsetProgress));
  37. }
  38. }
  39. function defaultOffset(arr) {
  40. const offset = [0];
  41. fillOffset(offset, arr.length - 1);
  42. return offset;
  43. }
  44. const isMotionValue = (value) => Boolean(value && value.getVelocity);
  45. function isDOMKeyframes(keyframes) {
  46. return typeof keyframes === "object" && !Array.isArray(keyframes);
  47. }
  48. function resolveSubjects(subject, keyframes, scope, selectorCache) {
  49. if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
  50. return motionDom.resolveElements(subject, scope, selectorCache);
  51. }
  52. else if (subject instanceof NodeList) {
  53. return Array.from(subject);
  54. }
  55. else if (Array.isArray(subject)) {
  56. return subject;
  57. }
  58. else {
  59. return [subject];
  60. }
  61. }
  62. function calculateRepeatDuration(duration, repeat, _repeatDelay) {
  63. return duration * (repeat + 1);
  64. }
  65. /**
  66. * Given a absolute or relative time definition and current/prev time state of the sequence,
  67. * calculate an absolute time for the next keyframes.
  68. */
  69. function calcNextTime(current, next, prev, labels) {
  70. if (typeof next === "number") {
  71. return next;
  72. }
  73. else if (next.startsWith("-") || next.startsWith("+")) {
  74. return Math.max(0, current + parseFloat(next));
  75. }
  76. else if (next === "<") {
  77. return prev;
  78. }
  79. else {
  80. return labels.get(next) ?? current;
  81. }
  82. }
  83. function eraseKeyframes(sequence, startTime, endTime) {
  84. for (let i = 0; i < sequence.length; i++) {
  85. const keyframe = sequence[i];
  86. if (keyframe.at > startTime && keyframe.at < endTime) {
  87. motionUtils.removeItem(sequence, keyframe);
  88. // If we remove this item we have to push the pointer back one
  89. i--;
  90. }
  91. }
  92. }
  93. function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
  94. /**
  95. * Erase every existing value between currentTime and targetTime,
  96. * this will essentially splice this timeline into any currently
  97. * defined ones.
  98. */
  99. eraseKeyframes(sequence, startTime, endTime);
  100. for (let i = 0; i < keyframes.length; i++) {
  101. sequence.push({
  102. value: keyframes[i],
  103. at: mixNumber(startTime, endTime, offset[i]),
  104. easing: getEasingForSegment(easing, i),
  105. });
  106. }
  107. }
  108. /**
  109. * Take an array of times that represent repeated keyframes. For instance
  110. * if we have original times of [0, 0.5, 1] then our repeated times will
  111. * be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
  112. * down to a 0-1 scale.
  113. */
  114. function normalizeTimes(times, repeat) {
  115. for (let i = 0; i < times.length; i++) {
  116. times[i] = times[i] / (repeat + 1);
  117. }
  118. }
  119. function compareByTime(a, b) {
  120. if (a.at === b.at) {
  121. if (a.value === null)
  122. return 1;
  123. if (b.value === null)
  124. return -1;
  125. return 0;
  126. }
  127. else {
  128. return a.at - b.at;
  129. }
  130. }
  131. const defaultSegmentEasing = "easeInOut";
  132. const MAX_REPEAT = 20;
  133. function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
  134. const defaultDuration = defaultTransition.duration || 0.3;
  135. const animationDefinitions = new Map();
  136. const sequences = new Map();
  137. const elementCache = {};
  138. const timeLabels = new Map();
  139. let prevTime = 0;
  140. let currentTime = 0;
  141. let totalDuration = 0;
  142. /**
  143. * Build the timeline by mapping over the sequence array and converting
  144. * the definitions into keyframes and offsets with absolute time values.
  145. * These will later get converted into relative offsets in a second pass.
  146. */
  147. for (let i = 0; i < sequence.length; i++) {
  148. const segment = sequence[i];
  149. /**
  150. * If this is a timeline label, mark it and skip the rest of this iteration.
  151. */
  152. if (typeof segment === "string") {
  153. timeLabels.set(segment, currentTime);
  154. continue;
  155. }
  156. else if (!Array.isArray(segment)) {
  157. timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
  158. continue;
  159. }
  160. let [subject, keyframes, transition = {}] = segment;
  161. /**
  162. * If a relative or absolute time value has been specified we need to resolve
  163. * it in relation to the currentTime.
  164. */
  165. if (transition.at !== undefined) {
  166. currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
  167. }
  168. /**
  169. * Keep track of the maximum duration in this definition. This will be
  170. * applied to currentTime once the definition has been parsed.
  171. */
  172. let maxDuration = 0;
  173. const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
  174. const valueKeyframesAsList = keyframesAsList(valueKeyframes);
  175. const { delay = 0, times = defaultOffset(valueKeyframesAsList), type = "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
  176. let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
  177. /**
  178. * Resolve stagger() if defined.
  179. */
  180. const calculatedDelay = typeof delay === "function"
  181. ? delay(elementIndex, numSubjects)
  182. : delay;
  183. /**
  184. * If this animation should and can use a spring, generate a spring easing function.
  185. */
  186. const numKeyframes = valueKeyframesAsList.length;
  187. const createGenerator = motionDom.isGenerator(type)
  188. ? type
  189. : generators?.[type];
  190. if (numKeyframes <= 2 && createGenerator) {
  191. /**
  192. * As we're creating an easing function from a spring,
  193. * ideally we want to generate it using the real distance
  194. * between the two keyframes. However this isn't always
  195. * possible - in these situations we use 0-100.
  196. */
  197. let absoluteDelta = 100;
  198. if (numKeyframes === 2 &&
  199. isNumberKeyframesArray(valueKeyframesAsList)) {
  200. const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
  201. absoluteDelta = Math.abs(delta);
  202. }
  203. const springTransition = { ...remainingTransition };
  204. if (duration !== undefined) {
  205. springTransition.duration = motionUtils.secondsToMilliseconds(duration);
  206. }
  207. const springEasing = motionDom.createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
  208. ease = springEasing.ease;
  209. duration = springEasing.duration;
  210. }
  211. duration ?? (duration = defaultDuration);
  212. const startTime = currentTime + calculatedDelay;
  213. /**
  214. * If there's only one time offset of 0, fill in a second with length 1
  215. */
  216. if (times.length === 1 && times[0] === 0) {
  217. times[1] = 1;
  218. }
  219. /**
  220. * Fill out if offset if fewer offsets than keyframes
  221. */
  222. const remainder = times.length - valueKeyframesAsList.length;
  223. remainder > 0 && fillOffset(times, remainder);
  224. /**
  225. * If only one value has been set, ie [1], push a null to the start of
  226. * the keyframe array. This will let us mark a keyframe at this point
  227. * that will later be hydrated with the previous value.
  228. */
  229. valueKeyframesAsList.length === 1 &&
  230. valueKeyframesAsList.unshift(null);
  231. /**
  232. * Handle repeat options
  233. */
  234. if (repeat) {
  235. motionUtils.invariant(repeat < MAX_REPEAT, "Repeat count too high, must be less than 20");
  236. duration = calculateRepeatDuration(duration, repeat);
  237. const originalKeyframes = [...valueKeyframesAsList];
  238. const originalTimes = [...times];
  239. ease = Array.isArray(ease) ? [...ease] : [ease];
  240. const originalEase = [...ease];
  241. for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
  242. valueKeyframesAsList.push(...originalKeyframes);
  243. for (let keyframeIndex = 0; keyframeIndex < originalKeyframes.length; keyframeIndex++) {
  244. times.push(originalTimes[keyframeIndex] + (repeatIndex + 1));
  245. ease.push(keyframeIndex === 0
  246. ? "linear"
  247. : getEasingForSegment(originalEase, keyframeIndex - 1));
  248. }
  249. }
  250. normalizeTimes(times, repeat);
  251. }
  252. const targetTime = startTime + duration;
  253. /**
  254. * Add keyframes, mapping offsets to absolute time.
  255. */
  256. addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
  257. maxDuration = Math.max(calculatedDelay + duration, maxDuration);
  258. totalDuration = Math.max(targetTime, totalDuration);
  259. };
  260. if (isMotionValue(subject)) {
  261. const subjectSequence = getSubjectSequence(subject, sequences);
  262. resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
  263. }
  264. else {
  265. const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
  266. const numSubjects = subjects.length;
  267. /**
  268. * For every element in this segment, process the defined values.
  269. */
  270. for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
  271. /**
  272. * Cast necessary, but we know these are of this type
  273. */
  274. keyframes = keyframes;
  275. transition = transition;
  276. const thisSubject = subjects[subjectIndex];
  277. const subjectSequence = getSubjectSequence(thisSubject, sequences);
  278. for (const key in keyframes) {
  279. resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
  280. }
  281. }
  282. }
  283. prevTime = currentTime;
  284. currentTime += maxDuration;
  285. }
  286. /**
  287. * For every element and value combination create a new animation.
  288. */
  289. sequences.forEach((valueSequences, element) => {
  290. for (const key in valueSequences) {
  291. const valueSequence = valueSequences[key];
  292. /**
  293. * Arrange all the keyframes in ascending time order.
  294. */
  295. valueSequence.sort(compareByTime);
  296. const keyframes = [];
  297. const valueOffset = [];
  298. const valueEasing = [];
  299. /**
  300. * For each keyframe, translate absolute times into
  301. * relative offsets based on the total duration of the timeline.
  302. */
  303. for (let i = 0; i < valueSequence.length; i++) {
  304. const { at, value, easing } = valueSequence[i];
  305. keyframes.push(value);
  306. valueOffset.push(motionUtils.progress(0, totalDuration, at));
  307. valueEasing.push(easing || "easeOut");
  308. }
  309. /**
  310. * If the first keyframe doesn't land on offset: 0
  311. * provide one by duplicating the initial keyframe. This ensures
  312. * it snaps to the first keyframe when the animation starts.
  313. */
  314. if (valueOffset[0] !== 0) {
  315. valueOffset.unshift(0);
  316. keyframes.unshift(keyframes[0]);
  317. valueEasing.unshift(defaultSegmentEasing);
  318. }
  319. /**
  320. * If the last keyframe doesn't land on offset: 1
  321. * provide one with a null wildcard value. This will ensure it
  322. * stays static until the end of the animation.
  323. */
  324. if (valueOffset[valueOffset.length - 1] !== 1) {
  325. valueOffset.push(1);
  326. keyframes.push(null);
  327. }
  328. if (!animationDefinitions.has(element)) {
  329. animationDefinitions.set(element, {
  330. keyframes: {},
  331. transition: {},
  332. });
  333. }
  334. const definition = animationDefinitions.get(element);
  335. definition.keyframes[key] = keyframes;
  336. definition.transition[key] = {
  337. ...defaultTransition,
  338. duration: totalDuration,
  339. ease: valueEasing,
  340. times: valueOffset,
  341. ...sequenceTransition,
  342. };
  343. }
  344. });
  345. return animationDefinitions;
  346. }
  347. function getSubjectSequence(subject, sequences) {
  348. !sequences.has(subject) && sequences.set(subject, {});
  349. return sequences.get(subject);
  350. }
  351. function getValueSequence(name, sequences) {
  352. if (!sequences[name])
  353. sequences[name] = [];
  354. return sequences[name];
  355. }
  356. function keyframesAsList(keyframes) {
  357. return Array.isArray(keyframes) ? keyframes : [keyframes];
  358. }
  359. function getValueTransition(transition, key) {
  360. return transition && transition[key]
  361. ? {
  362. ...transition,
  363. ...transition[key],
  364. }
  365. : { ...transition };
  366. }
  367. const isNumber = (keyframe) => typeof keyframe === "number";
  368. const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
  369. function animateElements(elementOrSelector, keyframes, options, scope) {
  370. const elements = motionDom.resolveElements(elementOrSelector, scope);
  371. const numElements = elements.length;
  372. motionUtils.invariant(Boolean(numElements), "No valid element provided.");
  373. const animations = [];
  374. for (let i = 0; i < numElements; i++) {
  375. const element = elements[i];
  376. const elementTransition = { ...options };
  377. /**
  378. * Resolve stagger function if provided.
  379. */
  380. if (typeof elementTransition.delay === "function") {
  381. elementTransition.delay = elementTransition.delay(i, numElements);
  382. }
  383. for (const valueName in keyframes) {
  384. const valueKeyframes = keyframes[valueName];
  385. const valueOptions = {
  386. ...motionDom.getValueTransition(elementTransition, valueName),
  387. };
  388. valueOptions.duration && (valueOptions.duration = motionUtils.secondsToMilliseconds(valueOptions.duration));
  389. valueOptions.delay && (valueOptions.delay = motionUtils.secondsToMilliseconds(valueOptions.delay));
  390. animations.push(new motionDom.NativeAnimation({
  391. element,
  392. name: valueName,
  393. keyframes: valueKeyframes,
  394. transition: valueOptions,
  395. allowFlatten: !elementTransition.type && !elementTransition.ease,
  396. }));
  397. }
  398. }
  399. return animations;
  400. }
  401. function animateSequence(definition, options) {
  402. const animations = [];
  403. createAnimationsFromSequence(definition, options).forEach(({ keyframes, transition }, element) => {
  404. animations.push(...animateElements(element, keyframes, transition));
  405. });
  406. return new motionDom.GroupAnimationWithThen(animations);
  407. }
  408. const createScopedWaapiAnimate = (scope) => {
  409. function scopedAnimate(elementOrSelector, keyframes, options) {
  410. return new motionDom.GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope));
  411. }
  412. return scopedAnimate;
  413. };
  414. const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
  415. exports.animate = animateMini;
  416. exports.animateSequence = animateSequence;