1/** 2 * Anime.js - UMD bundle 3 * @version v4.3.2 4 * @license MIT 5 * @copyright 2026 - Julian Garnier 6 */ 7 8// Global types 9 10/** 11 * @typedef {Object} DefaultsParams 12 * @property {Number|String} [id] 13 * @property {PercentageKeyframes|DurationKeyframes} [keyframes] 14 * @property {EasingParam} [playbackEase] 15 * @property {Number} [playbackRate] 16 * @property {Number} [frameRate] 17 * @property {Number|Boolean} [loop] 18 * @property {Boolean} [reversed] 19 * @property {Boolean} [alternate] 20 * @property {Boolean} [persist] 21 * @property {Boolean|ScrollObserver} [autoplay] 22 * @property {Number|FunctionValue} [duration] 23 * @property {Number|FunctionValue} [delay] 24 * @property {Number} [loopDelay] 25 * @property {EasingParam|FunctionValue} [ease] 26 * @property {'none'|'replace'|'blend'|compositionTypes} [composition] 27 * @property {(v: any) => any} [modifier] 28 * @property {Callback<Tickable>} [onBegin] 29 * @property {Callback<Tickable>} [onBeforeUpdate] 30 * @property {Callback<Tickable>} [onUpdate] 31 * @property {Callback<Tickable>} [onLoop] 32 * @property {Callback<Tickable>} [onPause] 33 * @property {Callback<Tickable>} [onComplete] 34 * @property {Callback<Renderable>} [onRender] 35 */ 36 37/** @typedef {JSAnimation|Timeline} Renderable */ 38/** @typedef {Timer|Renderable} Tickable */ 39/** @typedef {Timer&JSAnimation&Timeline} CallbackArgument */ 40/** @typedef {Animatable|Tickable|WAAPIAnimation|Draggable|ScrollObserver|TextSplitter|Scope|AutoLayout} Revertible */ 41 42// Stagger types 43 44/** 45 * @template T 46 * @callback StaggerFunction 47 * @param {Target} [target] 48 * @param {Number} [index] 49 * @param {Number} [length] 50 * @param {Timeline} [tl] 51 * @return {T} 52 */ 53 54/** 55 * @typedef {Object} StaggerParams 56 * @property {Number|String} [start] 57 * @property {Number|'first'|'center'|'last'|'random'} [from] 58 * @property {Boolean} [reversed] 59 * @property {Array.<Number>} [grid] 60 * @property {('x'|'y')} [axis] 61 * @property {String|((target: Target, i: Number, length: Number) => Number)} [use] 62 * @property {Number} [total] 63 * @property {EasingParam} [ease] 64 * @property {TweenModifier} [modifier] 65 */ 66 67// Targets types 68 69/** @typedef {HTMLElement|SVGElement} DOMTarget */ 70/** @typedef {Record<String, any>} JSTarget */ 71/** @typedef {DOMTarget|JSTarget} Target */ 72/** @typedef {Target|NodeList|String} TargetSelector */ 73/** @typedef {DOMTarget|NodeList|String} DOMTargetSelector */ 74/** @typedef {Array.<DOMTargetSelector>|DOMTargetSelector} DOMTargetsParam */ 75/** @typedef {Array.<DOMTarget>} DOMTargetsArray */ 76/** @typedef {Array.<JSTarget>|JSTarget} JSTargetsParam */ 77/** @typedef {Array.<JSTarget>} JSTargetsArray */ 78/** @typedef {Array.<TargetSelector>|TargetSelector} TargetsParam */ 79/** @typedef {Array.<Target>} TargetsArray */ 80 81// Eases types 82 83/** 84 * @callback EasingFunction 85 * @param {Number} time 86 * @return {Number} 87 */ 88 89/** 90 * @typedef {('linear'|'none'|'in'|'out'|'inOut'|'inQuad'|'outQuad'|'inOutQuad'|'inCubic'|'outCubic'|'inOutCubic'|'inQuart'|'outQuart'|'inOutQuart'|'inQuint'|'outQuint'|'inOutQuint'|'inSine'|'outSine'|'inOutSine'|'inCirc'|'outCirc'|'inOutCirc'|'inExpo'|'outExpo'|'inOutExpo'|'inBounce'|'outBounce'|'inOutBounce'|'inBack'|'outBack'|'inOutBack'|'inElastic'|'outElastic'|'inOutElastic'|'out(p = 1.675)'|'inOut(p = 1.675)'|'inBack(overshoot = 1.7)'|'outBack(overshoot = 1.7)'|'inOutBack(overshoot = 1.7)'|'inElastic(amplitude = 1, period = .3)'|'outElastic(amplitude = 1, period = .3)'|'inOutElastic(amplitude = 1, period = .3)')} EaseStringParamNames 91 */ 92 93/** 94 * @typedef {('ease'|'ease-in'|'ease-out'|'ease-in-out'|'linear(0, 0.25, 1)'|'steps'|'steps(6, start)'|'step-start'|'step-end'|'cubic-bezier(0.42, 0, 1, 1)') } WAAPIEaseStringParamNames 95 */ 96 97/** 98 * @callback PowerEasing 99 * @param {Number|String} [power=1.675] 100 * @return {EasingFunction} 101 */ 102 103/** 104 * @callback BackEasing 105 * @param {Number|String} [overshoot=1.7] 106 * @return {EasingFunction} 107 */ 108 109/** 110 * @callback ElasticEasing 111 * @param {Number|String} [amplitude=1] 112 * @param {Number|String} [period=.3] 113 * @return {EasingFunction} 114 */ 115 116/** @typedef {PowerEasing|BackEasing|ElasticEasing} EasingFunctionWithParams */ 117 118// A hack to get both ease names suggestions AND allow any strings 119// https://github.com/microsoft/TypeScript/issues/29729#issuecomment-460346421 120/** @typedef {(String & {})|EaseStringParamNames|EasingFunction|Spring} EasingParam */ 121/** @typedef {(String & {})|EaseStringParamNames|WAAPIEaseStringParamNames|EasingFunction|Spring} WAAPIEasingParam */ 122 123// Spring types 124 125/** 126 * @typedef {Object} SpringParams 127 * @property {Number} [mass=1] - Mass, default 1 128 * @property {Number} [stiffness=100] - Stiffness, default 100 129 * @property {Number} [damping=10] - Damping, default 10 130 * @property {Number} [velocity=0] - Initial velocity, default 0 131 * @property {Number} [bounce=0] - Initial bounce, default 0 132 * @property {Number} [duration=0] - The perceived duration, default 0 133 * @property {Callback<JSAnimation>} [onComplete] - Callback function called when the spring currentTime hits the perceived duration 134 */ 135 136 // Callback types 137 138/** 139 * @template T 140 * @callback Callback 141 * @param {T} self - Returns itself 142 * @param {PointerEvent} [e] 143 * @return {*} 144 */ 145
146/** 147 * @template {object} T 148 * @typedef {Object} TickableCallbacks 149 * @property {Callback<T>} [onBegin] 150 * @property {Callback<T>} [onBeforeUpdate] 151 * @property {Callback<T>} [onUpdate] 152 * @property {Callback<T>} [onLoop] 153 * @property {Callback<T>} [onPause] 154 * @property {Callback<T>} [onComplete] 155 */ 156 157/** 158 * @template {object} T 159 * @typedef {Object} RenderableCallbacks 160 * @property {Callback<T>} [onRender] 161 */ 162 163// Timer types 164 165/** 166 * @typedef {Object} TimerOptions 167 * @property {Number|String} [id] 168 * @property {TweenParamValue} [duration] 169 * @property {TweenParamValue} [delay] 170 * @property {Number} [loopDelay] 171 * @property {Boolean} [reversed] 172 * @property {Boolean} [alternate] 173 * @property {Boolean|Number} [loop] 174 * @property {Boolean|ScrollObserver} [autoplay] 175 * @property {Number} [frameRate] 176 * @property {Number} [playbackRate] 177 */ 178 179/** 180 * @typedef {TimerOptions & TickableCallbacks<Timer>} TimerParams 181 */ 182 183// Tween types 184 185/** 186 * @callback FunctionValue 187 * @param {Target} target - The animated target 188 * @param {Number} index - The target index 189 * @param {Number} length - The total number of animated targets 190 * @return {Number|String|TweenObjectValue|EasingParam|Array.<Number|String|TweenObjectValue>} 191 */ 192 193/** 194 * @callback TweenModifier 195 * @param {Number} value - The animated value 196 * @return {Number|String} 197 */ 198 199/** @typedef {[Number, Number, Number, Number]} ColorArray */ 200 201/** 202 * @typedef {Object} Tween 203 * @property {Number} id 204 * @property {JSAnimation} parent 205 * @property {String} property 206 * @property {Target} target 207 * @property {String|Number} _value 208 * @property {Function|null} _func 209 * @property {EasingFunction} _ease 210 * @property {Array.<Number>} _fromNumbers 211 * @property {Array.<Number>} _toNumbers 212 * @property {Array.<String>} _strings 213 * @property {Number} _fromNumber 214 * @property {Number} _toNumber 215 * @property {Array.<Number>} _numbers 216 * @property {Number} _number 217 * @property {String} _unit 218 * @property {TweenModifier} _modifier 219 * @property {Number} _currentTime 220 * @property {Number} _delay 221 * @property {Number} _updateDuration 222 * @property {Number} _startTime 223 * @property {Number} _changeDuration 224 * @property {Number} _absoluteStartTime 225 * @property {tweenTypes} _tweenType 226 * @property {valueTypes} _valueType 227 * @property {Number} _composition 228 * @property {Number} _isOverlapped 229 * @property {Number} _isOverridden 230 * @property {Number} _renderTransforms 231 * @property {String} _inlineValue 232 * @property {Tween} _prevRep 233 * @property {Tween} _nextRep 234 * @property {Tween} _prevAdd 235 * @property {Tween} _nextAdd 236 * @property {Tween} _prev 237 * @property {Tween} _next 238 */ 239 240/** 241 * @typedef TweenDecomposedValue 242 * @property {Number} t - Type 243 * @property {Number} n - Single number value 244 * @property {String} u - Value unit 245 * @property {String} o - Value operator 246 * @property {Array.<Number>} d - Array of Numbers (in case of complex value type) 247 * @property {Array.<String>} s - Strings (in case of complex value type) 248 */ 249 250/** @typedef {{_head: null|Tween, _tail: null|Tween}} TweenPropertySiblings */ 251/** @typedef {Record<String, TweenPropertySiblings>} TweenLookups */ 252/** @typedef {WeakMap.<Target, TweenLookups>} TweenReplaceLookups */ 253/** @typedef {Map.<Target, TweenLookups>} TweenAdditiveLookups */ 254 255// JSAnimation types 256 257/** 258 * @typedef {Number|String|FunctionValue|EasingParam} TweenParamValue 259 */ 260 261/** 262 * @typedef {TweenParamValue|[TweenParamValue, TweenParamValue]} TweenPropValue 263 */ 264 265/** 266 * @typedef {(String & {})|'none'|'replace'|'blend'|compositionTypes} TweenComposition 267 */ 268 269/** 270 * @typedef {Object} TweenParamsOptions 271 * @property {TweenParamValue} [duration] 272 * @property {TweenParamValue} [delay] 273 * @property {EasingParam|FunctionValue} [ease] 274 * @property {TweenModifier} [modifier] 275 * @property {TweenComposition} [composition] 276 */ 277 278/** 279 * @typedef {Object} TweenValues 280 * @property {TweenParamValue} [from] 281 * @property {TweenPropValue} [to] 282 * @property {TweenPropValue} [fromTo] 283 */ 284 285/** 286 * @typedef {TweenParamsOptions & TweenValues} TweenKeyValue 287 */ 288 289/** 290 * @typedef {Array.<TweenKeyValue|TweenPropValue>} ArraySyntaxValue 291 */ 292 293/** 294 * @typedef {TweenParamValue|ArraySyntaxValue|TweenKeyValue} TweenOptions 295 */ 296 297/** 298 * @typedef {Partial<{to: TweenParamValue|Array.<TweenParamValue>; from: TweenParamValue|Array.<TweenParamValue>; fromTo: TweenParamValue|Array.<TweenParamValue>;}>} TweenObjectValue 299 */ 300 301/** 302 * @typedef {Object} PercentageKeyframeOptions 303 * @property {EasingParam} [ease] 304 */ 305 306/** 307 * @typedef {Record<String, TweenParamValue>}
307 PercentageKeyframeParams 308 */ 309 310/** 311 * @typedef {Record<String, PercentageKeyframeParams & PercentageKeyframeOptions>} PercentageKeyframes 312 */ 313 314/** 315 * @typedef {Array<Record<String, TweenOptions | TweenModifier | boolean> & TweenParamsOptions>} DurationKeyframes 316 */ 317 318/** 319 * @typedef {Object} AnimationOptions 320 * @property {PercentageKeyframes|DurationKeyframes} [keyframes] 321 * @property {EasingParam} [playbackEase] 322 */ 323 324// TODO: Currently setting TweenModifier to the intersected Record<> makes the FunctionValue type target param any if only one parameter is set 325/** 326 * @typedef {Record<String, TweenOptions | Callback<JSAnimation> | TweenModifier | boolean | PercentageKeyframes | DurationKeyframes | ScrollObserver> & TimerOptions & AnimationOptions & TweenParamsOptions & TickableCallbacks<JSAnimation> & RenderableCallbacks<JSAnimation>} AnimationParams 327 */ 328 329// Timeline types 330 331/** 332 * Accepts:<br> 333 * - `Number` - Absolute position in milliseconds (e.g., `500` places element at exactly 500ms)<br> 334 * - `'+=Number'` - Addition: Position element X ms after the last element (e.g., `'+=100'`)<br> 335 * - `'-=Number'` - Subtraction: Position element X ms before the last element's end (e.g., `'-=100'`)<br> 336 * - `'*=Number'` - Multiplier: Position element at a fraction of the total duration (e.g., `'*=.5'` for halfway)<br> 337 * - `'<'` - Previous end: Position element at the end position of the previous element<br> 338 * - `'<<'` - Previous start: Position element at the start position of the previous element<br> 339 * - `'<<+=Number'` - Combined: Position element relative to previous element's start (e.g., `'<<+=250'`)<br> 340 * - `'label'` - Label: Position element at a named label position (e.g., `'My Label'`) 341 * 342 * @typedef {Number|`+=${Number}`|`-=${Number}`|`*=${Number}`|'<'|'<<'|`<<+=${Number}`|`<<-=${Number}`|String} TimelinePosition 343 */ 344 345/** 346 * Accepts:<br> 347 * - `Number` - Absolute position in milliseconds (e.g., `500` places animation at exactly 500ms)<br> 348 * - `'+=Number'` - Addition: Position animation X ms after the last animation (e.g., `'+=100'`)<br> 349 * - `'-=Number'` - Subtraction: Position animation X ms before the last animation's end (e.g., `'-=100'`)<br> 350 * - `'*=Number'` - Multiplier: Position animation at a fraction of the total duration (e.g., `'*=.5'` for halfway)<br> 351 * - `'<'` - Previous end: Position animation at the end position of the previous animation<br> 352 * - `'<<'` - Previous start: Position animation at the start position of the previous animation<br> 353 * - `'<<+=Number'` - Combined: Position animation relative to previous animation's start (e.g., `'<<+=250'`)<br> 354 * - `'label'` - Label: Position animation at a named label position (e.g., `'My Label'`)<br> 355 * - `stagger(String|Nummber)` - Stagger multi-elements animation positions (e.g., 10, 20, 30...) 356 * 357 * @typedef {TimelinePosition | StaggerFunction<Number|String>} TimelineAnimationPosition 358 */ 359 360/** 361 * @typedef {Object} TimelineOptions 362 * @property {DefaultsParams} [defaults] 363 * @property {EasingParam} [playbackEase] 364 * @property {Boolean} [composition] 365 */ 366 367/** 368 * @typedef {TimerOptions & TimelineOptions & TickableCallbacks<Timeline> & RenderableCallbacks<Timeline>} TimelineParams 369 */ 370 371// WAAPIAnimation types 372 373/** 374 * @typedef {String|Number|Array<String>|Array<Number>} WAAPITweenValue 375 */ 376 377/** 378 * @callback WAAPIFunctionValue 379 * @param {DOMTarget} target - The animated target 380 * @param {Number} index - The target index 381 * @param {Number} length - The total number of animated targets 382 * @return {WAAPITweenValue|WAAPIEasingParam} 383 */ 384 385/** 386 * @typedef {WAAPITweenValue|WAAPIFunctionValue|Array<String|Number|WAAPIFunctionValue>} WAAPIKeyframeValue 387 */ 388 389/** 390 * @typedef {Object} WAAPITweenOptions 391 * @property {WAAPIKeyframeValue} [to] 392 * @property {WAAPIKeyframeValue} [from] 393 * @property {Number|WAAPIFunctionValue} [duration] 394 * @property {Number|WAAPIFunctionValue} [delay] 395 * @property {WAAPIEasingParam} [ease] 396 * @property {CompositeOperation} [composition] 397 */ 398 399/** 400 * @typedef {Object} WAAPIAnimationOptions 401 * @property {Number|Boolean} [loop] 402 * @property {Boolean} [Reversed] 403 * @property {Boolean} [Alternate] 404 * @property {Boolean|ScrollObserver} [autoplay] 405 * @property {Number} [playbackRate] 406 * @property {Number|WAAPIFunctionValue} [duration] 407 * @property {Number|WAAPIFunctionValue} [delay] 408 * @property {WAAPIEasingParam|WAAPIFunctionValue} [ease] 409 * @property {CompositeOperation} [composition] 410 * @property {Boolean} [persist] 411 * @property {Callback<WAAPIAnimation>} [onComplete] 412 */ 413 414/** 415 * @typedef {Record<String, WAAPIKeyframeValue | WAAPIAnimationOptions | Boolean | ScrollObserver | Callback<WAAPIAnimation> | WAAPIEasingParam | WAAPITweenOptions> & WAAPIAnimationOptions} WAAPIAnimationParams 416 */ 417 418// Animatable types 419 420/** 421 * @callback AnimatablePropertySetter 422 * @param {Number|Array.<Number>} to 423 * @param {Number} [duration] 424 * @param {EasingParam} [ease] 425 * @return {AnimatableObject} 426 */ 427 428/** 429 * @callback AnimatablePropertyGetter 430 * @return {Number|Array.<Number>} 431 */ 432
433/** 434 * @typedef {AnimatablePropertySetter & AnimatablePropertyGetter} AnimatableProperty 435 */ 436 437/** 438 * @typedef {Animatable & Record<String, AnimatableProperty>} AnimatableObject 439 */ 440 441/** 442 * @typedef {Object} AnimatablePropertyParamsOptions 443 * @property {String} [unit] 444 * @property {TweenParamValue} [duration] 445 * @property {EasingParam} [ease] 446 * @property {TweenModifier} [modifier] 447 * @property {TweenComposition} [composition] 448 */ 449 450/** 451 * @typedef {Record<String, TweenParamValue | EasingParam | TweenModifier | TweenComposition | AnimatablePropertyParamsOptions> & AnimatablePropertyParamsOptions} AnimatableParams 452 */ 453 454// Scope types 455 456/** 457 * @typedef {Object} ReactRef 458 * @property {HTMLElement|SVGElement|null} [current] 459 */ 460 461/** 462 * @typedef {Object} AngularRef 463 * @property {HTMLElement|SVGElement} [nativeElement] 464 */ 465 466/** 467 * @typedef {Object} ScopeParams 468 * @property {DOMTargetSelector|ReactRef|AngularRef} [root] 469 * @property {DefaultsParams} [defaults] 470 * @property {Record<String, String>} [mediaQueries] 471 */ 472 473/** 474 * @template T 475 * @callback ScopedCallback 476 * @param {Scope} scope 477 * @return {T} 478 */ 479 480/** 481 * @callback ScopeCleanupCallback 482 * @param {Scope} [scope] 483 */ 484 485/** 486 * @callback ScopeConstructorCallback 487 * @param {Scope} [scope] 488 * @return {ScopeCleanupCallback|void} 489 */ 490 491/** 492 * @callback ScopeMethod 493 * @param {...*} args 494 * @return {ScopeCleanupCallback|void} 495 */ 496 497// Scroll types 498 499/** 500 * @typedef {String|Number} ScrollThresholdValue 501 */ 502 503/** 504 * @typedef {Object} ScrollThresholdParam 505 * @property {ScrollThresholdValue} [target] 506 * @property {ScrollThresholdValue} [container] 507 */ 508 509/** 510 * @callback ScrollObserverAxisCallback 511 * @param {ScrollObserver} self 512 * @return {'x'|'y'} 513 */ 514 515/** 516 * @callback ScrollThresholdCallback 517 * @param {ScrollObserver} self 518 * @return {ScrollThresholdValue|ScrollThresholdParam} 519 */ 520 521/** 522 * @typedef {Object} ScrollObserverParams 523 * @property {Number|String} [id] 524 * @property {Boolean|Number|String|EasingParam} [sync] 525 * @property {TargetsParam} [container] 526 * @property {TargetsParam} [target] 527 * @property {'x'|'y'|ScrollObserverAxisCallback|((observer: ScrollObserver) => 'x'|'y'|ScrollObserverAxisCallback)} [axis] 528 * @property {ScrollThresholdValue|ScrollThresholdParam|ScrollThresholdCallback|((observer: ScrollObserver) => ScrollThresholdValue|ScrollThresholdParam|ScrollThresholdCallback)} [enter] 529 * @property {ScrollThresholdValue|ScrollThresholdParam|ScrollThresholdCallback|((observer: ScrollObserver) => ScrollThresholdValue|ScrollThresholdParam|ScrollThresholdCallback)} [leave] 530 * @property {Boolean|((observer: ScrollObserver) => Boolean)} [repeat] 531 * @property {Boolean} [debug] 532 * @property {Callback<ScrollObserver>} [onEnter] 533 * @property {Callback<ScrollObserver>} [onLeave] 534 * @property {Callback<ScrollObserver>} [onEnterForward] 535 * @property {Callback<ScrollObserver>} [onLeaveForward] 536 * @property {Callback<ScrollObserver>} [onEnterBackward] 537 * @property {Callback<ScrollObserver>} [onLeaveBackward] 538 * @property {Callback<ScrollObserver>} [onUpdate] 539 * @property {Callback<ScrollObserver>} [onSyncComplete] 540 */ 541 542// Draggable types 543 544/** 545 * @typedef {Object} DraggableAxisParam 546 * @property {String} [mapTo] 547 * @property {TweenModifier} [modifier] 548 * @property {TweenComposition} [composition] 549 * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [snap] 550 */ 551 552/** 553 * @typedef {Object} DraggableCursorParams 554 * @property {String} [onHover] 555 * @property {String} [onGrab] 556 */ 557 558/** 559 * @typedef {Object} DraggableDragThresholdParams 560 * @property {Number} [mouse] 561 * @property {Number} [touch] 562 */ 563
vendor: 8,671 bytes, lines 564-854
564/** 565 * @typedef {Object} DraggableParams 566 * @property {DOMTargetSelector} [trigger] 567 * @property {DOMTargetSelector|Array<Number>|((draggable: Draggable) => DOMTargetSelector|Array<Number>)} [container] 568 * @property {Boolean|DraggableAxisParam} [x] 569 * @property {Boolean|DraggableAxisParam} [y] 570 * @property {TweenModifier} [modifier] 571 * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [snap] 572 * @property {Number|Array<Number>|((draggable: Draggable) => Number|Array<Number>)} [containerPadding] 573 * @property {Number|((draggable: Draggable) => Number)} [containerFriction] 574 * @property {Number|((draggable: Draggable) => Number)} [releaseContainerFriction] 575 * @property {Number|((draggable: Draggable) => Number)} [dragSpeed] 576 * @property {Number|DraggableDragThresholdParams|((draggable: Draggable) => Number|DraggableDragThresholdParams)} [dragThreshold] 577 * @property {Number|((draggable: Draggable) => Number)} [scrollSpeed] 578 * @property {Number|((draggable: Draggable) => Number)} [scrollThreshold] 579 * @property {Number|((draggable: Draggable) => Number)} [minVelocity] 580 * @property {Number|((draggable: Draggable) => Number)} [maxVelocity] 581 * @property {Number|((draggable: Draggable) => Number)} [velocityMultiplier] 582 * @property {Number} [releaseMass] 583 * @property {Number} [releaseStiffness] 584 * @property {Number} [releaseDamping] 585 * @property {Boolean} [releaseDamping] 586 * @property {EasingParam} [releaseEase] 587 * @property {Boolean|DraggableCursorParams|((draggable: Draggable) => Boolean|DraggableCursorParams)} [cursor] 588 * @property {Callback<Draggable>} [onGrab] 589 * @property {Callback<Draggable>} [onDrag] 590 * @property {Callback<Draggable>} [onRelease] 591 * @property {Callback<Draggable>} [onUpdate] 592 * @property {Callback<Draggable>} [onSettle] 593 * @property {Callback<Draggable>} [onSnap] 594 * @property {Callback<Draggable>} [onResize] 595 * @property {Callback<Draggable>} [onAfterResize] 596 */ 597 598// Text types 599 600/** 601 * @typedef {Object} SplitTemplateParams 602 * @property {false|String} [class] 603 * @property {Boolean|'hidden'|'clip'|'visible'|'scroll'|'auto'} [wrap] 604 * @property {Boolean|'top'|'right'|'bottom'|'left'|'center'} [clone] 605 */ 606 607/** 608 * @typedef {Boolean|String} SplitValue 609 */ 610 611/** 612 * @callback SplitFunctionValue 613 * @param {Node|HTMLElement} [value] 614 * @return String 615 */ 616 617/** 618 * @typedef {Object} TextSplitterParams 619 * @property {SplitValue|SplitTemplateParams|SplitFunctionValue} [lines] 620 * @property {SplitValue|SplitTemplateParams|SplitFunctionValue} [words] 621 * @property {SplitValue|SplitTemplateParams|SplitFunctionValue} [chars] 622 * @property {Boolean} [accessible] 623 * @property {Boolean} [includeSpaces] 624 * @property {Boolean} [debug] 625 */ 626 627// SVG types 628 629/** 630 * @typedef {SVGGeometryElement & { 631 * setAttribute(name: 'draw', value: `${number} ${number}`): void; 632 * draw: `${number} ${number}`; 633 * }} DrawableSVGGeometry 634 */ 635 636 637(function (global, factory) { 638 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 639 typeof define === 'function' && define.amd ? define(['exports'], factory) : 640 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.anime = {})); 641})(this, (function (exports) { 'use strict'; 642 643 // Environments 644 645 // TODO: Do we need to check if we're running inside a worker ? 646 const isBrowser = typeof window !== 'undefined'; 647 648 /** @typedef {Window & {AnimeJS: Array} & {AnimeJSDevTools: any}|null} AnimeJSWindow 649 650 /** @type {AnimeJSWindow} */ 651 const win = isBrowser ? /** @type {AnimeJSWindow} */(/** @type {unknown} */(window)) : null; 652 653 /** @type {Document|null} */ 654 const doc = isBrowser ? document : null; 655 656 // Enums 657 658 /** @enum {Number} */ 659 const tweenTypes = { 660 OBJECT: 0, 661 ATTRIBUTE: 1, 662 CSS: 2, 663 TRANSFORM: 3, 664 CSS_VAR: 4, 665 }; 666 667 /** @enum {Number} */ 668 const valueTypes = { 669 NUMBER: 0, 670 UNIT: 1, 671 COLOR: 2, 672 COMPLEX: 3, 673 }; 674 675 /** @enum {Number} */ 676 const tickModes = { 677 NONE: 0, 678 AUTO: 1, 679 FORCE: 2, 680 }; 681 682 /** @enum {Number} */ 683 const compositionTypes = { 684 replace: 0, 685 none: 1, 686 blend: 2, 687 }; 688 689 // Cache symbols 690 691 const isRegisteredTargetSymbol = Symbol(); 692 const isDomSymbol = Symbol(); 693 const isSvgSymbol = Symbol(); 694 const transformsSymbol = Symbol(); 695 const morphPointsSymbol = Symbol(); 696 const proxyTargetSymbol = Symbol(); 697 698 // Numbers 699 700 const minValue = 1e-11; 701 const maxValue = 1e12; 702 const K = 1e3; 703 const maxFps = 240; 704 705 // Strings 706 707 const emptyString = ''; 708 const cssVarPrefix = 'var('; 709 710 const shortTransforms = /*#__PURE__*/ (() => { 711 const map = new Map(); 712 map.set('x', 'translateX'); 713 map.set('y', 'translateY'); 714 map.set('z', 'translateZ'); 715 return map; 716 })(); 717 718 const validTransforms = [ 719 'translateX', 720 'translateY', 721 'translateZ', 722 'rotate', 723 'rotateX', 724 'rotateY', 725 'rotateZ', 726 'scale', 727 'scaleX', 728 'scaleY', 729 'scaleZ', 730 'skew', 731 'skewX', 732 'skewY', 733 'matrix', 734 'matrix3d', 735 'perspective', 736 ]; 737 738 const transformsFragmentStrings = /*#__PURE__*/ validTransforms.reduce((a, v) => ({...a, [v]: v + '('}), {}); 739 740 // Functions 741 742 /** @return {void} */ 743 const noop = () => {}; 744 745 // Regex 746 747 const hexTestRgx = /(^#([\da-f]{3}){1,2}$)|(^#([\da-f]{4}){1,2}$)/i; 748 const rgbExecRgx = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i; 749 const rgbaExecRgx = /rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(-?\d+|-?\d*.\d+)\s*\)/i; 750 const hslExecRgx = /hsl\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*\)/i; 751 const hslaExecRgx = /hsla\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)\s*\)/i; 752 // export const digitWithExponentRgx = /[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?/g; 753 const digitWithExponentRgx = /[-+]?\d*\.?\d+(?:e[-+]?\d)?/gi; 754 // export const unitsExecRgx = /^([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)+([a-z]+|%)$/i; 755 const unitsExecRgx = /^([-+]?\d*\.?\d+(?:e[-+]?\d+)?)([a-z]+|%)$/i; 756 const lowerCaseRgx = /([a-z])([A-Z])/g; 757 const transformsExecRgx = /(\w+)(\([^)]+\)+)/g; // Match inline transforms with cacl() values, returns the value wrapped in () 758 const relativeValuesExecRgx = /(\*=|\+=|-=)/; 759 const cssVariableMatchRgx = /var\(\s*(--[\w-]+)(?:\s*,\s*([^)]+))?\s*\)/; 760 761 762 763 /** @type {DefaultsParams} */ 764 const defaults = { 765 id: null, 766 keyframes: null, 767 playbackEase: null, 768 playbackRate: 1, 769 frameRate: maxFps, 770 loop: 0, 771 reversed: false, 772 alternate: false, 773 autoplay: true, 774 persist: false, 775 duration: K, 776 delay: 0, 777 loopDelay: 0, 778 ease: 'out(2)', 779 composition: compositionTypes.replace, 780 modifier: v => v, 781 onBegin: noop, 782 onBeforeUpdate: noop, 783 onUpdate: noop, 784 onLoop: noop, 785 onPause: noop, 786 onComplete: noop, 787 onRender: noop, 788 }; 789 790 const scope = { 791 /** @type {Scope} */ 792 current: null, 793 /** @type {Document|DOMTarget} */ 794 root: doc, 795 }; 796 797 const globals = { 798 /** @type {DefaultsParams} */ 799 defaults, 800 /** @type {Number} */ 801 precision: 4, 802 /** @type {Number} equals 1 in ms mode, 0.001 in s mode */ 803 timeScale: 1, 804 /** @type {Number} */ 805 tickThreshold: 200, 806 }; 807 808 const devTools = isBrowser && win.AnimeJSDevTools; 809 810 const globalVersions = { version: '4.3.2', engine: null }; 811 812 if (isBrowser) { 813 if (!win.AnimeJS) win.AnimeJS = []; 814 win.AnimeJS.push(globalVersions); 815 } 816 817 818 819 // Strings 820 821 /** 822 * @param {String} str 823 * @return {String} 824 */ 825 const toLowerCase = str => str.replace(lowerCaseRgx, '$1-$2').toLowerCase(); 826 827 /** 828 * Prioritize this method instead of regex when possible 829 * @param {String} str 830 * @param {String} sub 831 * @return {Boolean} 832 */ 833 const stringStartsWith = (str, sub) => str.indexOf(sub) === 0; 834 835 // Note: Date.now is used instead of performance.now since it is precise enough for timings calculations, performs slightly faster and works in Node.js environement. 836 const now = Date.now; 837 838 // Types checkers 839 840 const isArr = Array.isArray; 841 /**@param {any} a @return {a is Record<String, any>} */ 842 const isObj = a => a && a.constructor === Object; 843 /**@param {any} a @return {a is Number} */ 844 const isNum = a => typeof a === 'number' && !isNaN(a); 845 /**@param {any} a @return {a is String} */ 846 const isStr = a => typeof a === 'string'; 847 /**@param {any} a @return {a is Function} */ 848 const isFnc = a => typeof a === 'function'; 849 /**@param {any} a @return {a is undefined} */ 850 const isUnd = a => typeof a === 'undefined'; 851 /**@param {any} a @return {a is null | undefined} */ 852 const isNil = a => isUnd(a) || a === null; 853 /**@param {any} a @return {a is SVGElement} */ 854 const isSvg = a =>
854 isBrowser && a instanceof SVGElement; 855 /**@param {any} a @return {Boolean} */ 856 const isHex = a => hexTestRgx.test(a); 857 /**@param {any} a @return {Boolean} */ 858 const isRgb = a => stringStartsWith(a, 'rgb') && a[a.length - 1] === ')'; 859 /**@param {any} a @return {Boolean} */ 860 const isHsl = a => stringStartsWith(a, 'hsl') && a[a.length - 1] === ')'; 861 /**@param {any} a @return {Boolean} */ 862 const isCol = a => isHex(a) || isRgb(a) || isHsl(a); 863 /**@param {any} a @return {Boolean} */ 864 const isKey = a => !globals.defaults.hasOwnProperty(a); 865 866 // SVG 867 868 // Consider the following as CSS animation 869 // CSS opacity animation has better default values (opacity: 1 instead of 0)) 870 // rotate is more commonly intended to be used as a transform 871 const svgCssReservedProperties = ['opacity', 'rotate', 'overflow', 'color']; 872 873 /** 874 * @param {Target} el 875 * @param {String} propertyName 876 * @return {Boolean} 877 */ 878 const isValidSVGAttribute = (el, propertyName) => { 879 if (svgCssReservedProperties.includes(propertyName)) return false; 880 if (el.getAttribute(propertyName) || propertyName in el) { 881 if (propertyName === 'scale') { // Scale 882 const elParentNode = /** @type {SVGGeometryElement} */(/** @type {DOMTarget} */(el).parentNode); 883 // Only consider scale as a valid SVG attribute on filter element 884 return elParentNode && elParentNode.tagName === 'filter'; 885 } 886 return true; 887 } 888 }; 889 890 // Number 891 892 /** 893 * @param {Number|String} str 894 * @return {Number} 895 */ 896 const parseNumber = str => isStr(str) ? 897 parseFloat(/** @type {String} */(str)) : 898 /** @type {Number} */(str); 899 900 // Math 901 902 const pow = Math.pow; 903 const sqrt = Math.sqrt; 904 const sin = Math.sin; 905 const cos = Math.cos; 906 const abs = Math.abs; 907 const exp = Math.exp; 908 const ceil = Math.ceil; 909 const floor = Math.floor; 910 const asin = Math.asin; 911 const max = Math.max; 912 const atan2 = Math.atan2; 913 const PI = Math.PI; 914 const _round = Math.round; 915 916 /** 917 * Clamps a value between min and max bounds 918 * 919 * @param {Number} v - Value to clamp 920 * @param {Number} min - Minimum boundary 921 * @param {Number} max - Maximum boundary 922 * @return {Number} 923 */ 924 const clamp$1 = (v, min, max) => v < min ? min : v > max ? max : v; 925 926 const powCache = {}; 927 928 /** 929 * Rounds a number to specified decimal places 930 * 931 * @param {Number} v - Value to round 932 * @param {Number} decimalLength - Number of decimal places 933 * @return {Number} 934 */ 935 const round$1 = (v, decimalLength) => { 936 if (decimalLength < 0) return v; 937 if (!decimalLength) return _round(v); 938 let p = powCache[decimalLength]; 939 if (!p) p = powCache[decimalLength] = 10 ** decimalLength; 940 return _round(v * p) / p; 941 }; 942 943 /** 944 * Snaps a value to nearest increment or array value 945 * 946 * @param {Number} v - Value to snap 947 * @param {Number|Array<Number>} increment - Step size or array of snap points 948 * @return {Number} 949 */ 950 const snap$1 = (v, increment) => isArr(increment) ? increment.reduce((closest, cv) => (abs(cv - v) < abs(closest - v) ? cv : closest)) : increment ? _round(v / increment) * increment : v; 951 952 /** 953 * Linear interpolation between two values 954 * 955 * @param {Number} start - Starting value 956 * @param {Number} end - Ending value 957 * @param {Number} factor - Interpolation factor in the range [0, 1] 958 * @return {Number} The interpolated value 959 */ 960 const lerp$1 = (start, end, factor) => start + (end - start) * factor; 961 962 /** 963 * Replaces infinity with maximum safe value 964 * 965 * @param {Number} v - Value to check 966 * @return {Number} 967 */ 968 const clampInfinity = v => v === Infinity ? maxValue : v === -Infinity ? -maxValue : v; 969 970 /** 971 * Normalizes time value with minimum threshold 972 * 973 * @param {Number} v - Time value to normalize 974 * @return {Number} 975 */ 976 const normalizeTime = v => v <= minValue ? minValue : clampInfinity(round$1(v, 11)); 977 978 // Arrays 979 980 /** 981 * @template T 982 * @param {T[]} a 983 * @return {T[]} 984 */ 985 const cloneArray = a => isArr(a) ? [ ...a ] : a; 986 987 // Objects 988 989 /** 990 * @template T 991 * @template U 992 * @param {T} o1 993 * @param {U} o2 994 * @return {T & U} 995 */ 996 const mergeObjects = (o1, o2) => { 997 const merged = /** @type {T & U} */({ ...o1 }); 998 for (let p in o2) { 999 const o1p = /** @type {T & U} */(o1)[p]; 1000 merged[p] = isUnd(o1p) ? /** @type {T & U} */(o2)[p] : o1p; 1001 } return merged; 1002 }; 1003 1004 // Linked lists 1005 1006 /** 1007 * @param {Object} parent 1008 * @param {Function} callback 1009 * @param {Boolean} [reverse] 1010 * @param {String} [prevProp] 1011 * @param {String} [nextProp] 1012 * @return {void} 1013 */
vendor: 4,788 bytes, lines 1014-1167
1014 const forEachChildren = (parent, callback, reverse, prevProp = '_prev', nextProp = '_next') => { 1015 let next = parent._head; 1016 let adjustedNextProp = nextProp; 1017 if (reverse) { 1018 next = parent._tail; 1019 adjustedNextProp = prevProp; 1020 } 1021 while (next) { 1022 const currentNext = next[adjustedNextProp]; 1023 callback(next); 1024 next = currentNext; 1025 } 1026 }; 1027 1028 /** 1029 * @param {Object} parent 1030 * @param {Object} child 1031 * @param {String} [prevProp] 1032 * @param {String} [nextProp] 1033 * @return {void} 1034 */ 1035 const removeChild = (parent, child, prevProp = '_prev', nextProp = '_next') => { 1036 const prev = child[prevProp]; 1037 const next = child[nextProp]; 1038 prev ? prev[nextProp] = next : parent._head = next; 1039 next ? next[prevProp] = prev : parent._tail = prev; 1040 child[prevProp] = null; 1041 child[nextProp] = null; 1042 }; 1043 1044 /** 1045 * @param {Object} parent 1046 * @param {Object} child 1047 * @param {Function} [sortMethod] 1048 * @param {String} prevProp 1049 * @param {String} nextProp 1050 * @return {void} 1051 */ 1052 const addChild = (parent, child, sortMethod, prevProp = '_prev', nextProp = '_next') => { 1053 let prev = parent._tail; 1054 while (prev && sortMethod && sortMethod(prev, child)) prev = prev[prevProp]; 1055 const next = prev ? prev[nextProp] : parent._head; 1056 prev ? prev[nextProp] = child : parent._head = child; 1057 next ? next[prevProp] = child : parent._tail = child; 1058 child[prevProp] = prev; 1059 child[nextProp] = next; 1060 }; 1061 1062 1063 1064 /** 1065 * @param {DOMTarget} target 1066 * @param {String} propName 1067 * @param {Object} animationInlineStyles 1068 * @return {String} 1069 */ 1070 const parseInlineTransforms = (target, propName, animationInlineStyles) => { 1071 const inlineTransforms = target.style.transform; 1072 let inlinedStylesPropertyValue; 1073 if (inlineTransforms) { 1074 const cachedTransforms = target[transformsSymbol]; 1075 let t; while (t = transformsExecRgx.exec(inlineTransforms)) { 1076 const inlinePropertyName = t[1]; 1077 // const inlinePropertyValue = t[2]; 1078 const inlinePropertyValue = t[2].slice(1, -1); 1079 cachedTransforms[inlinePropertyName] = inlinePropertyValue; 1080 if (inlinePropertyName === propName) { 1081 inlinedStylesPropertyValue = inlinePropertyValue; 1082 // Store the new parsed inline styles if animationInlineStyles is provided 1083 if (animationInlineStyles) { 1084 animationInlineStyles[propName] = inlinePropertyValue; 1085 } 1086 } 1087 } 1088 } 1089 return inlineTransforms && !isUnd(inlinedStylesPropertyValue) ? inlinedStylesPropertyValue : 1090 stringStartsWith(propName, 'scale') ? '1' : 1091 stringStartsWith(propName, 'rotate') || stringStartsWith(propName, 'skew') ? '0deg' : '0px'; 1092 }; 1093 1094 1095 1096 /** 1097 * RGB / RGBA Color value string -> RGBA values array 1098 * @param {String} rgbValue 1099 * @return {ColorArray} 1100 */ 1101 const rgbToRgba = rgbValue => { 1102 const rgba = rgbExecRgx.exec(rgbValue) || rgbaExecRgx.exec(rgbValue); 1103 const a = !isUnd(rgba[4]) ? +rgba[4] : 1; 1104 return [ 1105 +rgba[1], 1106 +rgba[2], 1107 +rgba[3], 1108 a 1109 ] 1110 }; 1111 1112 /** 1113 * HEX3 / HEX3A / HEX6 / HEX6A Color value string -> RGBA values array 1114 * @param {String} hexValue 1115 * @return {ColorArray} 1116 */ 1117 const hexToRgba = hexValue => { 1118 const hexLength = hexValue.length; 1119 const isShort = hexLength === 4 || hexLength === 5; 1120 return [ 1121 +('0x' + hexValue[1] + hexValue[isShort ? 1 : 2]), 1122 +('0x' + hexValue[isShort ? 2 : 3] + hexValue[isShort ? 2 : 4]), 1123 +('0x' + hexValue[isShort ? 3 : 5] + hexValue[isShort ? 3 : 6]), 1124 ((hexLength === 5 || hexLength === 9) ? +(+('0x' + hexValue[isShort ? 4 : 7] + hexValue[isShort ? 4 : 8]) / 255).toFixed(3) : 1) 1125 ] 1126 }; 1127 1128 /** 1129 * @param {Number} p 1130 * @param {Number} q 1131 * @param {Number} t 1132 * @return {Number} 1133 */ 1134 const hue2rgb = (p, q, t) => { 1135 if (t < 0) t += 1; 1136 if (t > 1) t -= 1; 1137 return t < 1 / 6 ? p + (q - p) * 6 * t : 1138 t < 1 / 2 ? q : 1139 t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : 1140 p; 1141 }; 1142 1143 /** 1144 * HSL / HSLA Color value string -> RGBA values array 1145 * @param {String} hslValue 1146 * @return {ColorArray} 1147 */ 1148 const hslToRgba = hslValue => { 1149 const hsla = hslExecRgx.exec(hslValue) || hslaExecRgx.exec(hslValue); 1150 const h = +hsla[1] / 360; 1151 const s = +hsla[2] / 100; 1152 const l = +hsla[3] / 100; 1153 const a = !isUnd(hsla[4]) ? +hsla[4] : 1; 1154 let r, g, b; 1155 if (s === 0) { 1156 r = g = b = l; 1157 } else { 1158 const q = l < .5 ? l * (1 + s) : l + s - l * s; 1159 const p = 2 * l - q; 1160 r = round$1(hue2rgb(p, q, h + 1 / 3) * 255, 0); 1161 g = round$1(hue2rgb(p, q, h) * 255, 0); 1162 b = round$1(hue2rgb(p, q, h - 1 / 3) * 255, 0); 1163 } 1164 return [r, g, b, a]; 1165 }; 1166 1167 /**
1168 * All in one color converter that converts a color string value into an array of RGBA values 1169 * @param {String} colorString 1170 * @return {ColorArray} 1171 */ 1172 const convertColorStringValuesToRgbaArray = colorString => { 1173 return isRgb(colorString) ? rgbToRgba(colorString) : 1174 isHex(colorString) ? hexToRgba(colorString) : 1175 isHsl(colorString) ? hslToRgba(colorString) : 1176 [0, 0, 0, 1]; 1177 }; 1178 1179 1180 1181 /** 1182 * @template T, D 1183 * @param {T|undefined} targetValue 1184 * @param {D} defaultValue 1185 * @return {T|D} 1186 */ 1187 const setValue = (targetValue, defaultValue) => { 1188 return isUnd(targetValue) ? defaultValue : targetValue; 1189 }; 1190 1191 /** 1192 * @param {TweenPropValue} value 1193 * @param {Target} target 1194 * @param {Number} index 1195 * @param {Number} total 1196 * @param {Object} [store] 1197 * @return {any} 1198 */ 1199 const getFunctionValue = (value, target, index, total, store) => { 1200 let func; 1201 if (isFnc(value)) { 1202 func = () => { 1203 const computed = /** @type {Function} */(value)(target, index, total); 1204 // Fallback to 0 if the function returns undefined / NaN / null / false / 0 1205 return !isNaN(+computed) ? +computed : computed || 0; 1206 }; 1207 } else if (isStr(value) && stringStartsWith(value, cssVarPrefix)) { 1208 func = () => { 1209 const match = value.match(cssVariableMatchRgx); 1210 const cssVarName = match[1]; 1211 const fallbackValue = match[2]; 1212 let computed = getComputedStyle(/** @type {HTMLElement} */(target))?.getPropertyValue(cssVarName); 1213 // Use fallback if CSS variable is not set or empty 1214 if ((!computed || computed.trim() === emptyString) && fallbackValue) { 1215 computed = fallbackValue.trim(); 1216 } 1217 return computed || 0; 1218 }; 1219 } else { 1220 return value; 1221 } 1222 if (store) store.func = func; 1223 return func(); 1224 }; 1225 1226 /** 1227 * @param {Target} target 1228 * @param {String} prop 1229 * @return {tweenTypes} 1230 */ 1231 const getTweenType = (target, prop) => { 1232 return !target[isDomSymbol] ? tweenTypes.OBJECT : 1233 // Handle SVG attributes 1234 target[isSvgSymbol] && isValidSVGAttribute(target, prop) ? tweenTypes.ATTRIBUTE : 1235 // Handle CSS Transform properties differently than CSS to allow individual animations 1236 validTransforms.includes(prop) || shortTransforms.get(prop) ? tweenTypes.TRANSFORM : 1237 // CSS variables 1238 stringStartsWith(prop, '--') ? tweenTypes.CSS_VAR : 1239 // All other CSS properties 1240 prop in /** @type {DOMTarget} */(target).style ? tweenTypes.CSS : 1241 // Handle other DOM Attributes 1242 prop in target ? tweenTypes.OBJECT : 1243 tweenTypes.ATTRIBUTE; 1244 }; 1245 1246 /** 1247 * @param {DOMTarget} target 1248 * @param {String} propName 1249 * @param {Object} animationInlineStyles 1250 * @return {String} 1251 */ 1252 const getCSSValue = (target, propName, animationInlineStyles) => { 1253 const inlineStyles = target.style[propName]; 1254 if (inlineStyles && animationInlineStyles) { 1255 animationInlineStyles[propName] = inlineStyles; 1256 } 1257 const value = inlineStyles || getComputedStyle(target[proxyTargetSymbol] || target).getPropertyValue(propName); 1258 return value === 'auto' ? '0' : value; 1259 }; 1260 1261 /** 1262 * @param {Target} target 1263 * @param {String} propName 1264 * @param {tweenTypes} [tweenType] 1265 * @param {Object|void} [animationInlineStyles] 1266 * @return {String|Number} 1267 */ 1268 const getOriginalAnimatableValue = (target, propName, tweenType, animationInlineStyles) => { 1269 const type = !isUnd(tweenType) ? tweenType : getTweenType(target, propName); 1270 return type === tweenTypes.OBJECT ? target[propName] || 0 : 1271 type === tweenTypes.ATTRIBUTE ? /** @type {DOMTarget} */(target).getAttribute(propName) : 1272 type === tweenTypes.TRANSFORM ? parseInlineTransforms(/** @type {DOMTarget} */(target), propName, animationInlineStyles) : 1273 type === tweenTypes.CSS_VAR ? getCSSValue(/** @type {DOMTarget} */(target), propName, animationInlineStyles).trimStart() : 1274 getCSSValue(/** @type {DOMTarget} */(target), propName, animationInlineStyles); 1275 }; 1276 1277 /** 1278 * @param {Number} x 1279 * @param {Number} y 1280 * @param {String} operator
vendor: 29,362 bytes, lines 1281-1995
1281 * @return {Number} 1282 */ 1283 const getRelativeValue = (x, y, operator) => { 1284 return operator === '-' ? x - y : 1285 operator === '+' ? x + y : 1286 x * y; 1287 }; 1288 1289 /** @return {TweenDecomposedValue} */ 1290 const createDecomposedValueTargetObject = () => { 1291 return { 1292 /** @type {valueTypes} */ 1293 t: valueTypes.NUMBER, 1294 n: 0, 1295 u: null, 1296 o: null, 1297 d: null, 1298 s: null, 1299 } 1300 }; 1301 1302 /** 1303 * @param {String|Number} rawValue 1304 * @param {TweenDecomposedValue} targetObject 1305 * @return {TweenDecomposedValue} 1306 */ 1307 const decomposeRawValue = (rawValue, targetObject) => { 1308 /** @type {valueTypes} */ 1309 targetObject.t = valueTypes.NUMBER; 1310 targetObject.n = 0; 1311 targetObject.u = null; 1312 targetObject.o = null; 1313 targetObject.d = null; 1314 targetObject.s = null; 1315 if (!rawValue) return targetObject; 1316 const num = +rawValue; 1317 if (!isNaN(num)) { 1318 // It's a number 1319 targetObject.n = num; 1320 return targetObject; 1321 } else { 1322 // let str = /** @type {String} */(rawValue).trim(); 1323 let str = /** @type {String} */(rawValue); 1324 // Parsing operators (+=, -=, *=) manually is much faster than using regex here 1325 if (str[1] === '=') { 1326 targetObject.o = str[0]; 1327 str = str.slice(2); 1328 } 1329 // Skip exec regex if the value type is complex or color to avoid long regex backtracking 1330 const unitMatch = str.includes(' ') ? false : unitsExecRgx.exec(str); 1331 if (unitMatch) { 1332 // Has a number and a unit 1333 targetObject.t = valueTypes.UNIT; 1334 targetObject.n = +unitMatch[1]; 1335 targetObject.u = unitMatch[2]; 1336 return targetObject; 1337 } else if (targetObject.o) { 1338 // Has an operator (+=, -=, *=) 1339 targetObject.n = +str; 1340 return targetObject; 1341 } else if (isCol(str)) { 1342 // Is a color 1343 targetObject.t = valueTypes.COLOR; 1344 targetObject.d = convertColorStringValuesToRgbaArray(str); 1345 return targetObject; 1346 } else { 1347 // Is a more complex string (generally svg coords, calc() or filters CSS values) 1348 const matchedNumbers = str.match(digitWithExponentRgx); 1349 targetObject.t = valueTypes.COMPLEX; 1350 targetObject.d = matchedNumbers ? matchedNumbers.map(Number) : []; 1351 targetObject.s = str.split(digitWithExponentRgx) || []; 1352 return targetObject; 1353 } 1354 } 1355 }; 1356 1357 /** 1358 * @param {Tween} tween 1359 * @param {TweenDecomposedValue} targetObject 1360 * @return {TweenDecomposedValue} 1361 */ 1362 const decomposeTweenValue = (tween, targetObject) => { 1363 targetObject.t = tween._valueType; 1364 targetObject.n = tween._toNumber; 1365 targetObject.u = tween._unit; 1366 targetObject.o = null; 1367 targetObject.d = cloneArray(tween._toNumbers); 1368 targetObject.s = cloneArray(tween._strings); 1369 return targetObject; 1370 }; 1371 1372 const decomposedOriginalValue = createDecomposedValueTargetObject(); 1373 1374 1375 1376 1377 1378 1379 1380 /** 1381 * @param {Tickable} tickable 1382 * @param {Number} time 1383 * @param {Number} muteCallbacks 1384 * @param {Number} internalRender 1385 * @param {tickModes} tickMode 1386 * @return {Number} 1387 */ 1388 const render = (tickable, time, muteCallbacks, internalRender, tickMode) => { 1389 1390 const parent = tickable.parent; 1391 const duration = tickable.duration; 1392 const completed = tickable.completed; 1393 const iterationDuration = tickable.iterationDuration; 1394 const iterationCount = tickable.iterationCount; 1395 const _currentIteration = tickable._currentIteration; 1396 const _loopDelay = tickable._loopDelay; 1397 const _reversed = tickable._reversed; 1398 const _alternate = tickable._alternate; 1399 const _hasChildren = tickable._hasChildren; 1400 const tickableDelay = tickable._delay; 1401 const tickablePrevAbsoluteTime = tickable._currentTime; // TODO: rename ._currentTime to ._absoluteCurrentTime 1402 1403 const tickableEndTime = tickableDelay + iterationDuration; 1404 const tickableAbsoluteTime = time - tickableDelay; 1405 const tickablePrevTime = clamp$1(tickablePrevAbsoluteTime, -tickableDelay, duration); 1406 const tickableCurrentTime = clamp$1(tickableAbsoluteTime, -tickableDelay, duration); 1407 const deltaTime = tickableAbsoluteTime - tickablePrevAbsoluteTime; 1408 const isCurrentTimeAboveZero = tickableCurrentTime > 0; 1409 const isCurrentTimeEqualOrAboveDuration = tickableCurrentTime >= duration; 1410 const isSetter = duration <= minValue; 1411 const forcedTick = tickMode === tickModes.FORCE; 1412 1413 let isOdd = 0; 1414 let iterationElapsedTime = tickableAbsoluteTime; 1415 // Render checks 1416 // Used to also check if the children have rendered in order to trigger the onRender callback on the parent timer 1417 let hasRendered = 0; 1418 1419 // Execute the "expensive" iterations calculations only when necessary 1420 if (iterationCount > 1) { 1421 // bitwise NOT operator seems to be generally faster than Math.floor() across browsers 1422 const currentIteration = ~~(tickableCurrentTime / (iterationDuration + (isCurrentTimeEqualOrAboveDuration ? 0 : _loopDelay))); 1423 tickable._currentIteration = clamp$1(currentIteration, 0, iterationCount); 1424 // Prevent the iteration count to go above the max iterations when reaching the end of the animation 1425 if (isCurrentTimeEqualOrAboveDuration) tickable._currentIteration--; 1426 isOdd = tickable._currentIteration % 2; 1427 iterationElapsedTime = tickableCurrentTime % (iterationDuration + _loopDelay) || 0; 1428 } 1429 1430 // Checks if exactly one of _reversed and (_alternate && isOdd) is true 1431 const isReversed = _reversed ^ (_alternate && isOdd); 1432 const _ease = /** @type {Renderable} */(tickable)._ease; 1433 let iterationTime = isCurrentTimeEqualOrAboveDuration ? isReversed ? 0 : duration : isReversed ? iterationDuration - iterationElapsedTime : iterationElapsedTime; 1434 if (_ease) iterationTime = iterationDuration * _ease(iterationTime / iterationDuration) || 0; 1435 const isRunningBackwards = (parent ? parent.backwards : tickableAbsoluteTime < tickablePrevAbsoluteTime) ? !isReversed : !!isReversed; 1436 1437 tickable._currentTime = tickableAbsoluteTime; 1438 tickable._iterationTime = iterationTime; 1439 tickable.backwards = isRunningBackwards; 1440 1441 if (isCurrentTimeAboveZero && !tickable.began) { 1442 tickable.began = true; 1443 if (!muteCallbacks && !(parent && (isRunningBackwards || !parent.began))) { 1444 tickable.onBegin(/** @type {CallbackArgument} */(tickable)); 1445 } 1446 } else if (tickableAbsoluteTime <= 0) { 1447 tickable.began = false; 1448 } 1449 1450 // Only triggers onLoop for tickable without children, otherwise call the the onLoop callback in the tick function 1451 // Make sure to trigger the onLoop before rendering to allow .refresh() to pickup the current values 1452 if (!muteCallbacks && !_hasChildren && isCurrentTimeAboveZero && tickable._currentIteration !== _currentIteration) { 1453 tickable.onLoop(/** @type {CallbackArgument} */(tickable)); 1454 } 1455 1456 if ( 1457 forcedTick || 1458 tickMode === tickModes.AUTO && ( 1459 time >= tickableDelay && time <= tickableEndTime || // Normal render 1460 time <= tickableDelay && tickablePrevTime > tickableDelay || // Playhead is before the animation start time so make sure the animation is at its initial state 1461 time >= tickableEndTime && tickablePrevTime !== duration // Playhead is after the animation end time so make sure the animation is at its end state 1462 ) || 1463 iterationTime >= tickableEndTime && tickablePrevTime !== duration || 1464 iterationTime <= tickableDelay && tickablePrevTime > 0 || 1465 time <= tickablePrevTime && tickablePrevTime === duration && completed || // Force a render if a seek occurs on an completed animation 1466 isCurrentTimeEqualOrAboveDuration && !completed && isSetter // This prevents 0 duration tickables to be skipped 1467 ) { 1468 1469 if (isCurrentTimeAboveZero) { 1470 // Trigger onUpdate callback before rendering 1471 tickable.computeDeltaTime(tickablePrevTime); 1472 if (!muteCallbacks) tickable.onBeforeUpdate(/** @type {CallbackArgument} */(tickable)); 1473 } 1474 1475 // Start tweens rendering 1476 if (!_hasChildren) { 1477 1478 // Time has jumped more than globals.tickThreshold so consider this tick manual 1479 const forcedRender = forcedTick || (isRunningBackwards ? deltaTime * -1 : deltaTime) >= globals.tickThreshold; 1480 const absoluteTime = tickable._offset + (parent ? parent._offset : 0) + tickableDelay + iterationTime; 1481 1482 // Only Animation can have tweens, Timer returns undefined 1483 let tween = /** @type {Tween} */(/** @type {JSAnimation} */(tickable)._head); 1484 let tweenTarget; 1485 let tweenStyle; 1486 let tweenTargetTransforms; 1487 let tweenTargetTransformsProperties; 1488 let tweenTransformsNeedUpdate = 0; 1489 1490 while (tween) { 1491 1492 const tweenComposition = tween._composition; 1493 const tweenCurrentTime = tween._currentTime; 1494 const tweenChangeDuration = tween._changeDuration; 1495 const tweenAbsEndTime = tween._absoluteStartTime + tween._changeDuration; 1496 const tweenNextRep = tween._nextRep; 1497 const tweenPrevRep = tween._prevRep; 1498 const tweenHasComposition = tweenComposition !== compositionTypes.none; 1499 1500 if ((forcedRender || ( 1501 (tweenCurrentTime !== tweenChangeDuration || absoluteTime <= tweenAbsEndTime + (tweenNextRep ? tweenNextRep._delay : 0)) && 1502 (tweenCurrentTime !== 0 || absoluteTime >= tween._absoluteStartTime) 1503 )) && (!tweenHasComposition || ( 1504 !tween._isOverridden && 1505 (!tween._isOverlapped || absoluteTime <= tweenAbsEndTime) && 1506 (!tweenNextRep || (tweenNextRep._isOverridden || absoluteTime <= tweenNextRep._absoluteStartTime)) && 1507 (!tweenPrevRep || (tweenPrevRep._isOverridden || (absoluteTime >= (tweenPrevRep._absoluteStartTime + tweenPrevRep._changeDuration) + tween._delay))) 1508 )) 1509 ) { 1510 1511 const tweenNewTime = tween._currentTime = clamp$1(iterationTime - tween._startTime, 0, tweenChangeDuration); 1512 const tweenProgress = tween._ease(tweenNewTime / tween._updateDuration); 1513 const tweenModifier = tween._modifier; 1514 const tweenValueType = tween._valueType; 1515 const tweenType = tween._tweenType; 1516 const tweenIsObject = tweenType === tweenTypes.OBJECT; 1517 const tweenIsNumber = tweenValueType === valueTypes.NUMBER; 1518 // Only round the in-between frames values if the final value is a string 1519 const tweenPrecision = (tweenIsNumber && tweenIsObject) || tweenProgress === 0 || tweenProgress === 1 ? -1 : globals.precision; 1520 1521 // Recompose tween value 1522 /** @type {String|Number} */ 1523 let value; 1524 /** @type {Number} */ 1525 let number; 1526 1527 if (tweenIsNumber) { 1528 value = number = /** @type {Number} */(tweenModifier(round$1(lerp$1(tween._fromNumber, tween._toNumber, tweenProgress), tweenPrecision ))); 1529 } else if (tweenValueType === valueTypes.UNIT) { 1530 // Rounding the values speed up string composition 1531 number = /** @type {Number} */(tweenModifier(round$1(lerp$1(tween._fromNumber, tween._toNumber, tweenProgress), tweenPrecision))); 1532 value = `${number}${tween._unit}`; 1533 } else if (tweenValueType === valueTypes.COLOR) { 1534 const fn = tween._fromNumbers; 1535 const tn = tween._toNumbers; 1536 const r = round$1(clamp$1(/** @type {Number} */(tweenModifier(lerp$1(fn[0], tn[0], tweenProgress))), 0, 255), 0); 1537 const g = round$1(clamp$1(/** @type {Number} */(tweenModifier(lerp$1(fn[1], tn[1], tweenProgress))), 0, 255), 0); 1538 const b = round$1(clamp$1(/** @type {Number} */(tweenModifier(lerp$1(fn[2], tn[2], tweenProgress))), 0, 255), 0); 1539 const a = clamp$1(/** @type {Number} */(tweenModifier(round$1(lerp$1(fn[3], tn[3], tweenProgress), tweenPrecision))), 0, 1); 1540 value = `rgba(${r},${g},${b},${a})`; 1541 if (tweenHasComposition) { 1542 const ns = tween._numbers; 1543 ns[0] = r; 1544 ns[1] = g; 1545 ns[2] = b; 1546 ns[3] = a; 1547 } 1548 } else if (tweenValueType === valueTypes.COMPLEX) { 1549 value = tween._strings[0]; 1550 for (let j = 0, l = tween._toNumbers.length; j < l; j++) { 1551 const n = /** @type {Number} */(tweenModifier(round$1(lerp$1(tween._fromNumbers[j], tween._toNumbers[j], tweenProgress), tweenPrecision))); 1552 const s = tween._strings[j + 1]; 1553 value += `${s ? n + s : n}`; 1554 if (tweenHasComposition) { 1555 tween._numbers[j] = n; 1556 } 1557 } 1558 } 1559 1560 // For additive tweens and Animatables 1561 if (tweenHasComposition) { 1562 tween._number = number; 1563 } 1564 1565 if (!internalRender && tweenComposition !== compositionTypes.blend) { 1566 1567 const tweenProperty = tween.property; 1568 tweenTarget = tween.target; 1569 1570 if (tweenIsObject) { 1571 tweenTarget[tweenProperty] = value; 1572 } else if (tweenType === tweenTypes.ATTRIBUTE) { 1573 /** @type {DOMTarget} */(tweenTarget).setAttribute(tweenProperty, /** @type {String} */(value)); 1574 } else { 1575 tweenStyle = /** @type {DOMTarget} */(tweenTarget).style; 1576 if (tweenType === tweenTypes.TRANSFORM) { 1577 if (tweenTarget !== tweenTargetTransforms) { 1578 tweenTargetTransforms = tweenTarget; 1579 // NOTE: Referencing the cachedTransforms in the tween property directly can be a little bit faster but appears to increase memory usage. 1580 tweenTargetTransformsProperties = tweenTarget[transformsSymbol]; 1581 } 1582 tweenTargetTransformsProperties[tweenProperty] = value; 1583 tweenTransformsNeedUpdate = 1; 1584 } else if (tweenType === tweenTypes.CSS) { 1585 tweenStyle[tweenProperty] = value; 1586 } else if (tweenType === tweenTypes.CSS_VAR) { 1587 tweenStyle.setProperty(tweenProperty,/** @type {String} */(value)); 1588 } 1589 } 1590 1591 if (isCurrentTimeAboveZero) hasRendered = 1; 1592 1593 } else { 1594 // Used for composing timeline tweens without having to do a real render 1595 tween._value = value; 1596 } 1597 1598 } 1599 1600 // NOTE: Possible improvement: Use translate(x,y) / translate3d(x,y,z) syntax 1601 // to reduce memory usage on string composition 1602 if (tweenTransformsNeedUpdate && tween._renderTransforms) { 1603 let str = emptyString; 1604 for (let key in tweenTargetTransformsProperties) { 1605 str += `${transformsFragmentStrings[key]}${tweenTargetTransformsProperties[key]}) `; 1606 } 1607 tweenStyle.transform = str; 1608 tweenTransformsNeedUpdate = 0; 1609 } 1610 1611 tween = tween._next; 1612 } 1613 1614 if (!muteCallbacks && hasRendered) { 1615 /** @type {JSAnimation} */(tickable).onRender(/** @type {JSAnimation} */(tickable)); 1616 } 1617 } 1618 1619 if (!muteCallbacks && isCurrentTimeAboveZero) { 1620 tickable.onUpdate(/** @type {CallbackArgument} */(tickable)); 1621 } 1622 1623 } 1624 1625 // End tweens rendering 1626 1627 // Handle setters on timeline differently and allow re-trigering the onComplete callback when seeking backwards 1628 if (parent && isSetter) { 1629 if (!muteCallbacks && ( 1630 // (tickableAbsoluteTime > 0 instead) of (tickableAbsoluteTime >= duration) to prevent floating point precision issues 1631 // see: https://github.com/juliangarnier/anime/issues/1088 1632 (parent.began && !isRunningBackwards && tickableAbsoluteTime > 0 && !completed) || 1633 (isRunningBackwards && tickableAbsoluteTime <= minValue && completed) 1634 )) { 1635 tickable.onComplete(/** @type {CallbackArgument} */(tickable)); 1636 tickable.completed = !isRunningBackwards; 1637 } 1638 // If currentTime is both above 0 and at least equals to duration, handles normal onComplete or infinite loops 1639 } else if (isCurrentTimeAboveZero && isCurrentTimeEqualOrAboveDuration) { 1640 if (iterationCount === Infinity) { 1641 // Offset the tickable _startTime with its duration to reset _currentTime to 0 and continue the infinite timer 1642 tickable._startTime += tickable.duration; 1643 } else if (tickable._currentIteration >= iterationCount - 1) { 1644 // By setting paused to true, we tell the engine loop to not render this tickable and removes it from the list on the next tick 1645 tickable.paused = true; 1646 if (!completed && !_hasChildren) { 1647 // If the tickable has children, triggers onComplete() only when all children have completed in the tick function 1648 tickable.completed = true; 1649 if (!muteCallbacks && !(parent && (isRunningBackwards || !parent.began))) { 1650 tickable.onComplete(/** @type {CallbackArgument} */(tickable)); 1651 tickable._resolve(/** @type {CallbackArgument} */(tickable)); 1652 } 1653 } 1654 } 1655 // Otherwise set the completed flag to false 1656 } else { 1657 tickable.completed = false; 1658 } 1659 1660 // NOTE: hasRendered * direction (negative for backwards) this way we can remove the tickable.backwards property completly ? 1661 return hasRendered; 1662 }; 1663 1664 /** 1665 * @param {Tickable} tickable 1666 * @param {Number} time 1667 * @param {Number} muteCallbacks 1668 * @param {Number} internalRender 1669 * @param {Number} tickMode 1670 * @return {void} 1671 */ 1672 const tick = (tickable, time, muteCallbacks, internalRender, tickMode) => { 1673 const _currentIteration = tickable._currentIteration; 1674 render(tickable, time, muteCallbacks, internalRender, tickMode); 1675 if (tickable._hasChildren) { 1676 const tl = /** @type {Timeline} */(tickable); 1677 const tlIsRunningBackwards = tl.backwards; 1678 const tlChildrenTime = internalRender ? time : tl._iterationTime; 1679 const tlCildrenTickTime = now(); 1680 1681 let tlChildrenHasRendered = 0; 1682 let tlChildrenHaveCompleted = true; 1683 1684 // If the timeline has looped forward, we need to manually triggers children skipped callbacks 1685 if (!internalRender && tl._currentIteration !== _currentIteration) { 1686 const tlIterationDuration = tl.iterationDuration; 1687 forEachChildren(tl, (/** @type {JSAnimation} */child) => { 1688 if (!tlIsRunningBackwards) { 1689 // Force an internal render to trigger the callbacks if the child has not completed on loop 1690 if (!child.completed && !child.backwards && child._currentTime < child.iterationDuration) { 1691 render(child, tlIterationDuration, muteCallbacks, 1, tickModes.FORCE); 1692 } 1693 // Reset their began and completed flags to allow retrigering callbacks on the next iteration 1694 child.began = false; 1695 child.completed = false; 1696 } else { 1697 const childDuration = child.duration; 1698 const childStartTime = child._offset + child._delay; 1699 const childEndTime = childStartTime + childDuration; 1700 // Triggers the onComplete callback on reverse for children on the edges of the timeline 1701 if (!muteCallbacks && childDuration <= minValue && (!childStartTime || childEndTime === tlIterationDuration)) { 1702 child.onComplete(child); 1703 } 1704 } 1705 }); 1706 if (!muteCallbacks) tl.onLoop(/** @type {CallbackArgument} */(tl)); 1707 } 1708 1709 forEachChildren(tl, (/** @type {JSAnimation} */child) => { 1710 const childTime = round$1((tlChildrenTime - child._offset) * child._speed, 12); // Rounding is needed when using seconds 1711 const childTickMode = child._fps < tl._fps ? child.requestTick(tlCildrenTickTime) : tickMode; 1712 tlChildrenHasRendered += render(child, childTime, muteCallbacks, internalRender, childTickMode); 1713 if (!child.completed && tlChildrenHaveCompleted) tlChildrenHaveCompleted = false; 1714 }, tlIsRunningBackwards); 1715 1716 // Renders on timeline are triggered by its children so it needs to be set after rendering the children 1717 if (!muteCallbacks && tlChildrenHasRendered) tl.onRender(/** @type {CallbackArgument} */(tl)); 1718 1719 // Triggers the timeline onComplete() once all chindren all completed and the current time has reached the end 1720 if ((tlChildrenHaveCompleted || tlIsRunningBackwards) && tl._currentTime >= tl.duration) { 1721 // Make sure the paused flag is false in case it has been skipped in the render function 1722 tl.paused = true; 1723 if (!tl.completed) { 1724 tl.completed = true; 1725 if (!muteCallbacks) { 1726 tl.onComplete(/** @type {CallbackArgument} */(tl)); 1727 tl._resolve(/** @type {CallbackArgument} */(tl)); 1728 } 1729 } 1730 } 1731 } 1732 }; 1733 1734 1735 1736 1737 1738 const propertyNamesCache = {}; 1739 1740 /** 1741 * @param {String} propertyName 1742 * @param {Target} target 1743 * @param {tweenTypes} tweenType 1744 * @return {String} 1745 */ 1746 const sanitizePropertyName = (propertyName, target, tweenType) => { 1747 if (tweenType === tweenTypes.TRANSFORM) { 1748 const t = shortTransforms.get(propertyName); 1749 return t ? t : propertyName; 1750 } else if ( 1751 tweenType === tweenTypes.CSS || 1752 // Handle special cases where properties like "strokeDashoffset" needs to be set as "stroke-dashoffset" 1753 // but properties like "baseFrequency" should stay in lowerCamelCase 1754 (tweenType === tweenTypes.ATTRIBUTE && (isSvg(target) && propertyName in /** @type {DOMTarget} */(target).style)) 1755 ) { 1756 const cachedPropertyName = propertyNamesCache[propertyName]; 1757 if (cachedPropertyName) { 1758 return cachedPropertyName; 1759 } else { 1760 const lowerCaseName = propertyName ? toLowerCase(propertyName) : propertyName; 1761 propertyNamesCache[propertyName] = lowerCaseName; 1762 return lowerCaseName; 1763 } 1764 } else { 1765 return propertyName; 1766 } 1767 }; 1768 1769 /** 1770 * @template {Renderable} T 1771 * @param {T} renderable 1772 * @return {T} 1773 */ 1774 const cleanInlineStyles = renderable => { 1775 // Allow cleanInlineStyles() to be called on timelines 1776 if (renderable._hasChildren) { 1777 forEachChildren(renderable, cleanInlineStyles, true); 1778 } else { 1779 const animation = /** @type {JSAnimation} */(renderable); 1780 animation.pause(); 1781 forEachChildren(animation, (/** @type {Tween} */tween) => { 1782 const tweenProperty = tween.property; 1783 const tweenTarget = tween.target; 1784 if (tweenTarget[isDomSymbol]) { 1785 const targetStyle = /** @type {DOMTarget} */(tweenTarget).style; 1786 const originalInlinedValue = tween._inlineValue; 1787 const tweenHadNoInlineValue = isNil(originalInlinedValue) || originalInlinedValue === emptyString; 1788 if (tween._tweenType === tweenTypes.TRANSFORM) { 1789 const cachedTransforms = tweenTarget[transformsSymbol]; 1790 if (tweenHadNoInlineValue) { 1791 delete cachedTransforms[tweenProperty]; 1792 } else { 1793 cachedTransforms[tweenProperty] = originalInlinedValue; 1794 } 1795 if (tween._renderTransforms) { 1796 if (!Object.keys(cachedTransforms).length) { 1797 targetStyle.removeProperty('transform'); 1798 } else { 1799 let str = emptyString; 1800 for (let key in cachedTransforms) { 1801 str += transformsFragmentStrings[key] + cachedTransforms[key] + ') '; 1802 } 1803 targetStyle.transform = str; 1804 } 1805 } 1806 } else { 1807 if (tweenHadNoInlineValue) { 1808 targetStyle.removeProperty(toLowerCase(tweenProperty)); 1809 } else { 1810 targetStyle[tweenProperty] = originalInlinedValue; 1811 } 1812 } 1813 if (animation._tail === tween) { 1814 animation.targets.forEach(t => { 1815 if (t.getAttribute && t.getAttribute('style') === emptyString) { 1816 t.removeAttribute('style'); 1817 } }); 1818 } 1819 } 1820 }); 1821 } 1822 return renderable; 1823 }; 1824 1825 1826 1827 /* 1828 * Base class to control framerate and playback rate. 1829 * Inherited by Engine, Timer, Animation and Timeline. 1830 */ 1831 class Clock { 1832 1833 /** @param {Number} [initTime] */ 1834 constructor(initTime = 0) { 1835 /** @type {Number} */ 1836 this.deltaTime = 0; 1837 /** @type {Number} */ 1838 this._currentTime = initTime; 1839 /** @type {Number} */ 1840 this._lastTickTime = initTime; 1841 /** @type {Number} */ 1842 this._startTime = initTime; 1843 /** @type {Number} */ 1844 this._lastTime = initTime; 1845 /** @type {Number} */ 1846 this._scheduledTime = 0; 1847 /** @type {Number} */ 1848 this._frameDuration = K / maxFps; 1849 /** @type {Number} */ 1850 this._fps = maxFps; 1851 /** @type {Number} */ 1852 this._speed = 1; 1853 /** @type {Boolean} */ 1854 this._hasChildren = false; 1855 /** @type {Tickable|Tween} */ 1856 this._head = null; 1857 /** @type {Tickable|Tween} */ 1858 this._tail = null; 1859 } 1860 1861 get fps() { 1862 return this._fps; 1863 } 1864 1865 set fps(frameRate) { 1866 const previousFrameDuration = this._frameDuration; 1867 const fr = +frameRate; 1868 const fps = fr < minValue ? minValue : fr; 1869 const frameDuration = K / fps; 1870 if (fps > defaults.frameRate) defaults.frameRate = fps; 1871 this._fps = fps; 1872 this._frameDuration = frameDuration; 1873 this._scheduledTime += frameDuration - previousFrameDuration; 1874 } 1875 1876 get speed() { 1877 return this._speed; 1878 } 1879 1880 set speed(playbackRate) { 1881 const pbr = +playbackRate; 1882 this._speed = pbr < minValue ? minValue : pbr; 1883 } 1884 1885 /** 1886 * @param {Number} time 1887 * @return {tickModes} 1888 */ 1889 requestTick(time) { 1890 const scheduledTime = this._scheduledTime; 1891 this._lastTickTime = time; 1892 // If the current time is lower than the scheduled time 1893 // this means not enough time has passed to hit one frameDuration 1894 // so skip that frame 1895 if (time < scheduledTime) return tickModes.NONE; 1896 const frameDuration = this._frameDuration; 1897 const frameDelta = time - scheduledTime; 1898 // Ensures that _scheduledTime progresses in steps of at least 1 frameDuration. 1899 // Skips ahead if the actual elapsed time is higher. 1900 this._scheduledTime += frameDelta < frameDuration ? frameDuration : frameDelta; 1901 return tickModes.AUTO; 1902 } 1903 1904 /** 1905 * @param {Number} time 1906 * @return {Number} 1907 */ 1908 computeDeltaTime(time) { 1909 const delta = time - this._lastTime; 1910 this.deltaTime = delta; 1911 this._lastTime = time; 1912 return delta; 1913 } 1914 1915 } 1916 1917 const additive = { 1918 animation: null, 1919 update: noop, 1920 }; 1921 1922 1923 1924 /** 1925 * @typedef AdditiveAnimation 1926 * @property {Number} duration 1927 * @property {Number} _offset 1928 * @property {Number} _delay 1929 * @property {Tween} _head 1930 * @property {Tween} _tail 1931 */ 1932 1933 /** 1934 * @param {TweenAdditiveLookups} lookups 1935 * @return {AdditiveAnimation} 1936 */ 1937 const addAdditiveAnimation = lookups => { 1938 let animation = additive.animation; 1939 if (!animation) { 1940 animation = { 1941 duration: minValue, 1942 computeDeltaTime: noop, 1943 _offset: 0, 1944 _delay: 0, 1945 _head: null, 1946 _tail: null, 1947 }; 1948 additive.animation = animation; 1949 additive.update = () => { 1950 lookups.forEach(propertyAnimation => { 1951 for (let propertyName in propertyAnimation) { 1952 const tweens = propertyAnimation[propertyName]; 1953 const lookupTween = tweens._head; 1954 if (lookupTween) { 1955 const valueType = lookupTween._valueType; 1956 const additiveValues = valueType === valueTypes.COMPLEX || valueType === valueTypes.COLOR ? cloneArray(lookupTween._fromNumbers) : null; 1957 let additiveValue = lookupTween._fromNumber; 1958 let tween = tweens._tail; 1959 while (tween && tween !== lookupTween) { 1960 if (additiveValues) { 1961 for (let i = 0, l = tween._numbers.length; i < l; i++) additiveValues[i] += tween._numbers[i]; 1962 } else { 1963 additiveValue += tween._number; 1964 } 1965 tween = tween._prevAdd; 1966 } 1967 lookupTween._toNumber = additiveValue; 1968 lookupTween._toNumbers = additiveValues; 1969 } 1970 } 1971 }); 1972 // TODO: Avoid polymorphism here, idealy the additive animation should be a regular animation with a higher priority in the render loop 1973 render(animation, 1, 1, 0, tickModes.FORCE); 1974 }; 1975 } 1976 return animation; 1977 }; 1978 1979 1980 1981 1982 1983 const engineTickMethod = /*#__PURE__*/ (() => isBrowser ? requestAnimationFrame : setImmediate)(); 1984 const engineCancelMethod = /*#__PURE__*/ (() => isBrowser ? cancelAnimationFrame : clearImmediate)(); 1985 1986 class Engine extends Clock { 1987 1988 /** @param {Number} [initTime] */ 1989 constructor(initTime) { 1990 super(initTime); 1991 this.useDefaultMainLoop = true; 1992 this.pauseOnDocumentHidden = true; 1993 /** @type {DefaultsParams} */ 1994 this.defaults = defaults; 1995 // this.paused = isBrowser && doc.hidden ? true : false;
vendor: 4,098 bytes, lines 1996-2143
1996 this.paused = true; 1997 /** @type {Number|NodeJS.Immediate} */ 1998 this.reqId = 0; 1999 } 2000 2001 update() { 2002 const time = this._currentTime = now(); 2003 if (this.requestTick(time)) { 2004 this.computeDeltaTime(time); 2005 const engineSpeed = this._speed; 2006 const engineFps = this._fps; 2007 let activeTickable = /** @type {Tickable} */(this._head); 2008 while (activeTickable) { 2009 const nextTickable = activeTickable._next; 2010 if (!activeTickable.paused) { 2011 tick( 2012 activeTickable, 2013 (time - activeTickable._startTime) * activeTickable._speed * engineSpeed, 2014 0, // !muteCallbacks 2015 0, // !internalRender 2016 activeTickable._fps < engineFps ? activeTickable.requestTick(time) : tickModes.AUTO 2017 ); 2018 } else { 2019 removeChild(this, activeTickable); 2020 this._hasChildren = !!this._tail; 2021 activeTickable._running = false; 2022 if (activeTickable.completed && !activeTickable._cancelled) { 2023 activeTickable.cancel(); 2024 } 2025 } 2026 activeTickable = nextTickable; 2027 } 2028 additive.update(); 2029 } 2030 } 2031 2032 wake() { 2033 if (this.useDefaultMainLoop && !this.reqId) { 2034 // Imediatly request a tick to update engine._lastTickTime and get accurate offsetPosition calculation in timer.js 2035 this.requestTick(now()); 2036 this.reqId = engineTickMethod(tickEngine); 2037 } 2038 return this; 2039 } 2040 2041 pause() { 2042 if (!this.reqId) return; 2043 this.paused = true; 2044 return killEngine(); 2045 } 2046 2047 resume() { 2048 if (!this.paused) return; 2049 this.paused = false; 2050 forEachChildren(this, (/** @type {Tickable} */child) => child.resetTime()); 2051 return this.wake(); 2052 } 2053 2054 // Getter and setter for speed 2055 get speed() { 2056 return this._speed * (globals.timeScale === 1 ? 1 : K); 2057 } 2058 2059 set speed(playbackRate) { 2060 this._speed = playbackRate * globals.timeScale; 2061 forEachChildren(this, (/** @type {Tickable} */child) => child.speed = child._speed); 2062 } 2063 2064 // Getter and setter for timeUnit 2065 get timeUnit() { 2066 return globals.timeScale === 1 ? 'ms' : 's'; 2067 } 2068 2069 set timeUnit(unit) { 2070 const secondsScale = 0.001; 2071 const isSecond = unit === 's'; 2072 const newScale = isSecond ? secondsScale : 1; 2073 if (globals.timeScale !== newScale) { 2074 globals.timeScale = newScale; 2075 globals.tickThreshold = 200 * newScale; 2076 const scaleFactor = isSecond ? secondsScale : K; 2077 /** @type {Number} */ 2078 (this.defaults.duration) *= scaleFactor; 2079 this._speed *= scaleFactor; 2080 } 2081 } 2082 2083 // Getter and setter for precision 2084 get precision() { 2085 return globals.precision; 2086 } 2087 2088 set precision(precision) { 2089 globals.precision = precision; 2090 } 2091 2092 } 2093 2094 const engine = /*#__PURE__*/(() => { 2095 const engine = new Engine(now()); 2096 if (isBrowser) { 2097 globalVersions.engine = engine; 2098 doc.addEventListener('visibilitychange', () => { 2099 if (!engine.pauseOnDocumentHidden) return; 2100 doc.hidden ? engine.pause() : engine.resume(); 2101 }); 2102 } 2103 return engine; 2104 })(); 2105 2106 2107 const tickEngine = () => { 2108 if (engine._head) { 2109 engine.reqId = engineTickMethod(tickEngine); 2110 engine.update(); 2111 } else { 2112 engine.reqId = 0; 2113 } 2114 }; 2115 2116 const killEngine = () => { 2117 engineCancelMethod(/** @type {NodeJS.Immediate & Number} */(engine.reqId)); 2118 engine.reqId = 0; 2119 return engine; 2120 }; 2121 2122 2123 2124 const lookups = { 2125 /** @type {TweenReplaceLookups} */ 2126 _rep: new WeakMap(), 2127 /** @type {TweenAdditiveLookups} */ 2128 _add: new Map(), 2129 }; 2130 2131 /** 2132 * @param {Target} target 2133 * @param {String} property 2134 * @param {String} lookup 2135 * @return {TweenPropertySiblings} 2136 */ 2137 const getTweenSiblings = (target, property, lookup = '_rep') => { 2138 const lookupMap = lookups[lookup]; 2139 let targetLookup = lookupMap.get(target); 2140 if (!targetLookup) { 2141 targetLookup = {}; 2142 lookupMap.set(target, targetLookup); 2143 }
vendor: 8,670 bytes, lines 2144-2391
2144 return targetLookup[property] ? targetLookup[property] : targetLookup[property] = { 2145 _head: null, 2146 _tail: null, 2147 } 2148 }; 2149 2150 /** 2151 * @param {Tween} p 2152 * @param {Tween} c 2153 * @return {Number|Boolean} 2154 */ 2155 const addTweenSortMethod = (p, c) => { 2156 return p._isOverridden || p._absoluteStartTime > c._absoluteStartTime; 2157 }; 2158 2159 /** 2160 * @param {Tween} tween 2161 */ 2162 const overrideTween = tween => { 2163 tween._isOverlapped = 1; 2164 tween._isOverridden = 1; 2165 tween._changeDuration = minValue; 2166 tween._currentTime = minValue; 2167 }; 2168 2169 /** 2170 * @param {Tween} tween 2171 * @param {TweenPropertySiblings} siblings 2172 * @return {Tween} 2173 */ 2174 const composeTween = (tween, siblings) => { 2175 2176 const tweenCompositionType = tween._composition; 2177 2178 // Handle replaced tweens 2179 2180 if (tweenCompositionType === compositionTypes.replace) { 2181 2182 const tweenAbsStartTime = tween._absoluteStartTime; 2183 2184 addChild(siblings, tween, addTweenSortMethod, '_prevRep', '_nextRep'); 2185 2186 const prevSibling = tween._prevRep; 2187 2188 // Update the previous siblings for composition replace tweens 2189 2190 if (prevSibling) { 2191 2192 const prevParent = prevSibling.parent; 2193 const prevAbsEndTime = prevSibling._absoluteStartTime + prevSibling._changeDuration; 2194 2195 // Handle looped animations tween 2196 2197 if ( 2198 // Check if the previous tween is from a different animation 2199 tween.parent.id !== prevParent.id && 2200 // Check if the animation has loops 2201 prevParent.iterationCount> 1 && 2202 // Check if _absoluteChangeEndTime of last loop overlaps the current tween 2203 prevAbsEndTime + (prevParent.duration - prevParent.iterationDuration) > tweenAbsStartTime 2204 ) { 2205 2206 // TODO: Find a way to only override the iterations overlapping with the tween 2207 overrideTween(prevSibling); 2208 2209 let prevPrevSibling = prevSibling._prevRep; 2210 2211 // If the tween was part of a set of keyframes, override its siblings 2212 while (prevPrevSibling && prevPrevSibling.parent.id === prevParent.id) { 2213 overrideTween(prevPrevSibling); 2214 prevPrevSibling = prevPrevSibling._prevRep; 2215 } 2216 2217 } 2218 2219 const absoluteUpdateStartTime = tweenAbsStartTime - tween._delay; 2220 2221 if (prevAbsEndTime > absoluteUpdateStartTime) { 2222 2223 const prevChangeStartTime = prevSibling._startTime; 2224 const prevTLOffset = prevAbsEndTime - (prevChangeStartTime + prevSibling._updateDuration); 2225 // Rounding is necessary here to minimize floating point errors when working in seconds 2226 const updatedPrevChangeDuration = round$1(absoluteUpdateStartTime - prevTLOffset - prevChangeStartTime, 12); 2227 2228 prevSibling._changeDuration = updatedPrevChangeDuration; 2229 prevSibling._currentTime = updatedPrevChangeDuration; 2230 prevSibling._isOverlapped = 1; 2231 2232 // Override the previous tween if its new _changeDuration is lower than minValue 2233 // TODO: See if it's even neceseeary to test against minValue, checking for 0 might be enough 2234 if (updatedPrevChangeDuration < minValue) { 2235 overrideTween(prevSibling); 2236 } 2237 } 2238 2239 // Pause (and cancel) the parent if it only contains overlapped tweens 2240 2241 let pausePrevParentAnimation = true; 2242 2243 forEachChildren(prevParent, (/** @type Tween */t) => { 2244 if (!t._isOverlapped) pausePrevParentAnimation = false; 2245 }); 2246 2247 if (pausePrevParentAnimation) { 2248 const prevParentTL = prevParent.parent; 2249 if (prevParentTL) { 2250 let pausePrevParentTL = true; 2251 forEachChildren(prevParentTL, (/** @type JSAnimation */a) => { 2252 if (a !== prevParent) { 2253 forEachChildren(a, (/** @type Tween */t) => { 2254 if (!t._isOverlapped) pausePrevParentTL = false; 2255 }); 2256 } 2257 }); 2258 if (pausePrevParentTL) { 2259 prevParentTL.cancel(); 2260 } 2261 } else { 2262 prevParent.cancel(); 2263 // Previously, calling .cancel() on a timeline child would affect the render order of other children 2264 // Worked around this by marking it as .completed and using .pause() for safe removal in the engine loop 2265 // This is no longer needed since timeline tween composition is now handled separately 2266 // Keeping this here for reference 2267 // prevParent.completed = true; 2268 // prevParent.pause(); 2269 } 2270 } 2271 2272 } 2273 2274 // let nextSibling = tween._nextRep; 2275 2276 // // All the next siblings are automatically overridden 2277 2278 // if (nextSibling && nextSibling._absoluteStartTime >= tweenAbsStartTime) { 2279 // while (nextSibling) { 2280 // overrideTween(nextSibling); 2281 // nextSibling = nextSibling._nextRep; 2282 // } 2283 // } 2284 2285 // if (nextSibling && nextSibling._absoluteStartTime < tweenAbsStartTime) { 2286 // while (nextSibling) { 2287 // overrideTween(nextSibling); 2288 // console.log(tween.id, nextSibling.id); 2289 // nextSibling = nextSibling._nextRep; 2290 // } 2291 // } 2292 2293 // Handle additive tweens composition 2294 2295 } else if (tweenCompositionType === compositionTypes.blend) { 2296 2297 const additiveTweenSiblings = getTweenSiblings(tween.target, tween.property, '_add'); 2298 const additiveAnimation = addAdditiveAnimation(lookups._add); 2299 2300 let lookupTween = additiveTweenSiblings._head; 2301 2302 if (!lookupTween) { 2303 lookupTween = { ...tween }; 2304 lookupTween._composition = compositionTypes.replace; 2305 lookupTween._updateDuration = minValue; 2306 lookupTween._startTime = 0; 2307 lookupTween._numbers = cloneArray(tween._fromNumbers); 2308 lookupTween._number = 0; 2309 lookupTween._next = null; 2310 lookupTween._prev = null; 2311 addChild(additiveTweenSiblings, lookupTween); 2312 addChild(additiveAnimation, lookupTween); 2313 } 2314 2315 // Convert the values of TO to FROM and set TO to 0 2316 2317 const toNumber = tween._toNumber; 2318 tween._fromNumber = lookupTween._fromNumber - toNumber; 2319 tween._toNumber = 0; 2320 tween._numbers = cloneArray(tween._fromNumbers); 2321 tween._number = 0; 2322 lookupTween._fromNumber = toNumber; 2323 2324 if (tween._toNumbers) { 2325 const toNumbers = cloneArray(tween._toNumbers); 2326 if (toNumbers) { 2327 toNumbers.forEach((value, i) => { 2328 tween._fromNumbers[i] = lookupTween._fromNumbers[i] - value; 2329 tween._toNumbers[i] = 0; 2330 }); 2331 } 2332 lookupTween._fromNumbers = toNumbers; 2333 } 2334 2335 addChild(additiveTweenSiblings, tween, null, '_prevAdd', '_nextAdd'); 2336 2337 } 2338 2339 return tween; 2340 2341 }; 2342 2343 /** 2344 * @param {Tween} tween 2345 * @return {Tween} 2346 */ 2347 const removeTweenSliblings = tween => { 2348 const tweenComposition = tween._composition; 2349 if (tweenComposition !== compositionTypes.none) { 2350 const tweenTarget = tween.target; 2351 const tweenProperty = tween.property; 2352 const replaceTweensLookup = lookups._rep; 2353 const replaceTargetProps = replaceTweensLookup.get(tweenTarget); 2354 const tweenReplaceSiblings = replaceTargetProps[tweenProperty]; 2355 removeChild(tweenReplaceSiblings, tween, '_prevRep', '_nextRep'); 2356 if (tweenComposition === compositionTypes.blend) { 2357 const addTweensLookup = lookups._add; 2358 const addTargetProps = addTweensLookup.get(tweenTarget); 2359 if (!addTargetProps) return; 2360 const additiveTweenSiblings = addTargetProps[tweenProperty]; 2361 const additiveAnimation = additive.animation; 2362 removeChild(additiveTweenSiblings, tween, '_prevAdd', '_nextAdd'); 2363 // If only one tween is left in the additive lookup, it's the tween lookup 2364 const lookupTween = additiveTweenSiblings._head; 2365 if (lookupTween && lookupTween === additiveTweenSiblings._tail) { 2366 removeChild(additiveTweenSiblings, lookupTween, '_prevAdd', '_nextAdd'); 2367 removeChild(additiveAnimation, lookupTween); 2368 let shouldClean = true; 2369 for (let prop in addTargetProps) { 2370 if (addTargetProps[prop]._head) { 2371 shouldClean = false; 2372 break; 2373 } 2374 } 2375 if (shouldClean) { 2376 addTweensLookup.delete(tweenTarget); 2377 } 2378 } 2379 } 2380 } 2381 return tween; 2382 }; 2383 2384 /** 2385 * @param {TargetsArray} targetsArray 2386 * @param {JSAnimation} animation 2387 * @param {String} [propertyName] 2388 * @return {Boolean} 2389 */ 2390 const removeTargetsFromJSAnimation = (targetsArray, animation, propertyName) => { 2391 let tweensMatchesTargets = false;
vendor: 39,435 bytes, lines 2392-3510
2392 forEachChildren(animation, (/**@type {Tween} */tween) => { 2393 const tweenTarget = tween.target; 2394 if (targetsArray.includes(tweenTarget)) { 2395 const tweenName = tween.property; 2396 const tweenType = tween._tweenType; 2397 const normalizePropName = sanitizePropertyName(propertyName, tweenTarget, tweenType); 2398 if (!normalizePropName || normalizePropName && normalizePropName === tweenName) { 2399 // Make sure to flag the previous CSS transform tween to renderTransform 2400 if (tween.parent._tail === tween && 2401 tween._tweenType === tweenTypes.TRANSFORM && 2402 tween._prev && 2403 tween._prev._tweenType === tweenTypes.TRANSFORM 2404 ) { 2405 tween._prev._renderTransforms = 1; 2406 } 2407 // Removes the tween from the selected animation 2408 removeChild(animation, tween); 2409 // Detach the tween from its siblings to make sure blended tweens are correctlly removed 2410 removeTweenSliblings(tween); 2411 tweensMatchesTargets = true; 2412 } 2413 } 2414 }, true); 2415 return tweensMatchesTargets; 2416 }; 2417 2418 /** 2419 * @param {TargetsArray} targetsArray 2420 * @param {Renderable} [renderable] 2421 * @param {String} [propertyName] 2422 */ 2423 const removeTargetsFromRenderable = (targetsArray, renderable, propertyName) => { 2424 const parent = /** @type {Renderable|typeof engine} **/(renderable ? renderable : engine); 2425 let removeMatches; 2426 if (parent._hasChildren) { 2427 let iterationDuration = 0; 2428 forEachChildren(parent, (/** @type {Renderable} */child) => { 2429 if (!child._hasChildren) { 2430 removeMatches = removeTargetsFromJSAnimation(targetsArray, /** @type {JSAnimation} */(child), propertyName); 2431 // Remove the child from its parent if no tweens and no children left after the removal 2432 if (removeMatches && !child._head) { 2433 child.cancel(); 2434 removeChild(parent, child); 2435 } else { 2436 // Calculate the new iterationDuration value to handle onComplete with last child in render() 2437 const childTLOffset = child._offset + child._delay; 2438 const childDur = childTLOffset + child.duration; 2439 if (childDur > iterationDuration) { 2440 iterationDuration = childDur; 2441 } 2442 } 2443 } 2444 // Make sure to also remove engine's children targets 2445 // NOTE: Avoid recursion? 2446 if (child._head) { 2447 removeTargetsFromRenderable(targetsArray, child, propertyName); 2448 } else { 2449 child._hasChildren = false; 2450 } 2451 }, true); 2452 // Update iterationDuration value to handle onComplete with last child in render() 2453 if (!isUnd(/** @type {Renderable} */(parent).iterationDuration)) { 2454 /** @type {Renderable} */(parent).iterationDuration = iterationDuration; 2455 } 2456 } else { 2457 removeMatches = removeTargetsFromJSAnimation( 2458 targetsArray, 2459 /** @type {JSAnimation} */(parent), 2460 propertyName 2461 ); 2462 } 2463 if (removeMatches && !parent._head) { 2464 parent._hasChildren = false; 2465 // Cancel the parent if there are no tweens and no children left after the removal 2466 // We have to check if the .cancel() method exist to handle cases where the parent is the engine itself 2467 if (/** @type {Renderable} */(parent).cancel) /** @type {Renderable} */(parent).cancel(); 2468 } 2469 }; 2470 2471 2472 2473 2474 2475 2476 2477 /** 2478 * @param {Timer} timer 2479 * @return {Timer} 2480 */ 2481 const resetTimerProperties = timer => { 2482 timer.paused = true; 2483 timer.began = false; 2484 timer.completed = false; 2485 return timer; 2486 }; 2487 2488 /** 2489 * @param {Timer} timer 2490 * @return {Timer} 2491 */ 2492 const reviveTimer = timer => { 2493 if (!timer._cancelled) return timer; 2494 if (timer._hasChildren) { 2495 forEachChildren(timer, reviveTimer); 2496 } else { 2497 forEachChildren(timer, (/** @type {Tween} tween */tween) => { 2498 if (tween._composition !== compositionTypes.none) { 2499 composeTween(tween, getTweenSiblings(tween.target, tween.property)); 2500 } 2501 }); 2502 } 2503 timer._cancelled = 0; 2504 return timer; 2505 }; 2506 2507 let timerId = 0; 2508 2509 /** 2510 * Base class used to create Timers, Animations and Timelines 2511 */ 2512 class Timer extends Clock { 2513 /** 2514 * @param {TimerParams} [parameters] 2515 * @param {Timeline} [parent] 2516 * @param {Number} [parentPosition] 2517 */ 2518 constructor(parameters = {}, parent = null, parentPosition = 0) { 2519 2520 super(0); 2521 2522 ++timerId; 2523 2524 const { 2525 id, 2526 delay, 2527 duration, 2528 reversed, 2529 alternate, 2530 loop, 2531 loopDelay, 2532 autoplay, 2533 frameRate, 2534 playbackRate, 2535 onComplete, 2536 onLoop, 2537 onPause, 2538 onBegin, 2539 onBeforeUpdate, 2540 onUpdate, 2541 } = parameters; 2542 2543 if (scope.current) scope.current.register(this); 2544 2545 const timerInitTime = parent ? 0 : engine._lastTickTime; 2546 const timerDefaults = parent ? parent.defaults : globals.defaults; 2547 const timerDelay = /** @type {Number} */(isFnc(delay) || isUnd(delay) ? timerDefaults.delay : +delay); 2548 const timerDuration = isFnc(duration) || isUnd(duration) ? Infinity : +duration; 2549 const timerLoop = setValue(loop, timerDefaults.loop); 2550 const timerLoopDelay = setValue(loopDelay, timerDefaults.loopDelay); 2551 let timerIterationCount = timerLoop === true || 2552 timerLoop === Infinity || 2553 /** @type {Number} */(timerLoop) < 0 ? Infinity : 2554 /** @type {Number} */(timerLoop) + 1; 2555 2556 if (devTools) { 2557 const isInfinite = timerIterationCount === Infinity; 2558 const registered = devTools.register(this, parameters, isInfinite); 2559 if (registered && isInfinite) { 2560 const minIterations = alternate ? 2 : 1; 2561 const iterations = parent ? devTools.maxNestedInfiniteLoops : devTools.maxInfiniteLoops; 2562 timerIterationCount = Math.max(iterations, minIterations); 2563 } 2564 } 2565 2566 let offsetPosition = 0; 2567 2568 if (parent) { 2569 offsetPosition = parentPosition; 2570 } else { 2571 // Make sure to tick the engine once if not currently running to get up to date engine._lastTickTime 2572 // to avoid big gaps with the following offsetPosition calculation 2573 if (!engine.reqId) engine.requestTick(now()); 2574 // Make sure to scale the offset position with globals.timeScale to properly handle seconds unit 2575 offsetPosition = (engine._lastTickTime - engine._startTime) * globals.timeScale; 2576 } 2577 2578 // Timer's parameters 2579 /** @type {String|Number} */ 2580 this.id = !isUnd(id) ? id : timerId; 2581 /** @type {Timeline} */ 2582 this.parent = parent; 2583 // Total duration of the timer 2584 this.duration = clampInfinity(((timerDuration + timerLoopDelay) * timerIterationCount) - timerLoopDelay) || minValue; 2585 /** @type {Boolean} */ 2586 this.backwards = false; 2587 /** @type {Boolean} */ 2588 this.paused = true; 2589 /** @type {Boolean} */ 2590 this.began = false; 2591 /** @type {Boolean} */ 2592 this.completed = false; 2593 /** @type {Callback<this>} */ 2594 this.onBegin = onBegin || timerDefaults.onBegin; 2595 /** @type {Callback<this>} */ 2596 this.onBeforeUpdate = onBeforeUpdate || timerDefaults.onBeforeUpdate; 2597 /** @type {Callback<this>} */ 2598 this.onUpdate = onUpdate || timerDefaults.onUpdate; 2599 /** @type {Callback<this>} */ 2600 this.onLoop = onLoop || timerDefaults.onLoop; 2601 /** @type {Callback<this>} */ 2602 this.onPause = onPause || timerDefaults.onPause; 2603 /** @type {Callback<this>} */ 2604 this.onComplete = onComplete || timerDefaults.onComplete; 2605 /** @type {Number} */ 2606 this.iterationDuration = timerDuration; // Duration of one loop 2607 /** @type {Number} */ 2608 this.iterationCount = timerIterationCount; // Number of loops 2609 /** @type {Boolean|ScrollObserver} */ 2610 this._autoplay = parent ? false : setValue(autoplay, timerDefaults.autoplay); 2611 /** @type {Number} */ 2612 this._offset = offsetPosition; 2613 /** @type {Number} */ 2614 this._delay = timerDelay; 2615 /** @type {Number} */ 2616 this._loopDelay = timerLoopDelay; 2617 /** @type {Number} */ 2618 this._iterationTime = 0; 2619 /** @type {Number} */ 2620 this._currentIteration = 0; // Current loop index 2621 /** @type {Function} */ 2622 this._resolve = noop; // Used by .then() 2623 /** @type {Boolean} */ 2624 this._running = false; 2625 /** @type {Number} */ 2626 this._reversed = +setValue(reversed, timerDefaults.reversed); 2627 /** @type {Number} */ 2628 this._reverse = this._reversed; 2629 /** @type {Number} */ 2630 this._cancelled = 0; 2631 /** @type {Boolean} */ 2632 this._alternate = setValue(alternate, timerDefaults.alternate); 2633 /** @type {Renderable} */ 2634 this._prev = null; 2635 /** @type {Renderable} */ 2636 this._next = null; 2637 2638 // Clock's parameters 2639 /** @type {Number} */ 2640 this._lastTickTime = timerInitTime; 2641 /** @type {Number} */ 2642 this._startTime = timerInitTime; 2643 /** @type {Number} */ 2644 this._lastTime = timerInitTime; 2645 /** @type {Number} */ 2646 this._fps = setValue(frameRate, timerDefaults.frameRate); 2647 /** @type {Number} */ 2648 this._speed = setValue(playbackRate, timerDefaults.playbackRate); 2649 } 2650 2651 get cancelled() { 2652 return !!this._cancelled; 2653 } 2654 2655 set cancelled(cancelled) { 2656 cancelled ? this.cancel() : this.reset(true).play(); 2657 } 2658 2659 get currentTime() { 2660 return clamp$1(round$1(this._currentTime, globals.precision), -this._delay, this.duration); 2661 } 2662 2663 set currentTime(time) { 2664 const paused = this.paused; 2665 // Pausing the timer is necessary to avoid time jumps on a running instance 2666 this.pause().seek(+time); 2667 if (!paused) this.resume(); 2668 } 2669 2670 get iterationCurrentTime() { 2671 return clamp$1(round$1(this._iterationTime, globals.precision), 0, this.iterationDuration); 2672 } 2673 2674 set iterationCurrentTime(time) { 2675 this.currentTime = (this.iterationDuration * this._currentIteration) + time; 2676 } 2677 2678 get progress() { 2679 return clamp$1(round$1(this._currentTime / this.duration, 10), 0, 1); 2680 } 2681 2682 set progress(progress) { 2683 this.currentTime = this.duration * progress; 2684 } 2685 2686 get iterationProgress() { 2687 return clamp$1(round$1(this._iterationTime / this.iterationDuration, 10), 0, 1); 2688 } 2689 2690 set iterationProgress(progress) { 2691 const iterationDuration = this.iterationDuration; 2692 this.currentTime = (iterationDuration * this._currentIteration) + (iterationDuration * progress); 2693 } 2694 2695 get currentIteration() { 2696 return this._currentIteration; 2697 } 2698 2699 set currentIteration(iterationCount) { 2700 this.currentTime = (this.iterationDuration * clamp$1(+iterationCount, 0, this.iterationCount - 1)); 2701 } 2702 2703 get reversed() { 2704 return !!this._reversed; 2705 } 2706 2707 set reversed(reverse) { 2708 reverse ? this.reverse() : this.play(); 2709 } 2710 2711 get speed() { 2712 return super.speed; 2713 } 2714 2715 set speed(playbackRate) { 2716 super.speed = playbackRate; 2717 this.resetTime(); 2718 } 2719 2720 /** 2721 * @param {Boolean} [softReset] 2722 * @return {this} 2723 */ 2724 reset(softReset = false) { 2725 // If cancelled, revive the timer before rendering in order to have propertly composed tweens siblings 2726 reviveTimer(this); 2727 if (this._reversed && !this._reverse) this.reversed = false; 2728 // Rendering before updating the completed flag to prevent skips and to make sure the properties are not overridden 2729 // Setting the iterationTime at the end to force the rendering to happend backwards, otherwise calling .reset() on Timelines might not render children in the right order 2730 // NOTE: This is only required for Timelines and might be better to move to the Timeline class? 2731 this._iterationTime = this.iterationDuration; 2732 // Set tickMode to tickModes.FORCE to force rendering 2733 tick(this, 0, 1, ~~softReset, tickModes.FORCE); 2734 // Reset timer properties after revive / render to make sure the props are not updated again 2735 resetTimerProperties(this); 2736 // Also reset children properties 2737 if (this._hasChildren) { 2738 forEachChildren(this, resetTimerProperties); 2739 } 2740 return this; 2741 } 2742 2743 /** 2744 * @param {Boolean} internalRender 2745 * @return {this} 2746 */ 2747 init(internalRender = false) { 2748 this.fps = this._fps; 2749 this.speed = this._speed; 2750 // Manually calling .init() on timelines should render all children intial state 2751 // Forces all children to render once then render to 0 when reseted 2752 if (!internalRender && this._hasChildren) { 2753 tick(this, this.duration, 1, ~~internalRender, tickModes.FORCE); 2754 } 2755 this.reset(internalRender); 2756 // Make sure to set autoplay to false to child timers so it doesn't attempt to autoplay / link 2757 const autoplay = this._autoplay; 2758 if (autoplay === true) { 2759 this.resume(); 2760 } else if (autoplay && !isUnd(/** @type {ScrollObserver} */(autoplay).linked)) { 2761 /** @type {ScrollObserver} */(autoplay).link(this); 2762 } 2763 return this; 2764 } 2765 2766 /** @return {this} */ 2767 resetTime() { 2768 const timeScale = 1 / (this._speed * engine._speed); 2769 // TODO: See if we can safely use engine._lastTickTime here 2770 // if (!engine.reqId) engine.requestTick(now()) 2771 // this._startTime = engine._lastTickTime - (this._currentTime + this._delay) * timeScale; 2772 this._startTime = now() - (this._currentTime + this._delay) * timeScale; 2773 return this; 2774 } 2775 2776 /** @return {this} */ 2777 pause() { 2778 if (this.paused) return this; 2779 this.paused = true; 2780 this.onPause(this); 2781 return this; 2782 } 2783 2784 /** @return {this} */ 2785 resume() { 2786 if (!this.paused) return this; 2787 this.paused = false; 2788 // We can safely imediatly render a timer that has no duration and no children 2789 if (this.duration <= minValue && !this._hasChildren) { 2790 tick(this, minValue, 0, 0, tickModes.FORCE); 2791 } else { 2792 if (!this._running) { 2793 addChild(engine, this); 2794 engine._hasChildren = true; 2795 this._running = true; 2796 } 2797 this.resetTime(); 2798 // Forces the timer to advance by at least one frame when the next tick occurs 2799 this._startTime -= 12; 2800 engine.wake(); 2801 } 2802 return this; 2803 } 2804 2805 /** @return {this} */ 2806 restart() { 2807 return this.reset().resume(); 2808 } 2809 2810 /** 2811 * @param {Number} time 2812 * @param {Boolean|Number} [muteCallbacks] 2813 * @param {Boolean|Number} [internalRender] 2814 * @return {this} 2815 */ 2816 seek(time, muteCallbacks = 0, internalRender = 0) { 2817 // Recompose the tween siblings in case the timer has been cancelled 2818 reviveTimer(this); 2819 // If you seek a completed animation, otherwise the next play will starts at 0 2820 this.completed = false; 2821 const isPaused = this.paused; 2822 this.paused = true; 2823 // timer, time, muteCallbacks, internalRender, tickMode 2824 tick(this, time + this._delay, ~~muteCallbacks, ~~internalRender, tickModes.AUTO); 2825 return isPaused ? this : this.resume(); 2826 } 2827 2828 /** @return {this} */ 2829 alternate() { 2830 const reversed = this._reversed; 2831 const count = this.iterationCount; 2832 const duration = this.iterationDuration; 2833 // Calculate the maximum iterations possible given the iteration duration 2834 const iterations = count === Infinity ? floor(maxValue / duration) : count; 2835 this._reversed = +(this._alternate && !(iterations % 2) ? reversed : !reversed); 2836 if (count === Infinity) { 2837 // Handle infinite loops to loop on themself 2838 this.iterationProgress = this._reversed ? 1 - this.iterationProgress : this.iterationProgress; 2839 } else { 2840 this.seek((duration * iterations) - this._currentTime); 2841 } 2842 this.resetTime(); 2843 return this; 2844 } 2845 2846 /** @return {this} */ 2847 play() { 2848 if (this._reversed) this.alternate(); 2849 return this.resume(); 2850 } 2851 2852 /** @return {this} */ 2853 reverse() { 2854 if (!this._reversed) this.alternate(); 2855 return this.resume(); 2856 } 2857 2858 // TODO: Move all the animation / tweens / children related code to Animation / Timeline 2859 2860 /** @return {this} */ 2861 cancel() { 2862 if (this._hasChildren) { 2863 forEachChildren(this, (/** @type {Renderable} */child) => child.cancel(), true); 2864 } else { 2865 forEachChildren(this, removeTweenSliblings); 2866 } 2867 this._cancelled = 1; 2868 // Pausing the timer removes it from the engine 2869 return this.pause(); 2870 } 2871 2872 /** 2873 * @param {Number} newDuration 2874 * @return {this} 2875 */ 2876 stretch(newDuration) { 2877 const currentDuration = this.duration; 2878 const normlizedDuration = normalizeTime(newDuration); 2879 if (currentDuration === normlizedDuration) return this; 2880 const timeScale = newDuration / currentDuration; 2881 const isSetter = newDuration <= minValue; 2882 this.duration = isSetter ? minValue : normlizedDuration; 2883 this.iterationDuration = isSetter ? minValue : normalizeTime(this.iterationDuration * timeScale); 2884 this._offset *= timeScale; 2885 this._delay *= timeScale; 2886 this._loopDelay *= timeScale; 2887 return this; 2888 } 2889 2890 /** 2891 * Cancels the timer by seeking it back to 0 and reverting the attached scroller if necessary 2892 * @return {this} 2893 */ 2894 revert() { 2895 tick(this, 0, 1, 0, tickModes.AUTO); 2896 const ap = /** @type {ScrollObserver} */(this._autoplay); 2897 if (ap && ap.linked && ap.linked === this) ap.revert(); 2898 return this.cancel(); 2899 } 2900 2901 /** 2902 * Imediatly completes the timer, cancels it and triggers the onComplete callback 2903 * @param {Boolean|Number} [muteCallbacks] 2904 * @return {this} 2905 */ 2906 complete(muteCallbacks = 0) { 2907 return this.seek(this.duration, muteCallbacks).cancel(); 2908 } 2909 2910 /** 2911 * @typedef {this & {then: null}} ResolvedTimer 2912 */ 2913 2914 /** 2915 * @param {Callback<ResolvedTimer>} [callback] 2916 * @return Promise<this> 2917 */ 2918 then(callback = noop) { 2919 const then = this.then; 2920 const onResolve = () => { 2921 // this.then = null prevents infinite recursion if returned by an async function 2922 // https://github.com/juliangarnierorg/anime-beta/issues/26 2923 this.then = null; 2924 callback(/** @type {ResolvedTimer} */(this)); 2925 this.then = then; 2926 this._resolve = noop; 2927 }; 2928 return new Promise(r => { 2929 this._resolve = () => r(onResolve()); 2930 // Make sure to resolve imediatly if the timer has already completed 2931 if (this.completed) this._resolve(); 2932 return this; 2933 }); 2934 } 2935 2936 } 2937 2938 /** 2939 * @param {TimerParams} [parameters] 2940 * @return {Timer} 2941 */ 2942 const createTimer = parameters => new Timer(parameters, null, 0).init(); 2943 2944 2945 2946 /** 2947 * @param {DOMTargetsParam|TargetsParam} v 2948 * @return {NodeList|HTMLCollection} 2949 */ 2950 function getNodeList(v) { 2951 const n = isStr(v) ? scope.root.querySelectorAll(v) : v; 2952 if (n instanceof NodeList || n instanceof HTMLCollection) return n; 2953 } 2954 2955 /** 2956 * @overload 2957 * @param {DOMTargetsParam} targets 2958 * @return {DOMTargetsArray} 2959 * 2960 * @overload 2961 * @param {JSTargetsParam} targets 2962 * @return {JSTargetsArray} 2963 * 2964 * @overload 2965 * @param {TargetsParam} targets 2966 * @return {TargetsArray} 2967 * 2968 * @param {DOMTargetsParam|JSTargetsParam|TargetsParam} targets 2969 */ 2970 function parseTargets(targets) { 2971 if (isNil(targets)) return /** @type {TargetsArray} */([]); 2972 if (!isBrowser) return /** @type {JSTargetsArray} */(isArr(targets) && targets.flat(Infinity) || [targets]); 2973 if (isArr(targets)) { 2974 const flattened = targets.flat(Infinity); 2975 /** @type {TargetsArray} */ 2976 const parsed = []; 2977 for (let i = 0, l = flattened.length; i < l; i++) { 2978 const item = flattened[i]; 2979 if (!isNil(item)) { 2980 const nodeList = getNodeList(item); 2981 if (nodeList) { 2982 for (let j = 0, jl = nodeList.length; j < jl; j++) { 2983 const subItem = nodeList[j]; 2984 if (!isNil(subItem)) { 2985 let isDuplicate = false; 2986 for (let k = 0, kl = parsed.length; k < kl; k++) { 2987 if (parsed[k] === subItem) { 2988 isDuplicate = true; 2989 break; 2990 } 2991 } 2992 if (!isDuplicate) { 2993 parsed.push(subItem); 2994 } 2995 } 2996 } 2997 } else { 2998 let isDuplicate = false; 2999 for (let j = 0, jl = parsed.length; j < jl; j++) { 3000 if (parsed[j] === item) { 3001 isDuplicate = true; 3002 break; 3003 } 3004 } 3005 if (!isDuplicate) { 3006 parsed.push(item); 3007 } 3008 } 3009 } 3010 } 3011 return parsed; 3012 } 3013 const nodeList = getNodeList(targets); 3014 if (nodeList) return /** @type {DOMTargetsArray} */(Array.from(nodeList)); 3015 return /** @type {TargetsArray} */([targets]); 3016 } 3017 3018 /** 3019 * @overload 3020 * @param {DOMTargetsParam} targets 3021 * @return {DOMTargetsArray} 3022 * 3023 * @overload 3024 * @param {JSTargetsParam} targets 3025 * @return {JSTargetsArray} 3026 * 3027 * @overload 3028 * @param {TargetsParam} targets 3029 * @return {TargetsArray} 3030 * 3031 * @param {DOMTargetsParam|JSTargetsParam|TargetsParam} targets 3032 */ 3033 function registerTargets(targets) { 3034 const parsedTargetsArray = parseTargets(targets); 3035 const parsedTargetsLength = parsedTargetsArray.length; 3036 if (parsedTargetsLength) { 3037 for (let i = 0; i < parsedTargetsLength; i++) { 3038 const target = parsedTargetsArray[i]; 3039 if (!target[isRegisteredTargetSymbol]) { 3040 target[isRegisteredTargetSymbol] = true; 3041 const isSvgType = isSvg(target); 3042 const isDom = /** @type {DOMTarget} */(target).nodeType || isSvgType; 3043 if (isDom) { 3044 target[isDomSymbol] = true; 3045 target[isSvgSymbol] = isSvgType; 3046 target[transformsSymbol] = {}; 3047 } 3048 } 3049 } 3050 } 3051 return parsedTargetsArray; 3052 } 3053 3054 const angleUnitsMap = { 'deg': 1, 'rad': 180 / PI, 'turn': 360 }; 3055 const convertedValuesCache = {}; 3056 3057 3058 3059 /** 3060 * @param {DOMTarget} el 3061 * @param {TweenDecomposedValue} decomposedValue 3062 * @param {String} unit 3063 * @param {Boolean} [force] 3064 * @return {TweenDecomposedValue} 3065 */ 3066 const convertValueUnit = (el, decomposedValue, unit, force = false) => { 3067 const currentUnit = decomposedValue.u; 3068 const currentNumber = decomposedValue.n; 3069 if (decomposedValue.t === valueTypes.UNIT && currentUnit === unit) { // TODO: Check if checking against the same unit string is necessary 3070 return decomposedValue; 3071 } 3072 const cachedKey = currentNumber + currentUnit + unit; 3073 const cached = convertedValuesCache[cachedKey]; 3074 if (!isUnd(cached) && !force) { 3075 decomposedValue.n = cached; 3076 } else { 3077 let convertedValue; 3078 if (currentUnit in angleUnitsMap) { 3079 convertedValue = currentNumber * angleUnitsMap[currentUnit] / angleUnitsMap[unit]; 3080 } else { 3081 const baseline = 100; 3082 const tempEl = /** @type {DOMTarget} */(el.cloneNode()); 3083 const parentNode = el.parentNode; 3084 const parentEl = (parentNode && (parentNode !== doc)) ? parentNode : doc.body; 3085 parentEl.appendChild(tempEl); 3086 const elStyle = tempEl.style; 3087 elStyle.width = baseline + currentUnit; 3088 const currentUnitWidth = /** @type {HTMLElement} */(tempEl).offsetWidth || baseline; 3089 elStyle.width = baseline + unit; 3090 const newUnitWidth = /** @type {HTMLElement} */(tempEl).offsetWidth || baseline; 3091 const factor = currentUnitWidth / newUnitWidth; 3092 parentEl.removeChild(tempEl); 3093 convertedValue = factor * currentNumber; 3094 } 3095 decomposedValue.n = convertedValue; 3096 convertedValuesCache[cachedKey] = convertedValue; 3097 } 3098 decomposedValue.t === valueTypes.UNIT; 3099 decomposedValue.u = unit; 3100 return decomposedValue; 3101 }; 3102 3103 3104 3105 /** @type {EasingFunction} */ 3106 const none = t => t; 3107 3108 3109 3110 3111 /** @type {PowerEasing} */ 3112 const easeInPower = (p = 1.68) => t => pow(t, +p); 3113 3114 /** 3115 * @callback EaseType 3116 * @param {EasingFunction} Ease 3117 * @return {EasingFunction} 3118 */ 3119 3120 /** @type {Record<String, EaseType>} */ 3121 const easeTypes = { 3122 in: easeIn => t => easeIn(t), 3123 out: easeIn => t => 1 - easeIn(1 - t), 3124 inOut: easeIn => t => t < .5 ? easeIn(t * 2) / 2 : 1 - easeIn(t * -2 + 2) / 2, 3125 outIn: easeIn => t => t < .5 ? (1 - easeIn(1 - t * 2)) / 2 : (easeIn(t * 2 - 1) + 1) / 2, 3126 }; 3127 3128 /** 3129 * Easing functions adapted and simplified from https://robertpenner.com/easing/ 3130 * (c) 2001 Robert Penner 3131 */ 3132 3133 const halfPI = PI / 2; 3134 const doublePI = PI * 2; 3135 3136 /** @type {Record<String, EasingFunctionWithParams|EasingFunction>} */ 3137 const easeInFunctions = { 3138 [emptyString]: easeInPower, 3139 Quad: easeInPower(2), 3140 Cubic: easeInPower(3), 3141 Quart: easeInPower(4), 3142 Quint: easeInPower(5), 3143 /** @type {EasingFunction} */ 3144 Sine: t => 1 - cos(t * halfPI), 3145 /** @type {EasingFunction} */ 3146 Circ: t => 1 - sqrt(1 - t * t), 3147 /** @type {EasingFunction} */ 3148 Expo: t => t ? pow(2, 10 * t - 10) : 0, 3149 /** @type {EasingFunction} */ 3150 Bounce: t => { 3151 let pow2, b = 4; 3152 while (t < ((pow2 = pow(2, --b)) - 1) / 11); 3153 return 1 / pow(4, 3 - b) - 7.5625 * pow((pow2 * 3 - 2) / 22 - t, 2); 3154 }, 3155 /** @type {BackEasing} */ 3156 Back: (overshoot = 1.7) => t => (+overshoot + 1) * t * t * t - +overshoot * t * t, 3157 /** @type {ElasticEasing} */ 3158 Elastic: (amplitude = 1, period = .3) => { 3159 const a = clamp$1(+amplitude, 1, 10); 3160 const p = clamp$1(+period, minValue, 2); 3161 const s = (p / doublePI) * asin(1 / a); 3162 const e = doublePI / p; 3163 return t => t === 0 || t === 1 ? t : -a * pow(2, -10 * (1 - t)) * sin(((1 - t) - s) * e); 3164 } 3165 }; 3166 3167 /** 3168 * @typedef {Object} EasesFunctions 3169 * @property {typeof none} linear 3170 * @property {typeof none} none 3171 * @property {PowerEasing} in 3172 * @property {PowerEasing} out 3173 * @property {PowerEasing} inOut 3174 * @property {PowerEasing} outIn 3175 * @property {EasingFunction} inQuad 3176 * @property {EasingFunction} outQuad 3177 * @property {EasingFunction} inOutQuad 3178 * @property {EasingFunction} outInQuad 3179 * @property {EasingFunction} inCubic 3180 * @property {EasingFunction} outCubic 3181 * @property {EasingFunction} inOutCubic 3182 * @property {EasingFunction} outInCubic 3183 * @property {EasingFunction} inQuart 3184 * @property {EasingFunction} outQuart 3185 * @property {EasingFunction} inOutQuart 3186 * @property {EasingFunction} outInQuart 3187 * @property {EasingFunction} inQuint 3188 * @property {EasingFunction} outQuint 3189 * @property {EasingFunction} inOutQuint 3190 * @property {EasingFunction} outInQuint 3191 * @property {EasingFunction} inSine 3192 * @property {EasingFunction} outSine 3193 * @property {EasingFunction} inOutSine 3194 * @property {EasingFunction} outInSine 3195 * @property {EasingFunction} inCirc 3196 * @property {EasingFunction} outCirc 3197 * @property {EasingFunction} inOutCirc 3198 * @property {EasingFunction} outInCirc 3199 * @property {EasingFunction} inExpo 3200 * @property {EasingFunction} outExpo 3201 * @property {EasingFunction} inOutExpo 3202 * @property {EasingFunction} outInExpo 3203 * @property {EasingFunction} inBounce 3204 * @property {EasingFunction} outBounce 3205 * @property {EasingFunction} inOutBounce 3206 * @property {EasingFunction} outInBounce 3207 * @property {BackEasing} inBack 3208 * @property {BackEasing} outBack 3209 * @property {BackEasing} inOutBack 3210 * @property {BackEasing} outInBack 3211 * @property {ElasticEasing} inElastic 3212 * @property {ElasticEasing} outElastic 3213 * @property {ElasticEasing} inOutElastic 3214 * @property {ElasticEasing} outInElastic 3215 */ 3216 3217 const eases = (/*#__PURE__ */ (() => { 3218 const list = { linear: none, none: none }; 3219 for (let type in easeTypes) { 3220 for (let name in easeInFunctions) { 3221 const easeIn = easeInFunctions[name]; 3222 const easeType = easeTypes[type]; 3223 list[type + name] = /** @type {EasingFunctionWithParams|EasingFunction} */( 3224 name === emptyString || name === 'Back' || name === 'Elastic' ? 3225 (a, b) => easeType(/** @type {EasingFunctionWithParams} */(easeIn)(a, b)) : 3226 easeType(/** @type {EasingFunction} */(easeIn)) 3227 ); 3228 } 3229 } 3230 return /** @type {EasesFunctions} */(list); 3231 })()); 3232 3233 /** @type {Record<String, EasingFunction>} */ 3234 const easesLookups = { linear: none, none: none }; 3235 3236 /** 3237 * @param {String} string 3238 * @return {EasingFunction} 3239 */ 3240 const parseEaseString = (string) => { 3241 if (easesLookups[string]) return easesLookups[string]; 3242 if (string.indexOf('(') <= -1) { 3243 const hasParams = easeTypes[string] || string.includes('Back') || string.includes('Elastic'); 3244 const parsedFn = /** @type {EasingFunction} */(hasParams ? /** @type {EasingFunctionWithParams} */(eases[string])() : eases[string]); 3245 return parsedFn ? easesLookups[string] = parsedFn : none; 3246 } else { 3247 const split = string.slice(0, -1).split('('); 3248 const parsedFn = /** @type {EasingFunctionWithParams} */(eases[split[0]]); 3249 return parsedFn ? easesLookups[string] = parsedFn(...split[1].split(',')) : none; 3250 } 3251 }; 3252 3253 const deprecated = ['steps(', 'irregular(', 'linear(', 'cubicBezier(']; 3254 3255 /** 3256 * @param {EasingParam} ease 3257 * @return {EasingFunction} 3258 */ 3259 const parseEase = ease => { 3260 if (isStr(ease)) { 3261 for (let i = 0, l = deprecated.length; i < l; i++) { 3262 if (stringStartsWith(ease, deprecated[i])) { 3263 console.warn(`String syntax for \`ease: "${ease}"\` has been removed from the core and replaced by importing and passing the easing function directly: \`ease: ${ease}\``); 3264 return none; 3265 } 3266 } 3267 } 3268 const easeFunc = isFnc(ease) ? ease : isStr(ease) ? parseEaseString(/** @type {String} */(ease)) : none; 3269 return easeFunc; 3270 }; 3271 3272 3273 3274 // Defines decomposed values target objects only once and mutate their properties later to avoid GC 3275 // TODO: Maybe move the objects creation to values.js and use the decompose function to create the base object 3276 const fromTargetObject = createDecomposedValueTargetObject(); 3277 const toTargetObject = createDecomposedValueTargetObject(); 3278 const inlineStylesStore = {}; 3279 const toFunctionStore = { func: null }; 3280 const keyframesTargetArray = [null]; 3281 const fastSetValuesArray = [null, null]; 3282 /** @type {TweenKeyValue} */ 3283 const keyObjectTarget = { to: null }; 3284 3285 let tweenId = 0; 3286 let JSAnimationId = 0; 3287 let keyframes; 3288 /** @type {TweenParamsOptions & TweenValues} */ 3289 let key; 3290 3291 /** 3292 * @param {DurationKeyframes | PercentageKeyframes} keyframes 3293 * @param {AnimationParams} parameters 3294 * @return {AnimationParams} 3295 */ 3296 const generateKeyframes = (keyframes, parameters) => { 3297 /** @type {AnimationParams} */ 3298 const properties = {}; 3299 if (isArr(keyframes)) { 3300 const propertyNames = [].concat(.../** @type {DurationKeyframes} */(keyframes).map(key => Object.keys(key))).filter(isKey); 3301 for (let i = 0, l = propertyNames.length; i < l; i++) { 3302 const propName = propertyNames[i]; 3303 const propArray = /** @type {DurationKeyframes} */(keyframes).map(key => { 3304 /** @type {TweenKeyValue} */ 3305 const newKey = {}; 3306 for (let p in key) { 3307 const keyValue = /** @type {TweenPropValue} */(key[p]); 3308 if (isKey(p)) { 3309 if (p === propName) { 3310 newKey.to = keyValue; 3311 } 3312 } else { 3313 newKey[p] = keyValue; 3314 } 3315 } 3316 return newKey; 3317 }); 3318 properties[propName] = /** @type {ArraySyntaxValue} */(propArray); 3319 } 3320 3321 } else { 3322 const totalDuration = /** @type {Number} */(setValue(parameters.duration, globals.defaults.duration)); 3323 const keys = Object.keys(keyframes) 3324 .map(key => { return {o: parseFloat(key) / 100, p: keyframes[key]} }) 3325 .sort((a, b) => a.o - b.o); 3326 keys.forEach(key => { 3327 const offset = key.o; 3328 const prop = key.p; 3329 for (let name in prop) { 3330 if (isKey(name)) { 3331 let propArray = /** @type {Array} */(properties[name]); 3332 if (!propArray) propArray = properties[name] = []; 3333 const duration = offset * totalDuration; 3334 let length = propArray.length; 3335 let prevKey = propArray[length - 1]; 3336 const keyObj = { to: prop[name] }; 3337 let durProgress = 0; 3338 for (let i = 0; i < length; i++) { 3339 durProgress += propArray[i].duration; 3340 } 3341 if (length === 1) { 3342 keyObj.from = prevKey.to; 3343 } 3344 if (prop.ease) { 3345 keyObj.ease = prop.ease; 3346 } 3347 keyObj.duration = duration - (length ? durProgress : 0); 3348 propArray.push(keyObj); 3349 } 3350 } 3351 return key; 3352 }); 3353 3354 for (let name in properties) { 3355 const propArray = /** @type {Array} */(properties[name]); 3356 let prevEase; 3357 // let durProgress = 0 3358 for (let i = 0, l = propArray.length; i < l; i++) { 3359 const prop = propArray[i]; 3360 // Emulate WAPPI easing parameter position 3361 const currentEase = prop.ease; 3362 prop.ease = prevEase ? prevEase : undefined; 3363 prevEase = currentEase; 3364 // durProgress += prop.duration; 3365 // if (i === l - 1 && durProgress !== totalDuration) { 3366 // propArray.push({ from: prop.to, ease: prop.ease, duration: totalDuration - durProgress }) 3367 // } 3368 } 3369 if (!propArray[0].duration) { 3370 propArray.shift(); 3371 } 3372 } 3373 3374 } 3375 3376 return properties; 3377 }; 3378 3379 class JSAnimation extends Timer { 3380 /** 3381 * @param {TargetsParam} targets 3382 * @param {AnimationParams} parameters 3383 * @param {Timeline} [parent] 3384 * @param {Number} [parentPosition] 3385 * @param {Boolean} [fastSet=false] 3386 * @param {Number} [index=0] 3387 * @param {Number} [length=0] 3388 */ 3389 constructor( 3390 targets, 3391 parameters, 3392 parent, 3393 parentPosition, 3394 fastSet = false, 3395 index = 0, 3396 length = 0 3397 ) { 3398 3399 super(/** @type {TimerParams & AnimationParams} */(parameters), parent, parentPosition); 3400 3401 ++JSAnimationId; 3402 3403 const parsedTargets = registerTargets(targets); 3404 const targetsLength = parsedTargets.length; 3405 3406 // If the parameters object contains a "keyframes" property, convert all the keyframes values to regular properties 3407 3408 const kfParams = /** @type {AnimationParams} */(parameters).keyframes; 3409 const params = /** @type {AnimationParams} */(kfParams ? mergeObjects(generateKeyframes(/** @type {DurationKeyframes} */(kfParams), parameters), parameters) : parameters); 3410 3411 const { 3412 id, 3413 delay, 3414 duration, 3415 ease, 3416 playbackEase, 3417 modifier, 3418 composition, 3419 onRender, 3420 } = params; 3421 3422 const animDefaults = parent ? parent.defaults : globals.defaults; 3423 const animEase = setValue(ease, animDefaults.ease); 3424 const animPlaybackEase = setValue(playbackEase, animDefaults.playbackEase); 3425 const parsedAnimPlaybackEase = animPlaybackEase ? parseEase(animPlaybackEase) : null; 3426 const hasSpring = !isUnd(/** @type {Spring} */(animEase).ease); 3427 const tEasing = hasSpring ? /** @type {Spring} */(animEase).ease : setValue(ease, parsedAnimPlaybackEase ? 'linear' : animDefaults.ease); 3428 const tDuration = hasSpring ? /** @type {Spring} */(animEase).settlingDuration : setValue(duration, animDefaults.duration); 3429 const tDelay = setValue(delay, animDefaults.delay); 3430 const tModifier = modifier || animDefaults.modifier; 3431 // If no composition is defined and the targets length is high (>= 1000) set the composition to 'none' (0) for faster tween creation 3432 const tComposition = isUnd(composition) && targetsLength >= K ? compositionTypes.none : !isUnd(composition) ? composition : animDefaults.composition; 3433 // const absoluteOffsetTime = this._offset; 3434 const absoluteOffsetTime = this._offset + (parent ? parent._offset : 0); 3435 // This allows targeting the current animation in the spring onComplete callback 3436 if (hasSpring) /** @type {Spring} */(animEase).parent = this; 3437 3438 let iterationDuration = NaN; 3439 let iterationDelay = NaN; 3440 let animationAnimationLength = 0; 3441 let shouldTriggerRender = 0; 3442 3443 for (let targetIndex = 0; targetIndex < targetsLength; targetIndex++) { 3444 3445 const target = parsedTargets[targetIndex]; 3446 const ti = index || targetIndex; 3447 const tl = length || targetsLength; 3448 3449 let lastTransformGroupIndex = NaN; 3450 let lastTransformGroupLength = NaN; 3451 3452 for (let p in params) { 3453 3454 if (isKey(p)) { 3455 3456 const tweenType = getTweenType(target, p); 3457 3458 const propName = sanitizePropertyName(p, target, tweenType); 3459 3460 let propValue = params[p]; 3461 3462 const isPropValueArray = isArr(propValue); 3463 3464 if (fastSet && !isPropValueArray) { 3465 fastSetValuesArray[0] = propValue; 3466 fastSetValuesArray[1] = propValue; 3467 propValue = fastSetValuesArray; 3468 } 3469 3470 // TODO: Allow nested keyframes inside ObjectValue value (prop: { to: [.5, 1, .75, 2, 3] }) 3471 // Normalize property values to valid keyframe syntax: 3472 // [x, y] to [{to: [x, y]}] or {to: x} to [{to: x}] or keep keys syntax [{}, {}, {}...] 3473 // const keyframes = isArr(propValue) ? propValue.length === 2 && !isObj(propValue[0]) ? [{ to: propValue }] : propValue : [propValue]; 3474 if (isPropValueArray) { 3475 const arrayLength = /** @type {Array} */(propValue).length; 3476 const isNotObjectValue = !isObj(propValue[0]); 3477 // Convert [x, y] to [{to: [x, y]}] 3478 if (arrayLength === 2 && isNotObjectValue) { 3479 keyObjectTarget.to = /** @type {TweenParamValue} */(/** @type {unknown} */(propValue)); 3480 keyframesTargetArray[0] = keyObjectTarget; 3481 keyframes = keyframesTargetArray; 3482 // Convert [x, y, z] to [[x, y], z] 3483 } else if (arrayLength > 2 && isNotObjectValue) { 3484 keyframes = []; 3485 /** @type {Array.<Number>} */(propValue).forEach((v, i) => { 3486 if (!i) { 3487 fastSetValuesArray[0] = v; 3488 } else if (i === 1) { 3489 fastSetValuesArray[1] = v; 3490 keyframes.push(fastSetValuesArray); 3491 } else { 3492 keyframes.push(v); 3493 } 3494 }); 3495 } else { 3496 keyframes = /** @type {Array.<TweenKeyValue>} */(propValue); 3497 } 3498 } else { 3499 keyframesTargetArray[0] = propValue; 3500 keyframes = keyframesTargetArray; 3501 } 3502 3503 let siblings = null; 3504 let prevTween = null; 3505 let firstTweenChangeStartTime = NaN; 3506 let lastTweenChangeEndTime = 0; 3507 let tweenIndex = 0; 3508 3509 for (let l = keyframes.length; tweenIndex < l; tweenIndex++) { 3510
vendor: 2,638 bytes, lines 3511-3552
3511 const keyframe = keyframes[tweenIndex]; 3512 3513 if (isObj(keyframe)) { 3514 key = keyframe; 3515 } else { 3516 keyObjectTarget.to = /** @type {TweenParamValue} */(keyframe); 3517 key = keyObjectTarget; 3518 } 3519 3520 toFunctionStore.func = null; 3521 3522 const computedToValue = getFunctionValue(key.to, target, ti, tl, toFunctionStore); 3523 3524 let tweenToValue; 3525 // Allows function based values to return an object syntax value ({to: v}) 3526 if (isObj(computedToValue) && !isUnd(computedToValue.to)) { 3527 key = computedToValue; 3528 tweenToValue = computedToValue.to; 3529 } else { 3530 tweenToValue = computedToValue; 3531 } 3532 const tweenFromValue = getFunctionValue(key.from, target, ti, tl); 3533 const easeToParse = key.ease || tEasing; 3534 3535 const easeFunctionResult = getFunctionValue(easeToParse, target, ti, tl); 3536 const keyEasing = isFnc(easeFunctionResult) || isStr(easeFunctionResult) ? easeFunctionResult : easeToParse; 3537 3538 const hasSpring = !isUnd(keyEasing) && !isUnd(/** @type {Spring} */(keyEasing).ease); 3539 const tweenEasing = hasSpring ? /** @type {Spring} */(keyEasing).ease : keyEasing; 3540 // Calculate default individual keyframe duration by dividing the tl of keyframes 3541 const tweenDuration = hasSpring ? /** @type {Spring} */(keyEasing).settlingDuration : getFunctionValue(setValue(key.duration, (l > 1 ? getFunctionValue(tDuration, target, ti, tl) / l : tDuration)), target, ti, tl); 3542 // Default delay value should only be applied to the first tween 3543 const tweenDelay = getFunctionValue(setValue(key.delay, (!tweenIndex ? tDelay : 0)), target, ti, tl); 3544 const computedComposition = getFunctionValue(setValue(key.composition, tComposition), target, ti, tl); 3545 const tweenComposition = isNum(computedComposition) ? computedComposition : compositionTypes[computedComposition]; 3546 // Modifiers are treated differently and don't accept function based value to prevent having to pass a function wrapper 3547 const tweenModifier = key.modifier || tModifier; 3548 const hasFromvalue = !isUnd(tweenFromValue); 3549 const hasToValue = !isUnd(tweenToValue); 3550 const isFromToArray = isArr(tweenToValue); 3551 const isFromToValue = isFromToArray || (hasFromvalue && hasToValue); 3552 const tweenStartTime = prevTween ? lastTweenChangeEndTime + tween
vendor: 6,531 bytes, lines 3552-3660
3552Delay : tweenDelay; 3553 // Rounding is necessary here to minimize floating point errors when working in seconds 3554 const absoluteStartTime = round$1(absoluteOffsetTime + tweenStartTime, 12); 3555 3556 // Force a onRender callback if the animation contains at least one from value and autoplay is set to false 3557 if (!shouldTriggerRender && (hasFromvalue || isFromToArray)) shouldTriggerRender = 1; 3558 3559 let prevSibling = prevTween; 3560 3561 if (tweenComposition !== compositionTypes.none) { 3562 if (!siblings) siblings = getTweenSiblings(target, propName); 3563 let nextSibling = siblings._head; 3564 // Iterate trough all the next siblings until we find a sibling with an equal or inferior start time 3565 while (nextSibling && !nextSibling._isOverridden && nextSibling._absoluteStartTime <= absoluteStartTime) { 3566 prevSibling = nextSibling; 3567 nextSibling = nextSibling._nextRep; 3568 // Overrides all the next siblings if the next sibling starts at the same time of after as the new tween start time 3569 if (nextSibling && nextSibling._absoluteStartTime >= absoluteStartTime) { 3570 while (nextSibling) { 3571 overrideTween(nextSibling); 3572 // This will ends both the current while loop and the upper one once all the next sibllings have been overriden 3573 nextSibling = nextSibling._nextRep; 3574 } 3575 } 3576 } 3577 } 3578 3579 // Decompose values 3580 if (isFromToValue) { 3581 decomposeRawValue(isFromToArray ? getFunctionValue(tweenToValue[0], target, ti, tl) : tweenFromValue, fromTargetObject); 3582 decomposeRawValue(isFromToArray ? getFunctionValue(tweenToValue[1], target, ti, tl, toFunctionStore) : tweenToValue, toTargetObject); 3583 // Needed to force an inline style registration 3584 const originalValue = getOriginalAnimatableValue(target, propName, tweenType, inlineStylesStore); 3585 if (fromTargetObject.t === valueTypes.NUMBER) { 3586 if (prevSibling) { 3587 if (prevSibling._valueType === valueTypes.UNIT) { 3588 fromTargetObject.t = valueTypes.UNIT; 3589 fromTargetObject.u = prevSibling._unit; 3590 } 3591 } else { 3592 decomposeRawValue( 3593 originalValue, 3594 decomposedOriginalValue 3595 ); 3596 if (decomposedOriginalValue.t === valueTypes.UNIT) { 3597 fromTargetObject.t = valueTypes.UNIT; 3598 fromTargetObject.u = decomposedOriginalValue.u; 3599 } 3600 } 3601 } 3602 } else { 3603 if (hasToValue) { 3604 decomposeRawValue(tweenToValue, toTargetObject); 3605 } else { 3606 if (prevTween) { 3607 decomposeTweenValue(prevTween, toTargetObject); 3608 } else { 3609 // No need to get and parse the original value if the tween is part of a timeline and has a previous sibling part of the same timeline 3610 decomposeRawValue(parent && prevSibling && prevSibling.parent.parent === parent ? prevSibling._value : 3611 getOriginalAnimatableValue(target, propName, tweenType, inlineStylesStore), toTargetObject); 3612 } 3613 } 3614 if (hasFromvalue) { 3615 decomposeRawValue(tweenFromValue, fromTargetObject); 3616 } else { 3617 if (prevTween) { 3618 decomposeTweenValue(prevTween, fromTargetObject); 3619 } else { 3620 decomposeRawValue(parent && prevSibling && prevSibling.parent.parent === parent ? prevSibling._value : 3621 // No need to get and parse the original value if the tween is part of a timeline and has a previous sibling part of the same timeline 3622 getOriginalAnimatableValue(target, propName, tweenType, inlineStylesStore), fromTargetObject); 3623 } 3624 } 3625 } 3626 3627 // Apply operators 3628 if (fromTargetObject.o) { 3629 fromTargetObject.n = getRelativeValue( 3630 !prevSibling ? decomposeRawValue( 3631 getOriginalAnimatableValue(target, propName, tweenType, inlineStylesStore), 3632 decomposedOriginalValue 3633 ).n : prevSibling._toNumber, 3634 fromTargetObject.n, 3635 fromTargetObject.o 3636 ); 3637 } 3638 3639 if (toTargetObject.o) { 3640 toTargetObject.n = getRelativeValue(fromTargetObject.n, toTargetObject.n, toTargetObject.o); 3641 } 3642 3643 // Values omogenisation in cases of type difference between "from" and "to" 3644 if (fromTargetObject.t !== toTargetObject.t) { 3645 if (fromTargetObject.t === valueTypes.COMPLEX || toTargetObject.t === valueTypes.COMPLEX) { 3646 const complexValue = fromTargetObject.t === valueTypes.COMPLEX ? fromTargetObject : toTargetObject; 3647 const notComplexValue = fromTargetObject.t === valueTypes.COMPLEX ? toTargetObject : fromTargetObject; 3648 notComplexValue.t = valueTypes.COMPLEX; 3649 notComplexValue.s = cloneArray(complexValue.s); 3650 notComplexValue.d = complexValue.d.map(() => notComplexValue.n); 3651 } else if (fromTargetObject.t === valueTypes.UNIT || toTargetObject.t === valueTypes.UNIT) { 3652 const unitValue = fromTargetObject.t === valueTypes.UNIT ? fromTargetObject : toTargetObject; 3653 const notUnitValue = fromTargetObject.t === valueTypes.UNIT ? toTargetObject : fromTargetObject; 3654 notUnitValue.t = valueTypes.UNIT; 3655 notUnitValue.u = unitValue.u; 3656 } else if (fromTargetObject.t === valueTypes.COLOR || toTargetObject.t === valueTypes.COLOR) { 3657 const colorValue = fromTargetObject.t === valueTypes.COLOR ? fromTargetObject : toTargetObject; 3658 const notColorValue = fromTargetObject.t === valueTypes.COLOR ? toTargetObject : fromTargetObject; 3659 notColorValue.t = valueTypes.COLOR; 3660 notColorValue.s = colorValue.s;
vendor: 5,017 bytes, lines 3661-3769
3661 notColorValue.d = [0, 0, 0, 1]; 3662 } 3663 } 3664 3665 // Unit conversion 3666 if (fromTargetObject.u !== toTargetObject.u) { 3667 let valueToConvert = toTargetObject.u ? fromTargetObject : toTargetObject; 3668 valueToConvert = convertValueUnit(/** @type {DOMTarget} */(target), valueToConvert, toTargetObject.u ? toTargetObject.u : fromTargetObject.u, false); 3669 // TODO: 3670 // convertValueUnit(target, to.u ? from : to, to.u ? to.u : from.u); 3671 } 3672 3673 // Fill in non existing complex values 3674 if (toTargetObject.d && fromTargetObject.d && (toTargetObject.d.length !== fromTargetObject.d.length)) { 3675 const longestValue = fromTargetObject.d.length > toTargetObject.d.length ? fromTargetObject : toTargetObject; 3676 const shortestValue = longestValue === fromTargetObject ? toTargetObject : fromTargetObject; 3677 // TODO: Check if n should be used instead of 0 for default complex values 3678 shortestValue.d = longestValue.d.map((/** @type {Number} */_, /** @type {Number} */i) => isUnd(shortestValue.d[i]) ? 0 : shortestValue.d[i]); 3679 shortestValue.s = cloneArray(longestValue.s); 3680 } 3681 3682 // Tween factory 3683 3684 // Rounding is necessary here to minimize floating point errors when working in seconds 3685 const tweenUpdateDuration = round$1(+tweenDuration || minValue, 12); 3686 3687 // Copy the value of the iniline style if it exist and imediatly nullify it to prevents false positive on other targets 3688 let inlineValue = inlineStylesStore[propName]; 3689 if (!isNil(inlineValue)) inlineStylesStore[propName] = null; 3690 3691 /** @type {Tween} */ 3692 const tween = { 3693 parent: this, 3694 id: tweenId++, 3695 property: propName, 3696 target: target, 3697 _value: null, 3698 _func: toFunctionStore.func, 3699 _ease: parseEase(tweenEasing), 3700 _fromNumbers: cloneArray(fromTargetObject.d), 3701 _toNumbers: cloneArray(toTargetObject.d), 3702 _strings: cloneArray(toTargetObject.s), 3703 _fromNumber: fromTargetObject.n, 3704 _toNumber: toTargetObject.n, 3705 _numbers: cloneArray(fromTargetObject.d), // For additive tween and animatables 3706 _number: fromTargetObject.n, // For additive tween and animatables 3707 _unit: toTargetObject.u, 3708 _modifier: tweenModifier, 3709 _currentTime: 0, 3710 _startTime: tweenStartTime, 3711 _delay: +tweenDelay, 3712 _updateDuration: tweenUpdateDuration, 3713 _changeDuration: tweenUpdateDuration, 3714 _absoluteStartTime: absoluteStartTime, 3715 // NOTE: Investigate bit packing to stores ENUM / BOOL 3716 _tweenType: tweenType, 3717 _valueType: toTargetObject.t, 3718 _composition: tweenComposition, 3719 _isOverlapped: 0, 3720 _isOverridden: 0, 3721 _renderTransforms: 0, 3722 _inlineValue: inlineValue, 3723 _prevRep: null, // For replaced tween 3724 _nextRep: null, // For replaced tween 3725 _prevAdd: null, // For additive tween 3726 _nextAdd: null, // For additive tween 3727 _prev: null, 3728 _next: null, 3729 }; 3730 3731 if (tweenComposition !== compositionTypes.none) { 3732 composeTween(tween, siblings); 3733 } 3734 3735 if (isNaN(firstTweenChangeStartTime)) { 3736 firstTweenChangeStartTime = tween._startTime; 3737 } 3738 // Rounding is necessary here to minimize floating point errors when working in seconds 3739 lastTweenChangeEndTime = round$1(tweenStartTime + tweenUpdateDuration, 12); 3740 prevTween = tween; 3741 animationAnimationLength++; 3742 3743 addChild(this, tween); 3744 3745 } 3746 3747 // Update animation timings with the added tweens properties 3748 3749 if (isNaN(iterationDelay) || firstTweenChangeStartTime < iterationDelay) { 3750 iterationDelay = firstTweenChangeStartTime; 3751 } 3752 3753 if (isNaN(iterationDuration) || lastTweenChangeEndTime > iterationDuration) { 3754 iterationDuration = lastTweenChangeEndTime; 3755 } 3756 3757 // TODO: Find a way to inline tween._renderTransforms = 1 here 3758 if (tweenType === tweenTypes.TRANSFORM) { 3759 lastTransformGroupIndex = animationAnimationLength - tweenIndex; 3760 lastTransformGroupLength = animationAnimationLength; 3761 } 3762 3763 } 3764 3765 } 3766 3767 // Set _renderTransforms to last transform property to correctly render the transforms list 3768 if (!isNaN(lastTransformGroupIndex)) { 3769 let i = 0;
vendor: 14,267 bytes, lines 3770-4163
3770 forEachChildren(this, (/** @type {Tween} */tween) => { 3771 if (i >= lastTransformGroupIndex && i < lastTransformGroupLength) { 3772 tween._renderTransforms = 1; 3773 if (tween._composition === compositionTypes.blend) { 3774 forEachChildren(additive.animation, (/** @type {Tween} */additiveTween) => { 3775 if (additiveTween.id === tween.id) { 3776 additiveTween._renderTransforms = 1; 3777 } 3778 }); 3779 } 3780 } 3781 i++; 3782 }); 3783 } 3784 3785 } 3786 3787 if (!targetsLength) { 3788 console.warn(`No target found. Make sure the element you're trying to animate is accessible before creating your animation.`); 3789 } 3790 3791 if (iterationDelay) { 3792 forEachChildren(this, (/** @type {Tween} */tween) => { 3793 // If (startTime - delay) equals 0, this means the tween is at the begining of the animation so we need to trim the delay too 3794 if (!(tween._startTime - tween._delay)) { 3795 tween._delay -= iterationDelay; 3796 } 3797 tween._startTime -= iterationDelay; 3798 }); 3799 iterationDuration -= iterationDelay; 3800 } else { 3801 iterationDelay = 0; 3802 } 3803 3804 // Prevents iterationDuration to be NaN if no valid animatable props have been provided 3805 // Prevents _iterationCount to be NaN if no valid animatable props have been provided 3806 if (!iterationDuration) { 3807 iterationDuration = minValue; 3808 this.iterationCount = 0; 3809 } 3810 /** @type {TargetsArray} */ 3811 this.targets = parsedTargets; 3812 /** @type {String|Number} */ 3813 this.id = !isUnd(id) ? id : JSAnimationId; 3814 /** @type {Number} */ 3815 this.duration = iterationDuration === minValue ? minValue : clampInfinity(((iterationDuration + this._loopDelay) * this.iterationCount) - this._loopDelay) || minValue; 3816 /** @type {Callback<this>} */ 3817 this.onRender = onRender || animDefaults.onRender; 3818 /** @type {EasingFunction} */ 3819 this._ease = parsedAnimPlaybackEase; 3820 /** @type {Number} */ 3821 this._delay = iterationDelay; 3822 // NOTE: I'm keeping delay values separated from offsets in timelines because delays can override previous tweens and it could be confusing to debug a timeline with overridden tweens and no associated visible delays. 3823 // this._delay = parent ? 0 : iterationDelay; 3824 // this._offset += parent ? iterationDelay : 0; 3825 /** @type {Number} */ 3826 this.iterationDuration = iterationDuration; 3827 3828 if (!this._autoplay && shouldTriggerRender) this.onRender(this); 3829 } 3830 3831 /** 3832 * @param {Number} newDuration 3833 * @return {this} 3834 */ 3835 stretch(newDuration) { 3836 const currentDuration = this.duration; 3837 if (currentDuration === normalizeTime(newDuration)) return this; 3838 const timeScale = newDuration / currentDuration; 3839 // NOTE: Find a better way to handle the stretch of an animation after stretch = 0 3840 forEachChildren(this, (/** @type {Tween} */tween) => { 3841 // Rounding is necessary here to minimize floating point errors 3842 tween._updateDuration = normalizeTime(tween._updateDuration * timeScale); 3843 tween._changeDuration = normalizeTime(tween._changeDuration * timeScale); 3844 tween._currentTime *= timeScale; 3845 tween._startTime *= timeScale; 3846 tween._absoluteStartTime *= timeScale; 3847 }); 3848 return super.stretch(newDuration); 3849 } 3850 3851 /** 3852 * @return {this} 3853 */ 3854 refresh() { 3855 forEachChildren(this, (/** @type {Tween} */tween) => { 3856 const tweenFunc = tween._func; 3857 if (tweenFunc) { 3858 const ogValue = getOriginalAnimatableValue(tween.target, tween.property, tween._tweenType); 3859 decomposeRawValue(ogValue, decomposedOriginalValue); 3860 // TODO: Check for from / to Array based values here, 3861 decomposeRawValue(tweenFunc(), toTargetObject); 3862 tween._fromNumbers = cloneArray(decomposedOriginalValue.d); 3863 tween._fromNumber = decomposedOriginalValue.n; 3864 tween._toNumbers = cloneArray(toTargetObject.d); 3865 tween._strings = cloneArray(toTargetObject.s); 3866 // Make sure to apply relative operators https://github.com/juliangarnier/anime/issues/1025 3867 tween._toNumber = toTargetObject.o ? getRelativeValue(decomposedOriginalValue.n, toTargetObject.n, toTargetObject.o) : toTargetObject.n; 3868 } 3869 }); 3870 // This forces setter animations to render once 3871 if (this.duration === minValue) this.restart(); 3872 return this; 3873 } 3874 3875 /** 3876 * Cancel the animation and revert all the values affected by this animation to their original state 3877 * @return {this} 3878 */ 3879 revert() { 3880 super.revert(); 3881 return cleanInlineStyles(this); 3882 } 3883 3884 /** 3885 * @typedef {this & {then: null}} ResolvedJSAnimation 3886 */ 3887 3888 /** 3889 * @param {Callback<ResolvedJSAnimation>} [callback] 3890 * @return Promise<this> 3891 */ 3892 then(callback) { 3893 return super.then(callback); 3894 } 3895 3896 } 3897 3898 /** 3899 * @param {TargetsParam} targets 3900 * @param {AnimationParams} parameters 3901 * @return {JSAnimation} 3902 */ 3903 const animate = (targets, parameters) => new JSAnimation(targets, parameters, null, 0, false).init(); 3904 3905 3906 3907 3908 3909 const WAAPIAnimationsLookups = { 3910 _head: null, 3911 _tail: null, 3912 }; 3913 3914 /** 3915 * @param {DOMTarget} $el 3916 * @param {String} [property] 3917 * @param {WAAPIAnimation} [parent] 3918 * @return {globalThis.Animation} 3919 */ 3920 const removeWAAPIAnimation = ($el, property, parent) => { 3921 let nextLookup = WAAPIAnimationsLookups._head; 3922 let anim; 3923 while (nextLookup) { 3924 const next = nextLookup._next; 3925 const matchTarget = nextLookup.$el === $el; 3926 const matchProperty = !property || nextLookup.property === property; 3927 const matchParent = !parent || nextLookup.parent === parent; 3928 if (matchTarget && matchProperty && matchParent) { 3929 anim = nextLookup.animation; 3930 try { anim.commitStyles(); } catch {} anim.cancel(); 3931 removeChild(WAAPIAnimationsLookups, nextLookup); 3932 const lookupParent = nextLookup.parent; 3933 if (lookupParent) { 3934 lookupParent._completed++; 3935 if (lookupParent.animations.length === lookupParent._completed) { 3936 lookupParent.completed = true; 3937 lookupParent.paused = true; 3938 if (!lookupParent.muteCallbacks) { 3939 lookupParent.onComplete(lookupParent); 3940 lookupParent._resolve(lookupParent); 3941 } 3942 } 3943 } 3944 } 3945 nextLookup = next; 3946 } 3947 return anim; 3948 }; 3949 3950 /** 3951 * @param {WAAPIAnimation} parent 3952 * @param {DOMTarget} $el 3953 * @param {String} property 3954 * @param {PropertyIndexedKeyframes} keyframes 3955 * @param {KeyframeAnimationOptions} params 3956 * @retun {globalThis.Animation} 3957 */ 3958 const addWAAPIAnimation = (parent, $el, property, keyframes, params) => { 3959 const animation = $el.animate(keyframes, params); 3960 const animTotalDuration = params.delay + (+params.duration * params.iterations); 3961 animation.playbackRate = parent._speed; 3962 if (parent.paused) animation.pause(); 3963 if (parent.duration < animTotalDuration) { 3964 parent.duration = animTotalDuration; 3965 parent.controlAnimation = animation; 3966 } 3967 parent.animations.push(animation); 3968 removeWAAPIAnimation($el, property); 3969 addChild(WAAPIAnimationsLookups, { parent, animation, $el, property, _next: null, _prev: null }); 3970 const handleRemove = () => { removeWAAPIAnimation($el, property, parent); }; 3971 animation.oncancel = handleRemove; 3972 animation.onremove = handleRemove; 3973 if (!parent.persist) { 3974 animation.onfinish = handleRemove; 3975 } 3976 return animation; 3977 }; 3978 3979 3980 3981 3982 3983 /** 3984 * @overload 3985 * @param {DOMTargetSelector} targetSelector 3986 * @param {String} propName 3987 * @return {String} 3988 * 3989 * @overload 3990 * @param {JSTargetsParam} targetSelector 3991 * @param {String} propName 3992 * @return {Number|String} 3993 * 3994 * @overload 3995 * @param {DOMTargetsParam} targetSelector 3996 * @param {String} propName 3997 * @param {String} unit 3998 * @return {String} 3999 * 4000 * @overload 4001 * @param {TargetsParam} targetSelector 4002 * @param {String} propName 4003 * @param {Boolean} unit 4004 * @return {Number} 4005 * 4006 * @param {TargetsParam} targetSelector 4007 * @param {String} propName 4008 * @param {String|Boolean} [unit] 4009 */ 4010 function get(targetSelector, propName, unit) { 4011 const targets = registerTargets(targetSelector); 4012 if (!targets.length) return; 4013 const [ target ] = targets; 4014 const tweenType = getTweenType(target, propName); 4015 const normalizePropName = sanitizePropertyName(propName, target, tweenType); 4016 let originalValue = getOriginalAnimatableValue(target, normalizePropName); 4017 if (isUnd(unit)) { 4018 return originalValue; 4019 } else { 4020 decomposeRawValue(originalValue, decomposedOriginalValue); 4021 if (decomposedOriginalValue.t === valueTypes.NUMBER || decomposedOriginalValue.t === valueTypes.UNIT) { 4022 if (unit === false) { 4023 return decomposedOriginalValue.n; 4024 } else { 4025 const convertedValue = convertValueUnit(/** @type {DOMTarget} */(target), decomposedOriginalValue, /** @type {String} */(unit), false); 4026 return `${round$1(convertedValue.n, globals.precision)}${convertedValue.u}`; 4027 } 4028 } 4029 } 4030 } 4031 4032 /** 4033 * @param {TargetsParam} targets 4034 * @param {AnimationParams} parameters 4035 * @return {JSAnimation} 4036 */ 4037 const set = (targets, parameters) => { 4038 if (isUnd(parameters)) return; 4039 parameters.duration = minValue; 4040 // Do not overrides currently active tweens by default 4041 parameters.composition = setValue(parameters.composition, compositionTypes.none); 4042 // Skip init() and force rendering by playing the animation 4043 return new JSAnimation(targets, parameters, null, 0, true).resume(); 4044 }; 4045 4046 /** 4047 * @param {TargetsParam} targets 4048 * @param {Renderable|WAAPIAnimation} [renderable] 4049 * @param {String} [propertyName] 4050 * @return {TargetsArray} 4051 */ 4052 const remove = (targets, renderable, propertyName) => { 4053 const targetsArray = parseTargets(targets); 4054 for (let i = 0, l = targetsArray.length; i < l; i++) { 4055 removeWAAPIAnimation( 4056 /** @type {DOMTarget} */(targetsArray[i]), 4057 propertyName, 4058 renderable && /** @type {WAAPIAnimation} */(renderable).controlAnimation && /** @type {WAAPIAnimation} */(renderable), 4059 ); 4060 } 4061 removeTargetsFromRenderable( 4062 targetsArray, 4063 /** @type {Renderable} */(renderable), 4064 propertyName 4065 ); 4066 return targetsArray; 4067 }; 4068 4069 4070 4071 4072 4073 /** 4074 * Timeline's children offsets positions parser 4075 * @param {Timeline} timeline 4076 * @param {String} timePosition 4077 * @return {Number} 4078 */ 4079 const getPrevChildOffset = (timeline, timePosition) => { 4080 if (stringStartsWith(timePosition, '<')) { 4081 const goToPrevAnimationOffset = timePosition[1] === '<'; 4082 const prevAnimation = /** @type {Tickable} */(timeline._tail); 4083 const prevOffset = prevAnimation ? prevAnimation._offset + prevAnimation._delay : 0; 4084 return goToPrevAnimationOffset ? prevOffset : prevOffset + prevAnimation.duration; 4085 } 4086 }; 4087 4088 /** 4089 * @param {Timeline} timeline 4090 * @param {TimelinePosition} [timePosition] 4091 * @return {Number} 4092 */ 4093 const parseTimelinePosition = (timeline, timePosition) => { 4094 let tlDuration = timeline.iterationDuration; 4095 if (tlDuration === minValue) tlDuration = 0; 4096 if (isUnd(timePosition)) return tlDuration; 4097 if (isNum(+timePosition)) return +timePosition; 4098 const timePosStr = /** @type {String} */(timePosition); 4099 const tlLabels = timeline ? timeline.labels : null; 4100 const hasLabels = !isNil(tlLabels); 4101 const prevOffset = getPrevChildOffset(timeline, timePosStr); 4102 const hasSibling = !isUnd(prevOffset); 4103 const matchedRelativeOperator = relativeValuesExecRgx.exec(timePosStr); 4104 if (matchedRelativeOperator) { 4105 const fullOperator = matchedRelativeOperator[0]; 4106 const split = timePosStr.split(fullOperator); 4107 const labelOffset = hasLabels && split[0] ? tlLabels[split[0]] : tlDuration; 4108 const parsedOffset = hasSibling ? prevOffset : hasLabels ? labelOffset : tlDuration; 4109 const parsedNumericalOffset = +split[1]; 4110 return getRelativeValue(parsedOffset, parsedNumericalOffset, fullOperator[0]); 4111 } else { 4112 return hasSibling ? prevOffset : 4113 hasLabels ? !isUnd(tlLabels[timePosStr]) ? tlLabels[timePosStr] : 4114 tlDuration : tlDuration; 4115 } 4116 }; 4117 4118 4119 4120 4121 4122 /** 4123 * @param {Timeline} tl 4124 * @return {Number} 4125 */ 4126 function getTimelineTotalDuration(tl) { 4127 return clampInfinity(((tl.iterationDuration + tl._loopDelay) * tl.iterationCount) - tl._loopDelay) || minValue; 4128 } 4129 4130 /** 4131 * @overload 4132 * @param {TimerParams} childParams 4133 * @param {Timeline} tl 4134 * @param {Number} timePosition 4135 * @return {Timeline} 4136 * 4137 * @overload 4138 * @param {AnimationParams} childParams 4139 * @param {Timeline} tl 4140 * @param {Number} timePosition 4141 * @param {TargetsParam} targets 4142 * @param {Number} [index] 4143 * @param {Number} [length] 4144 * @return {Timeline} 4145 * 4146 * @param {TimerParams|AnimationParams} childParams 4147 * @param {Timeline} tl 4148 * @param {Number} timePosition 4149 * @param {TargetsParam} [targets] 4150 * @param {Number} [index] 4151 * @param {Number} [length] 4152 */ 4153 function addTlChild(childParams, tl, timePosition, targets, index, length) { 4154 const isSetter = isNum(childParams.duration) && /** @type {Number} */(childParams.duration) <= minValue; 4155 // Offset the tl position with -minValue for 0 duration animations or .set() calls in order to align their end value with the defined position 4156 const adjustedPosition = isSetter ? timePosition - minValue : timePosition; 4157 if (tl.composition) tick(tl, adjustedPosition, 1, 1, tickModes.AUTO); 4158 const tlChild = targets ? 4159 new JSAnimation(targets,/** @type {AnimationParams} */(childParams), tl, adjustedPosition, false, index, length) : 4160 new Timer(/** @type {TimerParams} */(childParams), tl, adjustedPosition); 4161 if (tl.composition) tlChild.init(true); 4162 // TODO: Might be better to insert at a position relative to startTime? 4163 addChild(tl, tlChild);
4164 forEachChildren(tl, (/** @type {Renderable} */child) => { 4165 const childTLOffset = child._offset + child._delay; 4166 const childDur = childTLOffset + child.duration; 4167 if (childDur > tl.iterationDuration) tl.iterationDuration = childDur; 4168 }); 4169 tl.duration = getTimelineTotalDuration(tl); 4170 return tl; 4171 } 4172 4173 let TLId = 0; 4174 4175 class Timeline extends Timer { 4176 4177 /** 4178 * @param {TimelineParams} [parameters] 4179 */ 4180 constructor(parameters = {}) { 4181 super(/** @type {TimerParams&TimelineParams} */(parameters), null, 0); 4182 ++TLId; 4183 /** @type {String|Number} */ 4184 this.id = !isUnd(parameters.id) ? parameters.id : TLId; 4185 /** @type {Number} */ 4186 this.duration = 0; // TL duration starts at 0 and grows when adding children 4187 /** @type {Record<String, Number>} */ 4188 this.labels = {}; 4189 const defaultsParams = parameters.defaults; 4190 const globalDefaults = globals.defaults; 4191 /** @type {DefaultsParams} */ 4192 this.defaults = defaultsParams ? mergeObjects(defaultsParams, globalDefaults) : globalDefaults; 4193 /** @type {Boolean} */ 4194 this.composition = setValue(parameters.composition, true); 4195 /** @type {Callback<this>} */ 4196 this.onRender = parameters.onRender || globalDefaults.onRender; 4197 const tlPlaybackEase = setValue(parameters.playbackEase, globalDefaults.playbackEase); 4198 this._ease = tlPlaybackEase ? parseEase(tlPlaybackEase) : null; 4199 /** @type {Number} */ 4200 this.iterationDuration = 0; 4201 } 4202 4203 /** 4204 * @overload 4205 * @param {TargetsParam} a1 4206 * @param {AnimationParams} a2 4207 * @param {TimelinePosition|StaggerFunction<Number|String>} [a3] 4208 * @return {this} 4209 * 4210 * @overload 4211 * @param {TimerParams} a1 4212 * @param {TimelinePosition} [a2] 4213 * @return {this} 4214 * 4215 * @param {TargetsParam|TimerParams} a1 4216 * @param {TimelinePosition|AnimationParams} a2 4217 * @param {TimelinePosition|StaggerFunction<Number|String>} [a3] 4218 */ 4219 add(a1, a2, a3) { 4220 const isAnim = isObj(a2); 4221 const isTimer = isObj(a1); 4222 if (isAnim || isTimer) { 4223 this._hasChildren = true; 4224 if (isAnim) { 4225 const childParams = /** @type {AnimationParams} */(a2); 4226 // Check for function for children stagger positions 4227 if (isFnc(a3)) { 4228 const staggeredPosition = a3; 4229 const parsedTargetsArray = parseTargets(/** @type {TargetsParam} */(a1)); 4230 // Store initial duration before adding new children that will change the duration 4231 const tlDuration = this.duration; 4232 // Store initial _iterationDuration before adding new children that will change the duration 4233 const tlIterationDuration = this.iterationDuration; 4234 // Store the original id in order to add specific indexes to the new animations ids 4235 const id = childParams.id; 4236 let i = 0; 4237 /** @type {Number} */ 4238 const parsedLength = (parsedTargetsArray.length); 4239 parsedTargetsArray.forEach((/** @type {Target} */target) => { 4240 // Create a new parameter object for each staggered children 4241 const staggeredChildParams = { ...childParams }; 4242 // Reset the duration of the timeline iteration before each stagger to prevent wrong start value calculation 4243 this.duration = tlDuration; 4244 this.iterationDuration = tlIterationDuration; 4245 if (!isUnd(id)) staggeredChildParams.id = id + '-' + i; 4246 addTlChild( 4247 staggeredChildParams, 4248 this, 4249 parseTimelinePosition(this, staggeredPosition(target, i, parsedLength, this)), 4250 target, 4251 i, 4252 parsedLength 4253 ); 4254 i++; 4255 }); 4256 } else { 4257 addTlChild( 4258 childParams, 4259 this, 4260 parseTimelinePosition(this, a3), 4261 /** @type {TargetsParam} */(a1), 4262 ); 4263 } 4264 } else { 4265 // It's a Timer 4266 addTlChild( 4267 /** @type TimerParams */(a1), 4268 this, 4269 parseTimelinePosition(this,a2), 4270 ); 4271 } 4272 if (this.composition) this.init(true); 4273 return this; 4274 } 4275 } 4276 4277 /** 4278 * @overload 4279 * @param {Tickable} [synced] 4280 * @param {TimelinePosition} [position] 4281 * @return {this} 4282 * 4283 * @overload 4284 * @param {globalThis.Animation} [synced] 4285 * @param {TimelinePosition} [position] 4286 * @return {this} 4287 * 4288 * @overload 4289 * @param {WAAPIAnimation} [synced] 4290 * @param {TimelinePosition} [position] 4291 * @return {this} 4292 * 4293 * @param {Tickable|WAAPIAnimation|globalThis.Animation} [synced] 4294 * @param {TimelinePosition} [position] 4295 */ 4296 sync(synced, position) { 4297 if (isUnd(synced) || synced && isUnd(synced.pause)) return this; 4298 synced.pause(); 4299 const duration = +(/** @type {globalThis.Animation} */(synced).effect ? /** @type {globalThis.Animation} */(synced).effect.getTiming().duration : /** @type {Tickable} */(synced).duration); 4300 return this.add(synced, { currentTime: [0, duration], duration, delay: 0, ease: 'linear', playbackEase: 'linear' }, position); 4301 } 4302 4303 /** 4304 * @param {TargetsParam} targets 4305 * @param {AnimationParams} parameters 4306 * @param {TimelinePosition} [position] 4307 * @return {this} 4308 */ 4309 set(targets, parameters, position) { 4310 if (isUnd(parameters)) return this; 4311 parameters.duration = minValue; 4312 parameters.composition = compositionTypes.replace; 4313 return this.add(targets, parameters, position); 4314 } 4315 4316 /** 4317 * @param {Callback<Timer>} callback 4318 * @param {TimelinePosition} [position] 4319 * @return {this} 4320 */ 4321 call(callback, position) {
vendor: 3,147 bytes, lines 4322-4436
4322 if (isUnd(callback) || callback && !isFnc(callback)) return this; 4323 return this.add({ duration: 0, delay: 0, onComplete: () => callback(this) }, position); 4324 } 4325 4326 /** 4327 * @param {String} labelName 4328 * @param {TimelinePosition} [position] 4329 * @return {this} 4330 * 4331 */ 4332 label(labelName, position) { 4333 if (isUnd(labelName) || labelName && !isStr(labelName)) return this; 4334 this.labels[labelName] = parseTimelinePosition(this, position); 4335 return this; 4336 } 4337 4338 /** 4339 * @param {TargetsParam} targets 4340 * @param {String} [propertyName] 4341 * @return {this} 4342 */ 4343 remove(targets, propertyName) { 4344 removeTargetsFromRenderable(parseTargets(targets), this, propertyName); 4345 return this; 4346 } 4347 4348 /** 4349 * @param {Number} newDuration 4350 * @return {this} 4351 */ 4352 stretch(newDuration) { 4353 const currentDuration = this.duration; 4354 if (currentDuration === normalizeTime(newDuration)) return this; 4355 const timeScale = newDuration / currentDuration; 4356 const labels = this.labels; 4357 forEachChildren(this, (/** @type {JSAnimation} */child) => child.stretch(child.duration * timeScale)); 4358 for (let labelName in labels) labels[labelName] *= timeScale; 4359 return super.stretch(newDuration); 4360 } 4361 4362 /** 4363 * @return {this} 4364 */ 4365 refresh() { 4366 forEachChildren(this, (/** @type {JSAnimation} */child) => { 4367 if (child.refresh) child.refresh(); 4368 }); 4369 return this; 4370 } 4371 4372 /** 4373 * @return {this} 4374 */ 4375 revert() { 4376 super.revert(); 4377 forEachChildren(this, (/** @type {JSAnimation} */child) => child.revert, true); 4378 return cleanInlineStyles(this); 4379 } 4380 4381 /** 4382 * @typedef {this & {then: null}} ResolvedTimeline 4383 */ 4384 4385 /** 4386 * @param {Callback<ResolvedTimeline>} [callback] 4387 * @return Promise<this> 4388 */ 4389 then(callback) { 4390 return super.then(callback); 4391 } 4392 } 4393 4394 /** 4395 * @param {TimelineParams} [parameters] 4396 * @return {Timeline} 4397 */ 4398 const createTimeline = parameters => new Timeline(parameters).init(); 4399 4400 4401 4402 class Animatable { 4403 /** 4404 * @param {TargetsParam} targets 4405 * @param {AnimatableParams} parameters 4406 */ 4407 constructor(targets, parameters) { 4408 if (scope.current) scope.current.register(this); 4409 const beginHandler = () => { 4410 if (this.callbacks.completed) this.callbacks.reset(); 4411 this.callbacks.play(); 4412 }; 4413 const pauseHandler = () => { 4414 if (this.callbacks.completed) return; 4415 let paused = true; 4416 for (let name in this.animations) { 4417 const anim = this.animations[name]; 4418 if (!anim.paused && paused) { 4419 paused = false; 4420 break; 4421 } 4422 } 4423 if (paused) { 4424 this.callbacks.complete(); 4425 } 4426 }; 4427 /** @type {AnimationParams} */ 4428 const globalParams = { 4429 onBegin: beginHandler, 4430 onComplete: pauseHandler, 4431 onPause: pauseHandler, 4432 }; 4433 /** @type {AnimationParams} */ 4434 const callbacksAnimationParams = { v: 1, autoplay: false }; 4435 const properties = {}; 4436 this.targets = [];
vendor: 59,905 bytes, lines 4436-6047
4436 4437 this.animations = {}; 4438 /** @type {JSAnimation|null} */ 4439 this.callbacks = null; 4440 if (isUnd(targets) || isUnd(parameters)) return; 4441 for (let propName in parameters) { 4442 const paramValue = parameters[propName]; 4443 if (isKey(propName)) { 4444 properties[propName] = paramValue; 4445 } else if (stringStartsWith(propName, 'on')) { 4446 callbacksAnimationParams[propName] = paramValue; 4447 } else { 4448 globalParams[propName] = paramValue; 4449 } 4450 } 4451 this.callbacks = new JSAnimation({ v: 0 }, callbacksAnimationParams); 4452 for (let propName in properties) { 4453 const propValue = properties[propName]; 4454 const isObjValue = isObj(propValue); 4455 /** @type {TweenParamsOptions} */ 4456 let propParams = {}; 4457 let to = '+=0'; 4458 if (isObjValue) { 4459 const unit = propValue.unit; 4460 if (isStr(unit)) to += unit; 4461 } else { 4462 propParams.duration = propValue; 4463 } 4464 propParams[propName] = isObjValue ? mergeObjects({ to }, propValue) : to; 4465 const animParams = mergeObjects(globalParams, propParams); 4466 animParams.composition = compositionTypes.replace; 4467 animParams.autoplay = false; 4468 const animation = this.animations[propName] = new JSAnimation(targets, animParams, null, 0, false).init(); 4469 if (!this.targets.length) this.targets.push(...animation.targets); 4470 /** @type {AnimatableProperty} */ 4471 this[propName] = (to, duration, ease) => { 4472 const tween = /** @type {Tween} */(animation._head); 4473 if (isUnd(to) && tween) { 4474 const numbers = tween._numbers; 4475 if (numbers && numbers.length) { 4476 return numbers; 4477 } else { 4478 return tween._modifier(tween._number); 4479 } 4480 } else { 4481 forEachChildren(animation, (/** @type {Tween} */tween) => { 4482 if (isArr(to)) { 4483 for (let i = 0, l = /** @type {Array} */(to).length; i < l; i++) { 4484 if (!isUnd(tween._numbers[i])) { 4485 tween._fromNumbers[i] = /** @type {Number} */(tween._modifier(tween._numbers[i])); 4486 tween._toNumbers[i] = to[i]; 4487 } 4488 } 4489 } else { 4490 tween._fromNumber = /** @type {Number} */(tween._modifier(tween._number)); 4491 tween._toNumber = /** @type {Number} */(to); 4492 } 4493 if (!isUnd(ease)) tween._ease = parseEase(ease); 4494 tween._currentTime = 0; 4495 }); 4496 if (!isUnd(duration)) animation.stretch(duration); 4497 animation.reset(true).resume(); 4498 return this; 4499 } 4500 }; 4501 } 4502 } 4503 4504 revert() { 4505 for (let propName in this.animations) { 4506 this[propName] = noop; 4507 this.animations[propName].revert(); 4508 } 4509 this.animations = {}; 4510 this.targets.length = 0; 4511 if (this.callbacks) this.callbacks.revert(); 4512 return this; 4513 } 4514 } 4515 4516 /** 4517 * @param {TargetsParam} targets 4518 * @param {AnimatableParams} parameters 4519 * @return {AnimatableObject} 4520 */ 4521 const createAnimatable = (targets, parameters) => /** @type {AnimatableObject} */ (new Animatable(targets, parameters)); 4522 4523 /** 4524 * Rounds a number to fixed decimal places 4525 * @param {Number|String} v - Value to round 4526 * @param {Number} decimalLength - Number of decimal places 4527 * @return {String} 4528 */ 4529 const roundPad$1 = (v, decimalLength) => (+v).toFixed(decimalLength); 4530 4531 /** 4532 * Pads the start of a value with a string 4533 * @param {Number} v - Value to pad 4534 * @param {Number} totalLength - Target length 4535 * @param {String} padString - String to pad with 4536 * @return {String} 4537 */ 4538 const padStart$1 = (v, totalLength, padString) => `${v}`.padStart(totalLength, padString); 4539 4540 /** 4541 * Pads the end of a value with a string 4542 * @param {Number} v - Value to pad 4543 * @param {Number} totalLength - Target length 4544 * @param {String} padString - String to pad with 4545 * @return {String} 4546 */ 4547 const padEnd$1 = (v, totalLength, padString) => `${v}`.padEnd(totalLength, padString); 4548 4549 /** 4550 * Wraps a value within a range 4551 * @param {Number} v - Value to wrap 4552 * @param {Number} min - Minimum boundary 4553 * @param {Number} max - Maximum boundary 4554 * @return {Number} 4555 */ 4556 const wrap$1 = (v, min, max) => (((v - min) % (max - min) + (max - min)) % (max - min)) + min; 4557 4558 /** 4559 * Maps a value from one range to another 4560 * @param {Number} value - Input value 4561 * @param {Number} inLow - Input range minimum 4562 * @param {Number} inHigh - Input range maximum 4563 * @param {Number} outLow - Output range minimum 4564 * @param {Number} outHigh - Output range maximum 4565 * @return {Number} 4566 */ 4567 const mapRange$1 = (value, inLow, inHigh, outLow, outHigh) => outLow + ((value - inLow) / (inHigh - inLow)) * (outHigh - outLow); 4568 4569 /** 4570 * Converts degrees to radians 4571 * @param {Number} degrees - Angle in degrees 4572 * @return {Number} 4573 */ 4574 const degToRad$1 = degrees => degrees * Math.PI / 180; 4575 4576 /** 4577 * Converts radians to degrees 4578 * @param {Number} radians - Angle in radians 4579 * @return {Number} 4580 */ 4581 const radToDeg$1 = radians => radians * 180 / Math.PI; 4582 4583 /** 4584 * Frame rate independent damped lerp 4585 * Based on: https://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ 4586 * 4587 * @param {Number} start - Starting value 4588 * @param {Number} end - Target value 4589 * @param {Number} deltaTime - Delta time in ms 4590 * @param {Number} factor - Interpolation factor in the range [0, 1] 4591 * @return {Number} The interpolated value 4592 */ 4593 const damp$1 = (start, end, deltaTime, factor) => { 4594 return !factor ? start : factor === 1 ? end : lerp$1(start, end, 1 - Math.exp(-factor * deltaTime * .1)); 4595 }; 4596 4597 var numberImports = /*#__PURE__*/Object.freeze({ 4598 __proto__: null, 4599 clamp: clamp$1, 4600 damp: damp$1, 4601 degToRad: degToRad$1, 4602 lerp: lerp$1, 4603 mapRange: mapRange$1, 4604 padEnd: padEnd$1, 4605 padStart: padStart$1, 4606 radToDeg: radToDeg$1, 4607 round: round$1, 4608 roundPad: roundPad$1, 4609 snap: snap$1, 4610 wrap: wrap$1 4611 }); 4612 4613 4614 4615 4616 4617 /* 4618 * Spring easing solver adapted from https://webkit.org/demos/spring/spring.js 4619 * (c) 2016 Webkit - Apple Inc 4620 */ 4621 4622 const maxSpringParamValue = K * 10; 4623 4624 class Spring { 4625 /** 4626 * @param {SpringParams} [parameters] 4627 */ 4628 constructor(parameters = {}) { 4629 const hasBounceOrDuration = !isUnd(parameters.bounce) || !isUnd(parameters.duration); 4630 this.timeStep = .02; // Interval fed to the solver to calculate duration 4631 this.restThreshold = .0005; // Values below this threshold are considered resting position 4632 this.restDuration = 200; // Duration in ms used to check if the spring is resting after reaching restThreshold 4633 this.maxDuration = 60000; // The maximum allowed spring duration in ms (default 1 min) 4634 this.maxRestSteps = this.restDuration / this.timeStep / K; // How many steps allowed after reaching restThreshold before stopping the duration calculation 4635 this.maxIterations = this.maxDuration / this.timeStep / K; // Calculate the maximum iterations allowed based on maxDuration 4636 this.bn = clamp$1(setValue(parameters.bounce, .5), -1, 1); // The bounce percentage between -1 and 1. 4637 this.pd = clamp$1(setValue(parameters.duration, 628), 10 * globals.timeScale, maxSpringParamValue * globals.timeScale); // The perceived duration 4638 this.m = clamp$1(setValue(parameters.mass, 1), 1, maxSpringParamValue); 4639 this.s = clamp$1(setValue(parameters.stiffness, 100), minValue, maxSpringParamValue); 4640 this.d = clamp$1(setValue(parameters.damping, 10), minValue, maxSpringParamValue); 4641 this.v = clamp$1(setValue(parameters.velocity, 0), -maxSpringParamValue, maxSpringParamValue); 4642 this.w0 = 0; 4643 this.zeta = 0; 4644 this.wd = 0; 4645 this.b = 0; 4646 this.completed = false; 4647 this.solverDuration = 0; 4648 this.settlingDuration = 0; 4649 /** @type {JSAnimation} */ 4650 this.parent = null; 4651 /** @type {Callback<JSAnimation>} */ 4652 this.onComplete = parameters.onComplete || noop; 4653 if (hasBounceOrDuration) this.calculateSDFromBD(); 4654 this.compute(); 4655 /** @type {EasingFunction} */ 4656 this.ease = t => { 4657 const currentTime = t * this.settlingDuration; 4658 const completed = this.completed; 4659 const perceivedTime = this.pd; 4660 if (currentTime >= perceivedTime && !completed) { 4661 this.completed = true; 4662 this.onComplete(this.parent); 4663 } 4664 if (currentTime < perceivedTime && completed) { 4665 this.completed = false; 4666 } 4667 return t === 0 || t === 1 ? t : this.solve(t * this.solverDuration); 4668 }; 4669 } 4670 4671 /** @type {EasingFunction} */ 4672 solve(time) { 4673 const { zeta, w0, wd, b } = this; 4674 let t = time; 4675 if (zeta < 1) { 4676 // Underdamped 4677 t = exp(-t * zeta * w0) * (1 * cos(wd * t) + b * sin(wd * t)); 4678 } else if (zeta === 1) { 4679 // Critically damped 4680 t = (1 + b * t) * exp(-t * w0); 4681 } else { 4682 // Overdamped 4683 // Using exponential instead of cosh and sinh functions to prevent Infinity 4684 // Original exp(-zeta * w0 * t) * (cosh(wd * t) + b * sinh(wd * t)) 4685 t = ((1 + b) * exp((-zeta * w0 + wd) * t) + (1 - b) * exp((-zeta * w0 - wd) * t)) / 2; 4686 } 4687 return 1 - t; 4688 } 4689 4690 calculateSDFromBD() { 4691 // Apple's SwiftUI perceived spring duration implementation https://developer.apple.com/videos/play/wwdc2023/10158/?time=1010 4692 // Equations taken from Kevin Grajeda's article https://www.kvin.me/posts/effortless-ui-spring-animations 4693 const pds = globals.timeScale === 1 ? this.pd / K : this.pd; 4694 // Mass and velocity should be set to their default values 4695 this.m = 1; 4696 this.v = 0; 4697 // Stiffness = (2Ï Ã· perceptualDuration)² 4698 this.s = pow((2 * PI) / pds, 2); 4699 if (this.bn >= 0) { 4700 // For bounce ⥠0 (critically damped to underdamped) 4701 // damping = ((1 - bounce) à 4Ï) ÷ perceptualDuration 4702 this.d = ((1 - this.bn) * 4 * PI) / pds; 4703 } else { 4704 // For bounce < 0 (overdamped) 4705 // damping = 4Ï Ã· (perceptualDuration à (1 + bounce)) 4706 // Note: (1 + bounce) is positive since bounce is negative 4707 this.d = (4 * PI) / (pds * (1 + this.bn)); 4708 } 4709 this.s = round$1(clamp$1(this.s, minValue, maxSpringParamValue), 3); 4710 this.d = round$1(clamp$1(this.d, minValue, 300), 3); // Clamping to 300 is needed to prevent insane values in the solver 4711 } 4712 4713 calculateBDFromSD() { 4714 // Calculate perceived duration and bounce from stiffness and damping 4715 // Note: We assumes m = 1 and v = 0 for these calculations 4716 const pds = (2 * PI) / sqrt(this.s); 4717 this.pd = pds * (globals.timeScale === 1 ? K : 1); 4718 const zeta = this.d / (2 * sqrt(this.s)); 4719 if (zeta <= 1) { 4720 // Critically damped to underdamped 4721 this.bn = 1 - (this.d * pds) / (4 * PI); 4722 } else { 4723 // Overdamped 4724 this.bn = (4 * PI) / (this.d * pds) - 1; 4725 } 4726 this.bn = round$1(clamp$1(this.bn, -1, 1), 3); 4727 this.pd = round$1(clamp$1(this.pd, 10 * globals.timeScale, maxSpringParamValue * globals.timeScale), 3); 4728 } 4729 4730 compute() { 4731 const { maxRestSteps, maxIterations, restThreshold, timeStep, m, d, s, v } = this; 4732 const w0 = this.w0 = clamp$1(sqrt(s / m), minValue, K); 4733 const bouncedZeta = this.zeta = d / (2 * sqrt(s * m)); 4734 // Calculate wd based on damping type 4735 if (bouncedZeta < 1) { 4736 // Underdamped 4737 this.wd = w0 * sqrt(1 - bouncedZeta * bouncedZeta); 4738 this.b = (bouncedZeta * w0 + -v) / this.wd; 4739 } else if (bouncedZeta === 1) { 4740 // Critically damped 4741 this.wd = 0; 4742 this.b = -v + w0; 4743 } else { 4744 // Overdamped 4745 this.wd = w0 * sqrt(bouncedZeta * bouncedZeta - 1); 4746 this.b = (bouncedZeta * w0 + -v) / this.wd; 4747 } 4748 4749 let solverTime = 0; 4750 let restSteps = 0; 4751 let iterations = 0; 4752 while (restSteps <= maxRestSteps && iterations <= maxIterations) { 4753 if (abs(1 - this.solve(solverTime)) < restThreshold) { 4754 restSteps++; 4755 } else { 4756 restSteps = 0; 4757 } 4758 this.solverDuration = solverTime; 4759 solverTime += timeStep; 4760 iterations++; 4761 } 4762 this.settlingDuration = round$1(this.solverDuration * K, 0) * globals.timeScale; 4763 } 4764 4765 get bounce() { 4766 return this.bn; 4767 } 4768 4769 set bounce(v) { 4770 this.bn = clamp$1(setValue(v, 1), -1, 1); 4771 this.calculateSDFromBD(); 4772 this.compute(); 4773 } 4774 4775 get duration() { 4776 return this.pd; 4777 } 4778 4779 set duration(v) { 4780 this.pd = clamp$1(setValue(v, 1), 10 * globals.timeScale, maxSpringParamValue * globals.timeScale); 4781 this.calculateSDFromBD(); 4782 this.compute(); 4783 } 4784 4785 get stiffness() { 4786 return this.s; 4787 } 4788 4789 set stiffness(v) { 4790 this.s = clamp$1(setValue(v, 100), minValue, maxSpringParamValue); 4791 this.calculateBDFromSD(); 4792 this.compute(); 4793 } 4794 4795 get damping() { 4796 return this.d; 4797 } 4798 4799 set damping(v) { 4800 this.d = clamp$1(setValue(v, 10), minValue, maxSpringParamValue); 4801 this.calculateBDFromSD(); 4802 this.compute(); 4803 } 4804 4805 get mass() { 4806 return this.m; 4807 } 4808 4809 set mass(v) { 4810 this.m = clamp$1(setValue(v, 1), 1, maxSpringParamValue); 4811 this.compute(); 4812 } 4813 4814 get velocity() { 4815 return this.v; 4816 } 4817 4818 set velocity(v) { 4819 this.v = clamp$1(setValue(v, 0), -maxSpringParamValue, maxSpringParamValue); 4820 this.compute(); 4821 } 4822 } 4823 4824 /** 4825 * @param {SpringParams} [parameters] 4826 * @returns {Spring} 4827 */ 4828 const spring = (parameters) => new Spring(parameters); 4829 4830 /** 4831 * @deprecated createSpring() is deprecated use spring() instead 4832 * 4833 * @param {SpringParams} [parameters] 4834 * @returns {Spring} 4835 */ 4836 const createSpring = (parameters) => { 4837 console.warn('createSpring() is deprecated use spring() instead'); 4838 return new Spring(parameters); 4839 }; 4840 4841 4842 4843 4844 4845 /** 4846 * @param {Event} e 4847 */ 4848 const preventDefault = e => { 4849 if (e.cancelable) e.preventDefault(); 4850 }; 4851 4852 class DOMProxy { 4853 /** @param {Object} el */ 4854 constructor(el) { 4855 this.el = el; 4856 this.zIndex = 0; 4857 this.parentElement = null; 4858 this.classList = { 4859 add: noop, 4860 remove: noop, 4861 }; 4862 } 4863 4864 get x() { return this.el.x || 0 }; 4865 set x(v) { this.el.x = v; }; 4866 4867 get y() { return this.el.y || 0 }; 4868 set y(v) { this.el.y = v; }; 4869 4870 get width() { return this.el.width || 0 }; 4871 set width(v) { this.el.width = v; }; 4872 4873 get height() { return this.el.height || 0 }; 4874 set height(v) { this.el.height = v; }; 4875 4876 getBoundingClientRect() { 4877 return { 4878 top: this.y, 4879 right: this.x, 4880 bottom: this.y + this.height, 4881 left: this.x + this.width, 4882 } 4883 } 4884 } 4885 4886 class Transforms { 4887 /** 4888 * @param {DOMTarget|DOMProxy} $el 4889 */ 4890 constructor($el) { 4891 this.$el = $el; 4892 this.inlineTransforms = []; 4893 this.point = new DOMPoint(); 4894 this.inversedMatrix = this.getMatrix().inverse(); 4895 } 4896 4897 /** 4898 * @param {Number} x 4899 * @param {Number} y 4900 * @return {DOMPoint} 4901 */ 4902 normalizePoint(x, y) { 4903 this.point.x = x; 4904 this.point.y = y; 4905 return this.point.matrixTransform(this.inversedMatrix); 4906 } 4907 4908 /** 4909 * @callback TraverseParentsCallback 4910 * @param {DOMTarget} $el 4911 * @param {Number} i 4912 */ 4913 4914 /** 4915 * @param {TraverseParentsCallback} cb 4916 */ 4917 traverseUp(cb) { 4918 let $el = /** @type {DOMTarget|Document} */(this.$el.parentElement), i = 0; 4919 while ($el && $el !== doc) { 4920 cb(/** @type {DOMTarget} */($el), i); 4921 $el = /** @type {DOMTarget} */($el.parentElement); 4922 i++; 4923 } 4924 } 4925 4926 getMatrix() { 4927 const matrix = new DOMMatrix(); 4928 this.traverseUp($el => { 4929 const transformValue = getComputedStyle($el).transform; 4930 if (transformValue) { 4931 const elMatrix = new DOMMatrix(transformValue); 4932 matrix.preMultiplySelf(elMatrix); 4933 } 4934 }); 4935 return matrix; 4936 } 4937 4938 remove() { 4939 this.traverseUp(($el, i) => { 4940 this.inlineTransforms[i] = $el.style.transform; 4941 $el.style.transform = 'none'; 4942 }); 4943 } 4944 4945 revert() { 4946 this.traverseUp(($el, i) => { 4947 const ct = this.inlineTransforms[i]; 4948 if (ct === '') { 4949 $el.style.removeProperty('transform'); 4950 } else { 4951 $el.style.transform = ct; 4952 } 4953 }); 4954 } 4955 } 4956 4957 /** 4958 * @template {Array<Number>|DOMTargetSelector|String|Number|Boolean|Function|DraggableCursorParams|DraggableDragThresholdParams} T 4959 * @param {T | ((draggable: Draggable) => T)} value 4960 * @param {Draggable} draggable 4961 * @return {T} 4962 */ 4963 const parseDraggableFunctionParameter = (value, draggable) => value && isFnc(value) ? /** @type {Function} */(value)(draggable) : /** @type {T} */(value); 4964 4965 let zIndex = 0; 4966 4967 class Draggable { 4968 /** 4969 * @param {TargetsParam} target 4970 * @param {DraggableParams} [parameters] 4971 */ 4972 constructor(target, parameters = {}) { 4973 if (!target) return; 4974 if (scope.current) scope.current.register(this); 4975 const paramX = parameters.x; 4976 const paramY = parameters.y; 4977 const trigger = parameters.trigger; 4978 const modifier = parameters.modifier; 4979 const ease = parameters.releaseEase; 4980 const customEase = ease && parseEase(ease); 4981 const hasSpring = !isUnd(ease) && !isUnd(/** @type {Spring} */(ease).ease); 4982 const xProp = /** @type {String} */(isObj(paramX) && !isUnd(/** @type {Object} */(paramX).mapTo) ? /** @type {Object} */(paramX).mapTo : 'translateX'); 4983 const yProp = /** @type {String} */(isObj(paramY) && !isUnd(/** @type {Object} */(paramY).mapTo) ? /** @type {Object} */(paramY).mapTo : 'translateY'); 4984 const container = parseDraggableFunctionParameter(parameters.container, this); 4985 this.containerArray = isArr(container) ? container : null; 4986 this.$container = /** @type {HTMLElement} */(container && !this.containerArray ? parseTargets(/** @type {DOMTarget} */(container))[0] : doc.body); 4987 this.useWin = this.$container === doc.body; 4988 /** @type {Window | HTMLElement} */ 4989 this.$scrollContainer = this.useWin ? win : this.$container; 4990 this.$target = /** @type {HTMLElement} */(isObj(target) ? new DOMProxy(target) : parseTargets(target)[0]); 4991 this.$trigger = /** @type {HTMLElement} */(parseTargets(trigger ? trigger : target)[0]); 4992 this.fixed = get(this.$target, 'position') === 'fixed'; 4993 // Refreshable parameters 4994 this.isFinePointer = true; 4995 /** @type {[Number, Number, Number, Number]} */ 4996 this.containerPadding = [0, 0, 0, 0]; 4997 /** @type {Number} */ 4998 this.containerFriction = 0; 4999 /** @type {Number} */ 5000 this.releaseContainerFriction = 0; 5001 /** @type {Number|Array<Number>} */ 5002 this.snapX = 0; 5003 /** @type {Number|Array<Number>} */ 5004 this.snapY = 0; 5005 /** @type {Number} */ 5006 this.scrollSpeed = 0; 5007 /** @type {Number} */ 5008 this.scrollThreshold = 0; 5009 /** @type {Number} */ 5010 this.dragSpeed = 0; 5011 /** @type {Number} */ 5012 this.dragThreshold = 3; 5013 /** @type {Number} */ 5014 this.maxVelocity = 0; 5015 /** @type {Number} */ 5016 this.minVelocity = 0; 5017 /** @type {Number} */ 5018 this.velocityMultiplier = 0; 5019 /** @type {Boolean|DraggableCursorParams} */ 5020 this.cursor = false; 5021 /** @type {Spring} */ 5022 this.releaseXSpring = hasSpring ? /** @type {Spring} */(ease) : spring({ 5023 mass: setValue(parameters.releaseMass, 1), 5024 stiffness: setValue(parameters.releaseStiffness, 80), 5025 damping: setValue(parameters.releaseDamping, 20), 5026 }); 5027 /** @type {Spring} */ 5028 this.releaseYSpring = hasSpring ? /** @type {Spring} */(ease) : spring({ 5029 mass: setValue(parameters.releaseMass, 1), 5030 stiffness: setValue(parameters.releaseStiffness, 80), 5031 damping: setValue(parameters.releaseDamping, 20), 5032 }); 5033 /** @type {EasingFunction} */ 5034 this.releaseEase = customEase || eases.outQuint; 5035 /** @type {Boolean} */ 5036 this.hasReleaseSpring = hasSpring; 5037 /** @type {Callback<this>} */ 5038 this.onGrab = parameters.onGrab || noop; 5039 /** @type {Callback<this>} */ 5040 this.onDrag = parameters.onDrag || noop; 5041 /** @type {Callback<this>} */ 5042 this.onRelease = parameters.onRelease || noop; 5043 /** @type {Callback<this>} */ 5044 this.onUpdate = parameters.onUpdate || noop; 5045 /** @type {Callback<this>} */ 5046 this.onSettle = parameters.onSettle || noop; 5047 /** @type {Callback<this>} */ 5048 this.onSnap = parameters.onSnap || noop; 5049 /** @type {Callback<this>} */ 5050 this.onResize = parameters.onResize || noop; 5051 /** @type {Callback<this>} */ 5052 this.onAfterResize = parameters.onAfterResize || noop; 5053 /** @type {[Number, Number]} */ 5054 this.disabled = [0, 0]; 5055 /** @type {AnimatableParams} */ 5056 const animatableParams = {}; 5057 if (modifier) animatableParams.modifier = modifier; 5058 if (isUnd(paramX) || paramX === true) { 5059 animatableParams[xProp] = 0; 5060 } else if (isObj(paramX)) { 5061 const paramXObject = /** @type {DraggableAxisParam} */(paramX); 5062 const animatableXParams = {}; 5063 if (paramXObject.modifier) animatableXParams.modifier = paramXObject.modifier; 5064 if (paramXObject.composition) animatableXParams.composition = paramXObject.composition; 5065 animatableParams[xProp] = animatableXParams; 5066 } else if (paramX === false) { 5067 animatableParams[xProp] = 0; 5068 this.disabled[0] = 1; 5069 } 5070 if (isUnd(paramY) || paramY === true) { 5071 animatableParams[yProp] = 0; 5072 } else if (isObj(paramY)) { 5073 const paramYObject = /** @type {DraggableAxisParam} */(paramY); 5074 const animatableYParams = {}; 5075 if (paramYObject.modifier) animatableYParams.modifier = paramYObject.modifier; 5076 if (paramYObject.composition) animatableYParams.composition = paramYObject.composition; 5077 animatableParams[yProp] = animatableYParams; 5078 } else if (paramY === false) { 5079 animatableParams[yProp] = 0; 5080 this.disabled[1] = 1; 5081 } 5082 /** @type {AnimatableObject} */ 5083 this.animate = /** @type {AnimatableObject} */(new Animatable(this.$target, animatableParams)); 5084 // Internal props 5085 this.xProp = xProp; 5086 this.yProp = yProp; 5087 this.destX = 0; 5088 this.destY = 0; 5089 this.deltaX = 0; 5090 this.deltaY = 0; 5091 this.scroll = {x: 0, y: 0}; 5092 /** @type {[Number, Number, Number, Number]} */ 5093 this.coords = [this.x, this.y, 0, 0]; // x, y, temp x, temp y 5094 /** @type {[Number, Number]} */ 5095 this.snapped = [0, 0]; // x, y 5096 /** @type {[Number, Number, Number, Number, Number, Number, Number, Number]} */ 5097 this.pointer = [0, 0, 0, 0, 0, 0, 0, 0]; // x1, y1, x2, y2, temp x1, temp y1, temp x2, temp y2 5098 /** @type {[Number, Number]} */ 5099 this.scrollView = [0, 0]; // w, h 5100 /** @type {[Number, Number, Number, Number]} */ 5101 this.dragArea = [0, 0, 0, 0]; // x, y, w, h 5102 /** @type {[Number, Number, Number, Number]} */ 5103 this.containerBounds = [-maxValue, maxValue, maxValue, -maxValue]; // t, r, b, l 5104 /** @type {[Number, Number, Number, Number]} */ 5105 this.scrollBounds = [0, 0, 0, 0]; // t, r, b, l 5106 /** @type {[Number, Number, Number, Number]} */ 5107 this.targetBounds = [0, 0, 0, 0]; // t, r, b, l 5108 /** @type {[Number, Number]} */ 5109 this.window = [0, 0]; // w, h 5110 /** @type {[Number, Number, Number]} */ 5111 this.velocityStack = [0, 0, 0]; 5112 /** @type {Number} */ 5113 this.velocityStackIndex = 0; 5114 /** @type {Number} */ 5115 this.velocityTime = now(); 5116 /** @type {Number} */ 5117 this.velocity = 0; 5118 /** @type {Number} */ 5119 this.angle = 0; 5120 /** @type {JSAnimation} */ 5121 this.cursorStyles = null; 5122 /** @type {JSAnimation} */ 5123 this.triggerStyles = null; 5124 /** @type {JSAnimation} */ 5125 this.bodyStyles = null; 5126 /** @type {JSAnimation} */ 5127 this.targetStyles = null; 5128 /** @type {JSAnimation} */ 5129 this.touchActionStyles = null; 5130 this.transforms = new Transforms(this.$target); 5131 this.overshootCoords = { x: 0, y: 0 }; 5132 this.overshootTicker = new Timer({ 5133 autoplay: false, 5134 onUpdate: () => { 5135 this.updated = true; 5136 this.manual = true; 5137 // Use a duration of 1 to prevent the animatable from completing immediately to prevent issues with onSettle() 5138 // https://github.com/juliangarnier/anime/issues/1045 5139 if (!this.disabled[0]) this.animate[this.xProp](this.overshootCoords.x, 1); 5140 if (!this.disabled[1]) this.animate[this.yProp](this.overshootCoords.y, 1); 5141 }, 5142 onComplete: () => { 5143 this.manual = false; 5144 if (!this.disabled[0]) this.animate[this.xProp](this.overshootCoords.x, 0); 5145 if (!this.disabled[1]) this.animate[this.yProp](this.overshootCoords.y, 0); 5146 }, 5147 }, null, 0).init(); 5148 this.updateTicker = new Timer({ autoplay: false, onUpdate: () => this.update() }, null,0,).init(); 5149 this.contained = !isUnd(container); 5150 this.manual = false; 5151 this.grabbed = false; 5152 this.dragged = false; 5153 this.updated = false; 5154 this.released = false; 5155 this.canScroll = false; 5156 this.enabled = false; 5157 this.initialized = false; 5158 this.activeProp = this.disabled[1] ? xProp : yProp; 5159 this.animate.callbacks.onRender = () => { 5160 const hasUpdated = this.updated; 5161 const hasMoved = this.grabbed && hasUpdated; 5162 const hasReleased = !hasMoved && this.released; 5163 const x = this.x; 5164 const y = this.y; 5165 const dx = x - this.coords[2]; 5166 const dy = y - this.coords[3]; 5167 this.deltaX = dx; 5168 this.deltaY = dy; 5169 this.coords[2] = x; 5170 this.coords[3] = y; 5171 // Check if dx or dy are not 0 to check if the draggable has actually moved 5172 // https://github.com/juliangarnier/anime/issues/1032 5173 if (hasUpdated && (dx || dy)) { 5174 this.onUpdate(this); 5175 } 5176 if (!hasReleased) { 5177 this.updated = false; 5178 } else { 5179 this.computeVelocity(dx, dy); 5180 this.angle = atan2(dy, dx); 5181 } 5182 }; 5183 this.animate.callbacks.onComplete = () => { 5184 if ((!this.grabbed && this.released)) { 5185 // Set released to false before calling onSettle to avoid recursion 5186 this.released = false; 5187 } 5188 if (!this.manual) { 5189 this.deltaX = 0; 5190 this.deltaY = 0; 5191 this.velocity = 0; 5192 this.velocityStack[0] = 0; 5193 this.velocityStack[1] = 0; 5194 this.velocityStack[2] = 0; 5195 this.velocityStackIndex = 0; 5196 this.onSettle(this); 5197 } 5198 }; 5199 this.resizeTicker = new Timer({ 5200 autoplay: false, 5201 duration: 150 * globals.timeScale, 5202 onComplete: () => { 5203 this.onResize(this); 5204 this.refresh(); 5205 this.onAfterResize(this); 5206 }, 5207 }).init(); 5208 this.parameters = parameters; 5209 this.resizeObserver = new ResizeObserver(() => { 5210 if (this.initialized) { 5211 this.resizeTicker.restart(); 5212 } else { 5213 this.initialized = true; 5214 } 5215 }); 5216 this.enable(); 5217 this.refresh(); 5218 this.resizeObserver.observe(this.$container); 5219 if (!isObj(target)) this.resizeObserver.observe(this.$target); 5220 } 5221 5222 /** 5223 * @param {Number} dx 5224 * @param {Number} dy 5225 * @return {Number} 5226 */ 5227 computeVelocity(dx, dy) { 5228 const prevTime = this.velocityTime; 5229 const curTime = now(); 5230 const elapsed = curTime - prevTime; 5231 if (elapsed < 17) return this.velocity; 5232 this.velocityTime = curTime; 5233 const velocityStack = this.velocityStack; 5234 const vMul = this.velocityMultiplier; 5235 const minV = this.minVelocity; 5236 const maxV = this.maxVelocity; 5237 const vi = this.velocityStackIndex; 5238 velocityStack[vi] = round$1(clamp$1((sqrt(dx * dx + dy * dy) / elapsed) * vMul, minV, maxV), 5); 5239 const velocity = max(velocityStack[0], velocityStack[1], velocityStack[2]); 5240 this.velocity = velocity; 5241 this.velocityStackIndex = (vi + 1) % 3; 5242 return velocity; 5243 } 5244 5245 /** 5246 * @param {Number} x 5247 * @param {Boolean} [muteUpdateCallback] 5248 * @return {this} 5249 */ 5250 setX(x, muteUpdateCallback = false) { 5251 if (this.disabled[0]) return; 5252 const v = round$1(x, 5); 5253 this.overshootTicker.pause(); 5254 this.manual = true; 5255 this.updated = !muteUpdateCallback; 5256 this.destX = v; 5257 this.snapped[0] = snap$1(v, this.snapX); 5258 this.animate[this.xProp](v, 0); 5259 this.manual = false; 5260 return this; 5261 } 5262 5263 /** 5264 * @param {Number} y 5265 * @param {Boolean} [muteUpdateCallback] 5266 * @return {this} 5267 */ 5268 setY(y, muteUpdateCallback = false) { 5269 if (this.disabled[1]) return; 5270 const v = round$1(y, 5); 5271 this.overshootTicker.pause(); 5272 this.manual = true; 5273 this.updated = !muteUpdateCallback; 5274 this.destY = v; 5275 this.snapped[1] = snap$1(v, this.snapY); 5276 this.animate[this.yProp](v, 0); 5277 this.manual = false; 5278 return this; 5279 } 5280 5281 get x() { 5282 return round$1(/** @type {Number} */(this.animate[this.xProp]()), globals.precision); 5283 } 5284 5285 set x(x) { 5286 this.setX(x, false); 5287 } 5288 5289 get y() { 5290 return round$1(/** @type {Number} */(this.animate[this.yProp]()), globals.precision); 5291 } 5292 5293 set y(y) { 5294 this.setY(y, false); 5295 } 5296 5297 get progressX() { 5298 return mapRange$1(this.x, this.containerBounds[3], this.containerBounds[1], 0, 1); 5299 } 5300 5301 set progressX(x) { 5302 this.setX(mapRange$1(x, 0, 1, this.containerBounds[3], this.containerBounds[1]), false); 5303 } 5304 5305 get progressY() { 5306 return mapRange$1(this.y, this.containerBounds[0], this.containerBounds[2], 0, 1); 5307 } 5308 5309 set progressY(y) { 5310 this.setY(mapRange$1(y, 0, 1, this.containerBounds[0], this.containerBounds[2]), false); 5311 } 5312 5313 updateScrollCoords() { 5314 const sx = round$1(this.useWin ? win.scrollX : this.$container.scrollLeft, 0); 5315 const sy = round$1(this.useWin ? win.scrollY : this.$container.scrollTop, 0); 5316 const [ cpt, cpr, cpb, cpl ] = this.containerPadding; 5317 const threshold = this.scrollThreshold; 5318 this.scroll.x = sx; 5319 this.scroll.y = sy; 5320 this.scrollBounds[0] = sy - this.targetBounds[0] + cpt - threshold; 5321 this.scrollBounds[1] = sx - this.targetBounds[1] - cpr + threshold; 5322 this.scrollBounds[2] = sy - this.targetBounds[2] - cpb + threshold; 5323 this.scrollBounds[3] = sx - this.targetBounds[3] + cpl - threshold; 5324 } 5325 5326 updateBoundingValues() { 5327 const $container = this.$container; 5328 // Return early if no $container defined to prevents error when reading scrollWidth / scrollHeight 5329 // https://github.com/juliangarnier/anime/issues/1064 5330 if (!$container) return; 5331 const cx = this.x; 5332 const cy = this.y; 5333 const cx2 = this.coords[2]; 5334 const cy2 = this.coords[3]; 5335 // Prevents interfering with the scroll area in cases the target is outside of the container 5336 // Make sure the temp coords are also adjuset to prevents wrong delta calculation on updates 5337 this.coords[2] = 0; 5338 this.coords[3] = 0; 5339 this.setX(0, true); 5340 this.setY(0, true); 5341 this.transforms.remove(); 5342 const iw = this.window[0] = win.innerWidth; 5343 const ih = this.window[1] = win.innerHeight; 5344 const uw = this.useWin; 5345 const sw = $container.scrollWidth; 5346 const sh = $container.scrollHeight; 5347 const fx = this.fixed; 5348 const transformContainerRect = $container.getBoundingClientRect(); 5349 const [ cpt, cpr, cpb, cpl ] = this.containerPadding; 5350 this.dragArea[0] = uw ? 0 : transformContainerRect.left; 5351 this.dragArea[1] = uw ? 0 : transformContainerRect.top; 5352 this.scrollView[0] = uw ? clamp$1(sw, iw, sw) : sw; 5353 this.scrollView[1] = uw ? clamp$1(sh, ih, sh) : sh; 5354 this.updateScrollCoords(); 5355 const { width, height, left, top, right, bottom } = $container.getBoundingClientRect(); 5356 this.dragArea[2] = round$1(uw ? clamp$1(width, iw, iw) : width, 0); 5357 this.dragArea[3] = round$1(uw ? clamp$1(height, ih, ih) : height, 0); 5358 const containerOverflow = get($container, 'overflow'); 5359 const visibleOverflow = containerOverflow === 'visible'; 5360 const hiddenOverflow = containerOverflow === 'hidden'; 5361 this.canScroll = fx ? false : 5362 this.contained && 5363 (($container === doc.body && visibleOverflow) || (!hiddenOverflow && !visibleOverflow)) && 5364 (sw > this.dragArea[2] + cpl - cpr || sh > this.dragArea[3] + cpt - cpb) && 5365 (!this.containerArray || (this.containerArray && !isArr(this.containerArray))); 5366 if (this.contained) { 5367 const sx = this.scroll.x; 5368 const sy = this.scroll.y; 5369 const canScroll = this.canScroll; 5370 const targetRect = this.$target.getBoundingClientRect(); 5371 const hiddenLeft = canScroll ? uw ? 0 : $container.scrollLeft : 0; 5372 const hiddenTop = canScroll ? uw ? 0 : $container.scrollTop : 0; 5373 const hiddenRight = canScroll ? this.scrollView[0] - hiddenLeft - width : 0; 5374 const hiddenBottom = canScroll ? this.scrollView[1] - hiddenTop - height : 0; 5375 this.targetBounds[0] = round$1((targetRect.top + sy) - (uw ? 0 : top), 0); 5376 this.targetBounds[1] = round$1((targetRect.right + sx) - (uw ? iw : right), 0); 5377 this.targetBounds[2] = round$1((targetRect.bottom + sy) - (uw ? ih : bottom), 0); 5378 this.targetBounds[3] = round$1((targetRect.left + sx) - (uw ? 0 : left), 0); 5379 if (this.containerArray) { 5380 this.containerBounds[0] = this.containerArray[0] + cpt; 5381 this.containerBounds[1] = this.containerArray[1] - cpr; 5382 this.containerBounds[2] = this.containerArray[2] - cpb; 5383 this.containerBounds[3] = this.containerArray[3] + cpl; 5384 } else { 5385 this.containerBounds[0] = -round$1(targetRect.top - (fx ? clamp$1(top, 0, ih) : top) + hiddenTop - cpt, 0); 5386 this.containerBounds[1] = -round$1(targetRect.right - (fx ? clamp$1(right, 0, iw) : right) - hiddenRight + cpr, 0); 5387 this.containerBounds[2] = -round$1(targetRect.bottom - (fx ? clamp$1(bottom, 0, ih) : bottom) - hiddenBottom + cpb, 0); 5388 this.containerBounds[3] = -round$1(targetRect.left - (fx ? clamp$1(left, 0, iw) : left) + hiddenLeft - cpl, 0); 5389 } 5390 } 5391 this.transforms.revert(); 5392 // Restore coordinates 5393 this.coords[2] = cx2; 5394 this.coords[3] = cy2; 5395 this.setX(cx, true); 5396 this.setY(cy, true); 5397 } 5398 5399 /** 5400 * @param {Array} bounds 5401 * @param {Number} x 5402 * @param {Number} y 5403 * @return {Number} 5404 */ 5405 isOutOfBounds(bounds, x, y) { 5406 // Returns 0 if not OB, 1 if x is OB, 2 if y is OB, 3 if both x and y are OB 5407 if (!this.contained) return 0; 5408 const [ bt, br, bb, bl ] = bounds; 5409 const [ dx, dy ] = this.disabled; 5410 const obx = !dx && x < bl || !dx && x > br; 5411 const oby = !dy && y < bt || !dy && y > bb; 5412 return obx && !oby ? 1 : !obx && oby ? 2 : obx && oby ? 3 : 0; 5413 } 5414 5415 refresh() { 5416 const params = this.parameters; 5417 const paramX = params.x; 5418 const paramY = params.y; 5419 const container = parseDraggableFunctionParameter(params.container, this); 5420 const cp = parseDraggableFunctionParameter(params.containerPadding, this) || 0; 5421 const containerPadding = /** @type {[Number, Number, Number, Number]} */(isArr(cp) ? cp : [cp, cp, cp, cp]); 5422 const cx = this.x; 5423 const cy = this.y; 5424 const parsedCursorStyles = parseDraggableFunctionParameter(params.cursor, this); 5425 const cursorStyles = { onHover: 'grab', onGrab: 'grabbing' }; 5426 if (parsedCursorStyles) { 5427 const { onHover, onGrab } = /** @type {DraggableCursorParams} */(parsedCursorStyles); 5428 if (onHover) cursorStyles.onHover = onHover; 5429 if (onGrab) cursorStyles.onGrab = onGrab; 5430 } 5431 const parsedDragThreshold = parseDraggableFunctionParameter(params.dragThreshold, this); 5432 const dragThreshold = { mouse: 3, touch: 7 }; 5433 if (isNum(parsedDragThreshold)) { 5434 dragThreshold.mouse = parsedDragThreshold; 5435 dragThreshold.touch = parsedDragThreshold; 5436 } else if (parsedDragThreshold) { 5437 const { mouse, touch } = parsedDragThreshold; 5438 if (!isUnd(mouse)) dragThreshold.mouse = mouse; 5439 if (!isUnd(touch)) dragThreshold.touch = touch; 5440 } 5441 this.containerArray = isArr(container) ? container : null; 5442 this.$container = /** @type {HTMLElement} */(container && !this.containerArray ? parseTargets(/** @type {DOMTarget} */(container))[0] : doc.body); 5443 this.useWin = this.$container === doc.body; 5444 /** @type {Window | HTMLElement} */ 5445 this.$scrollContainer = this.useWin ? win : this.$container; 5446 this.isFinePointer = matchMedia('(pointer:fine)').matches; 5447 this.containerPadding = setValue(containerPadding, [0, 0, 0, 0]); 5448 this.containerFriction = clamp$1(setValue(parseDraggableFunctionParameter(params.containerFriction, this), .8), 0, 1); 5449 this.releaseContainerFriction = clamp$1(setValue(parseDraggableFunctionParameter(params.releaseContainerFriction, this), this.containerFriction), 0, 1); 5450 this.snapX = parseDraggableFunctionParameter(isObj(paramX) && !isUnd(paramX.snap) ? paramX.snap : params.snap, this); 5451 this.snapY = parseDraggableFunctionParameter(isObj(paramY) && !isUnd(paramY.snap) ? paramY.snap : params.snap, this); 5452 this.scrollSpeed = setValue(parseDraggableFunctionParameter(params.scrollSpeed, this), 1.5); 5453 this.scrollThreshold = setValue(parseDraggableFunctionParameter(params.scrollThreshold, this), 20); 5454 this.dragSpeed = setValue(parseDraggableFunctionParameter(params.dragSpeed, this), 1); 5455 this.dragThreshold = this.isFinePointer ? dragThreshold.mouse : dragThreshold.touch; 5456 this.minVelocity = setValue(parseDraggableFunctionParameter(params.minVelocity, this), 0); 5457 this.maxVelocity = setValue(parseDraggableFunctionParameter(params.maxVelocity, this), 50); 5458 this.velocityMultiplier = setValue(parseDraggableFunctionParameter(params.velocityMultiplier, this), 1); 5459 this.cursor = parsedCursorStyles === false ? false : cursorStyles; 5460 this.updateBoundingValues(); 5461 5462 // const ob = this.isOutOfBounds(this.containerBounds, this.x, this.y); 5463 // if (ob === 1 || ob === 3) this.progressX = px; 5464 // if (ob === 2 || ob === 3) this.progressY = py; 5465 5466 // if (this.initialized && this.contained) { 5467 // if (this.progressX !== px) this.progressX = px; 5468 // if (this.progressY !== py) this.progressY = py; 5469 // } 5470 5471 const [ bt, br, bb, bl ] = this.containerBounds; 5472 this.setX(clamp$1(cx, bl, br), true); 5473 this.setY(clamp$1(cy, bt, bb), true); 5474 } 5475 5476 update() { 5477 this.updateScrollCoords(); 5478 if (this.canScroll) { 5479 const [ cpt, cpr, cpb, cpl ] = this.containerPadding; 5480 const [ sw, sh ] = this.scrollView; 5481 const daw = this.dragArea[2]; 5482 const dah = this.dragArea[3]; 5483 const csx = this.scroll.x; 5484 const csy = this.scroll.y; 5485 const nsw = this.$container.scrollWidth; 5486 const nsh = this.$container.scrollHeight; 5487 const csw = this.useWin ? clamp$1(nsw, this.window[0], nsw) : nsw; 5488 const csh = this.useWin ? clamp$1(nsh, this.window[1], nsh) : nsh; 5489 const swd = sw - csw; 5490 const shd = sh - csh; 5491 // Handle cases where the scrollarea dimensions changes during drag 5492 if (this.dragged && swd > 0) { 5493 this.coords[0] -= swd; 5494 this.scrollView[0] = csw; 5495 } 5496 if (this.dragged && shd > 0) { 5497 this.coords[1] -= shd; 5498 this.scrollView[1] = csh; 5499 } 5500 // Handle autoscroll when target is at the edges of the scroll bounds 5501 const s = this.scrollSpeed * 10; 5502 const threshold = this.scrollThreshold; 5503 const [ x, y ] = this.coords; 5504 const [ st, sr, sb, sl ] = this.scrollBounds; 5505 const t = round$1(clamp$1((y - st + cpt) / threshold, -1, 0) * s, 0); 5506 const r = round$1(clamp$1((x - sr - cpr) / threshold, 0, 1) * s, 0); 5507 const b = round$1(clamp$1((y - sb - cpb) / threshold, 0, 1) * s, 0); 5508 const l = round$1(clamp$1((x - sl + cpl) / threshold, -1, 0) * s, 0); 5509 if (t || b || l || r) { 5510 const [nx, ny] = this.disabled; 5511 let scrollX = csx; 5512 let scrollY = csy; 5513 if (!nx) { 5514 scrollX = round$1(clamp$1(csx + (l || r), 0, sw - daw), 0); 5515 this.coords[0] -= csx - scrollX; 5516 } 5517 if (!ny) { 5518 scrollY = round$1(clamp$1(csy + (t || b), 0, sh - dah), 0); 5519 this.coords[1] -= csy - scrollY; 5520 } 5521 // Note: Safari mobile requires to use different scroll methods depending if using the window or not 5522 if (this.useWin) { 5523 this.$scrollContainer.scrollBy(-(csx - scrollX), -(csy - scrollY)); 5524 } else { 5525 this.$scrollContainer.scrollTo(scrollX, scrollY); 5526 } 5527 } 5528 } 5529 const [ ct, cr, cb, cl ] = this.containerBounds; 5530 const [ px1, py1, px2, py2, px3, py3 ] = this.pointer; 5531 this.coords[0] += (px1 - px3) * this.dragSpeed; 5532 this.coords[1] += (py1 - py3) * this.dragSpeed; 5533 this.pointer[4] = px1; 5534 this.pointer[5] = py1; 5535 const [ cx, cy ] = this.coords; 5536 const [ sx, sy ] = this.snapped; 5537 const cf = (1 - this.containerFriction) * this.dragSpeed; 5538 this.setX(cx > cr ? cr + (cx - cr) * cf : cx < cl ? cl + (cx - cl) * cf : cx, false); 5539 this.setY(cy > cb ? cb + (cy - cb) * cf : cy < ct ? ct + (cy - ct) * cf : cy, false); 5540 this.computeVelocity(px1 - px3, py1 - py3); 5541 this.angle = atan2(py1 - py2, px1 - px2); 5542 const [ nsx, nsy ] = this.snapped; 5543 if (nsx !== sx && this.snapX || nsy !== sy && this.snapY) { 5544 this.onSnap(this); 5545 } 5546 } 5547 5548 stop() { 5549 this.updateTicker.pause(); 5550 this.overshootTicker.pause(); 5551 // Pauses the in bounds onRelease animations 5552 for (let prop in this.animate.animations) this.animate.animations[prop].pause(); 5553 removeTargetsFromRenderable([this], null, 'x'); 5554 removeTargetsFromRenderable([this], null, 'y'); 5555 removeTargetsFromRenderable([this], null, 'progressX'); 5556 removeTargetsFromRenderable([this], null, 'progressY'); 5557 removeTargetsFromRenderable([this.scroll]); // Removes any active animations on the container scroll 5558 removeTargetsFromRenderable([this.overshootCoords]); // Removes active overshoot animations 5559 return this; 5560 } 5561 5562 /** 5563 * @param {Number} [duration] 5564 * @param {Number} [gap] 5565 * @param {EasingParam} [ease] 5566 * @return {this} 5567 */ 5568 scrollInView(duration, gap = 0, ease = eases.inOutQuad) { 5569 this.updateScrollCoords(); 5570 const x = this.destX; 5571 const y = this.destY; 5572 const scroll = this.scroll; 5573 const scrollBounds = this.scrollBounds; 5574 const canScroll = this.canScroll; 5575 if (!this.containerArray && this.isOutOfBounds(scrollBounds, x, y)) { 5576 const [ st, sr, sb, sl ] = scrollBounds; 5577 const t = round$1(clamp$1(y - st, -maxValue, 0), 0); 5578 const r = round$1(clamp$1(x - sr, 0, maxValue), 0); 5579 const b = round$1(clamp$1(y - sb, 0, maxValue), 0); 5580 const l = round$1(clamp$1(x - sl, -maxValue, 0), 0); 5581 new JSAnimation(scroll, { 5582 x: round$1(scroll.x + (l ? l - gap : r ? r + gap : 0), 0), 5583 y: round$1(scroll.y + (t ? t - gap : b ? b + gap : 0), 0), 5584 duration: isUnd(duration) ? 350 * globals.timeScale : duration, 5585 ease, 5586 onUpdate: () => { 5587 this.canScroll = false; 5588 this.$scrollContainer.scrollTo(scroll.x, scroll.y); 5589 } 5590 }).init().then(() => { 5591 this.canScroll = canScroll; 5592 }); 5593 } 5594 return this; 5595 } 5596 5597 handleHover() { 5598 if (this.isFinePointer && this.cursor && !this.cursorStyles) { 5599 this.cursorStyles = set(this.$trigger, { 5600 cursor: /** @type {DraggableCursorParams} */(this.cursor).onHover 5601 }); 5602 } 5603 } 5604 5605 /** 5606 * @param {Number} [duration] 5607 * @param {Number} [gap] 5608 * @param {EasingParam} [ease] 5609 * @return {this} 5610 */ 5611 animateInView(duration, gap = 0, ease = eases.inOutQuad) { 5612 this.stop(); 5613 this.updateBoundingValues(); 5614 const x = this.x; 5615 const y = this.y; 5616 const [ cpt, cpr, cpb, cpl ] = this.containerPadding; 5617 const bt = this.scroll.y - this.targetBounds[0] + cpt + gap; 5618 const br = this.scroll.x - this.targetBounds[1] - cpr - gap; 5619 const bb = this.scroll.y - this.targetBounds[2] - cpb - gap; 5620 const bl = this.scroll.x - this.targetBounds[3] + cpl + gap; 5621 const ob = this.isOutOfBounds([bt, br, bb, bl], x, y); 5622 if (ob) { 5623 const [ disabledX, disabledY ] = this.disabled; 5624 const destX = clamp$1(snap$1(x, this.snapX), bl, br); 5625 const destY = clamp$1(snap$1(y, this.snapY), bt, bb); 5626 const dur = isUnd(duration) ? 350 * globals.timeScale : duration; 5627 if (!disabledX && (ob === 1 || ob === 3)) this.animate[this.xProp](destX, dur, ease); 5628 if (!disabledY && (ob === 2 || ob === 3)) this.animate[this.yProp](destY, dur, ease); 5629 } 5630 return this; 5631 } 5632 5633 /** 5634 * @param {MouseEvent|TouchEvent} e 5635 */ 5636 handleDown(e) { 5637 const $eTarget = /** @type {HTMLElement} */(e.target); 5638 if (this.grabbed || /** @type {HTMLInputElement} */($eTarget).type === 'range') return; 5639 5640 e.stopPropagation(); 5641 5642 this.grabbed = true; 5643 this.released = false; 5644 this.stop(); 5645 this.updateBoundingValues(); 5646 const touches = /** @type {TouchEvent} */(e).changedTouches; 5647 const eventX = touches ? touches[0].clientX : /** @type {MouseEvent} */(e).clientX; 5648 const eventY = touches ? touches[0].clientY : /** @type {MouseEvent} */(e).clientY; 5649 const { x, y } = this.transforms.normalizePoint(eventX, eventY); 5650 const [ ct, cr, cb, cl ] = this.containerBounds; 5651 const cf = (1 - this.containerFriction) * this.dragSpeed; 5652 const cx = this.x; 5653 const cy = this.y; 5654 this.coords[0] = this.coords[2] = !cf ? cx : cx > cr ? cr + (cx - cr) / cf : cx < cl ? cl + (cx - cl) / cf : cx; 5655 this.coords[1] = this.coords[3] = !cf ? cy : cy > cb ? cb + (cy - cb) / cf : cy < ct ? ct + (cy - ct) / cf : cy; 5656 this.pointer[0] = x; 5657 this.pointer[1] = y; 5658 this.pointer[2] = x; 5659 this.pointer[3] = y; 5660 this.pointer[4] = x; 5661 this.pointer[5] = y; 5662 this.pointer[6] = x; 5663 this.pointer[7] = y; 5664 this.deltaX = 0; 5665 this.deltaY = 0; 5666 this.velocity = 0; 5667 this.velocityStack[0] = 0; 5668 this.velocityStack[1] = 0; 5669 this.velocityStack[2] = 0; 5670 this.velocityStackIndex = 0; 5671 this.angle = 0; 5672 if (this.targetStyles) { 5673 this.targetStyles.revert(); 5674 this.targetStyles = null; 5675 } 5676 const z = /** @type {Number} */(get(this.$target, 'zIndex', false)); 5677 zIndex = (z > zIndex ? z : zIndex) + 1; 5678 this.targetStyles = set(this.$target, { zIndex }); 5679 if (this.triggerStyles) { 5680 this.triggerStyles.revert(); 5681 this.triggerStyles = null; 5682 } 5683 if (this.cursorStyles) { 5684 this.cursorStyles.revert(); 5685 this.cursorStyles = null; 5686 } 5687 if (this.isFinePointer && this.cursor) { 5688 this.bodyStyles = set(doc.body, { 5689 cursor: /** @type {DraggableCursorParams} */(this.cursor).onGrab 5690 }); 5691 } 5692 this.scrollInView(100, 0, eases.out(3)); 5693 this.onGrab(this); 5694 5695 doc.addEventListener('touchmove', this); 5696 doc.addEventListener('touchend', this); 5697 doc.addEventListener('touchcancel', this); 5698 doc.addEventListener('mousemove', this); 5699 doc.addEventListener('mouseup', this); 5700 doc.addEventListener('selectstart', this); 5701 } 5702 5703 /** 5704 * @param {MouseEvent|TouchEvent} e 5705 */ 5706 handleMove(e) { 5707 if (!this.grabbed) return; 5708 const touches = /** @type {TouchEvent} */(e).changedTouches; 5709 const eventX = touches ? touches[0].clientX : /** @type {MouseEvent} */(e).clientX; 5710 const eventY = touches ? touches[0].clientY : /** @type {MouseEvent} */(e).clientY; 5711 const { x, y } = this.transforms.normalizePoint(eventX, eventY); 5712 const movedX = x - this.pointer[6]; 5713 const movedY = y - this.pointer[7]; 5714 5715 let $parent = /** @type {HTMLElement} */(e.target); 5716 let isAtTop = false; 5717 let isAtBottom = false; 5718 let canTouchScroll = false; 5719 5720 while (touches && $parent && $parent !== this.$trigger) { 5721 const overflowY = get($parent, 'overflow-y'); 5722 if (overflowY !== 'hidden' && overflowY !== 'visible') { 5723 const { scrollTop, scrollHeight, clientHeight } = $parent; 5724 if (scrollHeight > clientHeight) { 5725 canTouchScroll = true; 5726 isAtTop = scrollTop <= 3; 5727 isAtBottom = scrollTop >= (scrollHeight - clientHeight) - 3; 5728 break; 5729 } 5730 } 5731 $parent = $parent.parentElement; 5732 } 5733 5734 if (canTouchScroll && ((!isAtTop && !isAtBottom) || (isAtTop && movedY < 0) || (isAtBottom && movedY > 0))) { 5735 5736 this.pointer[0] = x; 5737 this.pointer[1] = y; 5738 this.pointer[2] = x; 5739 this.pointer[3] = y; 5740 this.pointer[4] = x; 5741 this.pointer[5] = y; 5742 this.pointer[6] = x; 5743 this.pointer[7] = y; 5744 5745 } else { 5746 5747 preventDefault(e); 5748 5749 // Needed to prevents click on handleUp 5750 if (!this.triggerStyles) this.triggerStyles = set(this.$trigger, { pointerEvents: 'none' }); 5751 // Needed to prevent page scroll while dragging on touch devvice 5752 this.$trigger.addEventListener('touchstart', preventDefault, { passive: false }); 5753 this.$trigger.addEventListener('touchmove', preventDefault, { passive: false }); 5754 this.$trigger.addEventListener('touchend', preventDefault); 5755 5756 // Don't check for a miminim distance move if already dragging 5757 if (this.dragged || (!this.disabled[0] && abs(movedX) > this.dragThreshold) || (!this.disabled[1] && abs(movedY) > this.dragThreshold)) { 5758 this.updateTicker.resume(); 5759 this.pointer[2] = this.pointer[0]; 5760 this.pointer[3] = this.pointer[1]; 5761 this.pointer[0] = x; 5762 this.pointer[1] = y; 5763 this.dragged = true; 5764 this.released = false; 5765 this.onDrag(this); 5766 } 5767 } 5768 } 5769 5770 handleUp() { 5771 5772 if (!this.grabbed) return; 5773 5774 this.updateTicker.pause(); 5775 5776 if (this.triggerStyles) { 5777 this.triggerStyles.revert(); 5778 this.triggerStyles = null; 5779 } 5780 5781 if (this.bodyStyles) { 5782 this.bodyStyles.revert(); 5783 this.bodyStyles = null; 5784 } 5785 5786 const [ disabledX, disabledY ] = this.disabled; 5787 const [ px1, py1, px2, py2, px3, py3 ] = this.pointer; 5788 const [ ct, cr, cb, cl ] = this.containerBounds; 5789 const [ sx, sy ] = this.snapped; 5790 const springX = this.releaseXSpring; 5791 const springY = this.releaseYSpring; 5792 const releaseEase = this.releaseEase; 5793 const hasReleaseSpring = this.hasReleaseSpring; 5794 const overshootCoords = this.overshootCoords; 5795 const cx = this.x; 5796 const cy = this.y; 5797 const pv = this.computeVelocity(px1 - px3, py1 - py3); 5798 const pa = this.angle = atan2(py1 - py2, px1 - px2); 5799 const ds = pv * 150; 5800 const cf = (1 - this.releaseContainerFriction) * this.dragSpeed; 5801 const nx = cx + (cos(pa) * ds); 5802 const ny = cy + (sin(pa) * ds); 5803 const bx = nx > cr ? cr + (nx - cr) * cf : nx < cl ? cl + (nx - cl) * cf : nx; 5804 const by = ny > cb ? cb + (ny - cb) * cf : ny < ct ? ct + (ny - ct) * cf : ny; 5805 const dx = this.destX = clamp$1(round$1(snap$1(bx, this.snapX), 5), cl, cr); 5806 const dy = this.destY = clamp$1(round$1(snap$1(by, this.snapY), 5), ct, cb); 5807 const ob = this.isOutOfBounds(this.containerBounds, nx, ny); 5808 5809 let durationX = 0; 5810 let durationY = 0; 5811 let easeX = releaseEase; 5812 let easeY = releaseEase; 5813 let longestReleaseDuration = 0; 5814 5815 overshootCoords.x = cx; 5816 overshootCoords.y = cy; 5817 5818 if (!disabledX) { 5819 const directionX = dx === cr ? cx > cr ? -1 : 1 : cx < cl ? -1 : 1; 5820 const distanceX = round$1(cx - dx, 0); 5821 springX.velocity = disabledY && hasReleaseSpring ? distanceX ? (ds * directionX) / abs(distanceX) : 0 : pv; 5822 const { ease, settlingDuration, restDuration } = springX; 5823 durationX = cx === dx ? 0 : hasReleaseSpring ? settlingDuration : settlingDuration - (restDuration * globals.timeScale); 5824 if (hasReleaseSpring) easeX = ease; 5825 if (durationX > longestReleaseDuration) longestReleaseDuration = durationX; 5826 } 5827 5828 if (!disabledY) { 5829 const directionY = dy === cb ? cy > cb ? -1 : 1 : cy < ct ? -1 : 1; 5830 const distanceY = round$1(cy - dy, 0); 5831 springY.velocity = disabledX && hasReleaseSpring ? distanceY ? (ds * directionY) / abs(distanceY) : 0 : pv; 5832 const { ease, settlingDuration, restDuration } = springY; 5833 durationY = cy === dy ? 0 : hasReleaseSpring ? settlingDuration : settlingDuration - (restDuration * globals.timeScale); 5834 if (hasReleaseSpring) easeY = ease; 5835 if (durationY > longestReleaseDuration) longestReleaseDuration = durationY; 5836 } 5837 5838 if (!hasReleaseSpring && ob && cf && (durationX || durationY)) { 5839 5840 const composition = compositionTypes.blend; 5841 5842 new JSAnimation(overshootCoords, { 5843 x: { to: bx, duration: durationX * .65 }, 5844 y: { to: by, duration: durationY * .65 }, 5845 ease: releaseEase, 5846 composition, 5847 }).init(); 5848 5849 new JSAnimation(overshootCoords, { 5850 x: { to: dx, duration: durationX }, 5851 y: { to: dy, duration: durationY }, 5852 ease: releaseEase, 5853 composition, 5854 }).init(); 5855 5856 this.overshootTicker.stretch(max(durationX, durationY)).restart(); 5857 5858 } else { 5859 5860 if (!disabledX) this.animate[this.xProp](dx, durationX, easeX); 5861 if (!disabledY) this.animate[this.yProp](dy, durationY, easeY); 5862 5863 } 5864 5865 this.scrollInView(longestReleaseDuration, this.scrollThreshold, releaseEase); 5866 5867 let hasSnapped = false; 5868 5869 if (dx !== sx) { 5870 this.snapped[0] = dx; 5871 if (this.snapX) hasSnapped = true; 5872 } 5873 5874 if (dy !== sy && this.snapY) { 5875 this.snapped[1] = dy; 5876 if (this.snapY) hasSnapped = true; 5877 } 5878 5879 if (hasSnapped) this.onSnap(this); 5880 5881 this.grabbed = false; 5882 this.dragged = false; 5883 this.updated = true; 5884 this.released = true; 5885 5886 // It's important to trigger the callback after the release animations to be able to cancel them 5887 this.onRelease(this); 5888 5889 this.$trigger.removeEventListener('touchstart', preventDefault); 5890 this.$trigger.removeEventListener('touchmove', preventDefault); 5891 this.$trigger.removeEventListener('touchend', preventDefault); 5892 5893 doc.removeEventListener('touchmove', this); 5894 doc.removeEventListener('touchend', this); 5895 doc.removeEventListener('touchcancel', this); 5896 doc.removeEventListener('mousemove', this); 5897 doc.removeEventListener('mouseup', this); 5898 doc.removeEventListener('selectstart', this); 5899 } 5900 5901 reset() { 5902 this.stop(); 5903 this.resizeTicker.pause(); 5904 this.grabbed = false; 5905 this.dragged = false; 5906 this.updated = false; 5907 this.released = false; 5908 this.canScroll = false; 5909 this.setX(0, true); 5910 this.setY(0, true); 5911 this.coords[0] = 0; 5912 this.coords[1] = 0; 5913 this.pointer[0] = 0; 5914 this.pointer[1] = 0; 5915 this.pointer[2] = 0; 5916 this.pointer[3] = 0; 5917 this.pointer[4] = 0; 5918 this.pointer[5] = 0; 5919 this.pointer[6] = 0; 5920 this.pointer[7] = 0; 5921 this.velocity = 0; 5922 this.velocityStack[0] = 0; 5923 this.velocityStack[1] = 0; 5924 this.velocityStack[2] = 0; 5925 this.velocityStackIndex = 0; 5926 this.angle = 0; 5927 return this; 5928 } 5929 5930 enable() { 5931 if (!this.enabled) { 5932 this.enabled = true; 5933 this.$target.classList.remove('is-disabled'); 5934 this.touchActionStyles = set(this.$trigger, { 5935 touchAction: this.disabled[0] ? 'pan-x' : this.disabled[1] ? 'pan-y' : 'none' 5936 }); 5937 this.$trigger.addEventListener('touchstart', this, { passive: true }); 5938 this.$trigger.addEventListener('mousedown', this, { passive: true }); 5939 this.$trigger.addEventListener('mouseenter', this); 5940 } 5941 return this; 5942 } 5943 5944 disable() { 5945 this.enabled = false; 5946 this.grabbed = false; 5947 this.dragged = false; 5948 this.updated = false; 5949 this.released = false; 5950 this.canScroll = false; 5951 this.touchActionStyles.revert(); 5952 if (this.cursorStyles) { 5953 this.cursorStyles.revert(); 5954 this.cursorStyles = null; 5955 } 5956 if (this.triggerStyles) { 5957 this.triggerStyles.revert(); 5958 this.triggerStyles = null; 5959 } 5960 if (this.bodyStyles) { 5961 this.bodyStyles.revert(); 5962 this.bodyStyles = null; 5963 } 5964 if (this.targetStyles) { 5965 this.targetStyles.revert(); 5966 this.targetStyles = null; 5967 } 5968 this.$target.classList.add('is-disabled'); 5969 this.$trigger.removeEventListener('touchstart', this); 5970 this.$trigger.removeEventListener('mousedown', this); 5971 this.$trigger.removeEventListener('mouseenter', this); 5972 doc.removeEventListener('touchmove', this); 5973 doc.removeEventListener('touchend', this); 5974 doc.removeEventListener('touchcancel', this); 5975 doc.removeEventListener('mousemove', this); 5976 doc.removeEventListener('mouseup', this); 5977 doc.removeEventListener('selectstart', this); 5978 return this; 5979 } 5980 5981 revert() { 5982 this.reset(); 5983 this.disable(); 5984 this.$target.classList.remove('is-disabled'); 5985 this.updateTicker.revert(); 5986 this.overshootTicker.revert(); 5987 this.resizeTicker.revert(); 5988 this.animate.revert(); 5989 this.resizeObserver.disconnect(); 5990 return this; 5991 } 5992 5993 /** 5994 * @param {Event} e 5995 */ 5996 handleEvent(e) { 5997 switch (e.type) { 5998 case 'mousedown': 5999 this.handleDown(/** @type {MouseEvent} */(e)); 6000 break; 6001 case 'touchstart': 6002 this.handleDown(/** @type {TouchEvent} */(e)); 6003 break; 6004 case 'mousemove': 6005 this.handleMove(/** @type {MouseEvent} */(e)); 6006 break; 6007 case 'touchmove': 6008 this.handleMove(/** @type {TouchEvent} */(e)); 6009 break; 6010 case 'mouseup': 6011 this.handleUp(); 6012 break; 6013 case 'touchend': 6014 this.handleUp(); 6015 break; 6016 case 'touchcancel': 6017 this.handleUp(); 6018 break; 6019 case 'mouseenter': 6020 this.handleHover(); 6021 break; 6022 case 'selectstart': 6023 preventDefault(e); 6024 break; 6025 } 6026 } 6027 } 6028 6029 /** 6030 * @param {TargetsParam} target 6031 * @param {DraggableParams} [parameters] 6032 * @return {Draggable} 6033 */ 6034 const createDraggable = (target, parameters) => new Draggable(target, parameters); 6035 6036 6037 6038 /** 6039 * @param {Callback<Timer>} [callback] 6040 * @return {Timer} 6041 */ 6042 const sync = (callback = noop) => { 6043 return new Timer({ duration: 1 * globals.timeScale, onComplete: callback }, null, 0).resume(); 6044 }; 6045 6046 /** 6047 * @
6047param {(...args: any[]) => Tickable | ((...args: any[]) => void)} constructor 6048 * @return {(...args: any[]) => Tickable | ((...args: any[]) => void)} 6049 */ 6050 const keepTime = constructor => { 6051 /** @type {Tickable} */ 6052 let tracked; 6053 return (...args) => { 6054 let currentIteration, currentIterationProgress, reversed, alternate; 6055 if (tracked) { 6056 currentIteration = tracked.currentIteration; 6057 currentIterationProgress = tracked.iterationProgress; 6058 reversed = tracked.reversed; 6059 alternate = tracked._alternate; 6060 tracked.revert(); 6061 } 6062 const cleanup = constructor(...args); 6063 if (cleanup && !isFnc(cleanup) && cleanup.revert) tracked = cleanup; 6064 if (!isUnd(currentIterationProgress)) { 6065 /** @type {Tickable} */(tracked).currentIteration = currentIteration; 6066 /** @type {Tickable} */(tracked).iterationProgress = (alternate ? !(currentIteration % 2) ? reversed : !reversed : reversed) ? 1 - currentIterationProgress : currentIterationProgress; 6067 } 6068 return cleanup || noop; 6069 }
vendor: 12,365 bytes, lines 6069-6467
6069 6070 }; 6071 6072 6073 6074 class Scope { 6075 /** @param {ScopeParams} [parameters] */ 6076 constructor(parameters = {}) { 6077 if (scope.current) scope.current.register(this); 6078 const rootParam = parameters.root; 6079 /** @type {Document|DOMTarget} */ 6080 let root = doc; 6081 if (rootParam) { 6082 root = /** @type {ReactRef} */(rootParam).current || 6083 /** @type {AngularRef} */(rootParam).nativeElement || 6084 parseTargets(/** @type {DOMTargetSelector} */(rootParam))[0] || 6085 doc; 6086 } 6087 const scopeDefaults = parameters.defaults; 6088 const globalDefault = globals.defaults; 6089 const mediaQueries = parameters.mediaQueries; 6090 /** @type {DefaultsParams} */ 6091 this.defaults = scopeDefaults ? mergeObjects(scopeDefaults, globalDefault) : globalDefault; 6092 /** @type {Document|DOMTarget} */ 6093 this.root = root; 6094 /** @type {Array<ScopeConstructorCallback>} */ 6095 this.constructors = []; 6096 /** @type {Array<ScopeCleanupCallback>} */ 6097 this.revertConstructors = []; 6098 /** @type {Array<Revertible>} */ 6099 this.revertibles = []; 6100 /** @type {Array<ScopeConstructorCallback | ((scope: this) => Tickable)>} */ 6101 this.constructorsOnce = []; 6102 /** @type {Array<ScopeCleanupCallback>} */ 6103 this.revertConstructorsOnce = []; 6104 /** @type {Array<Revertible>} */ 6105 this.revertiblesOnce = []; 6106 /** @type {Boolean} */ 6107 this.once = false; 6108 /** @type {Number} */ 6109 this.onceIndex = 0; 6110 /** @type {Record<String, ScopeMethod>} */ 6111 this.methods = {}; 6112 /** @type {Record<String, Boolean>} */ 6113 this.matches = {}; 6114 /** @type {Record<String, MediaQueryList>} */ 6115 this.mediaQueryLists = {}; 6116 /** @type {Record<String, any>} */ 6117 this.data = {}; 6118 if (mediaQueries) { 6119 for (let mq in mediaQueries) { 6120 const _mq = win.matchMedia(mediaQueries[mq]); 6121 this.mediaQueryLists[mq] = _mq; 6122 _mq.addEventListener('change', this); 6123 } 6124 } 6125 } 6126 6127 /** 6128 * @param {Revertible} revertible 6129 */ 6130 register(revertible) { 6131 const store = this.once ? this.revertiblesOnce : this.revertibles; 6132 store.push(revertible); 6133 } 6134 6135 /** 6136 * @template T 6137 * @param {ScopedCallback<T>} cb 6138 * @return {T} 6139 */ 6140 execute(cb) { 6141 let activeScope = scope.current; 6142 let activeRoot = scope.root; 6143 let activeDefaults = globals.defaults; 6144 scope.current = this; 6145 scope.root = this.root; 6146 globals.defaults = this.defaults; 6147 const mqs = this.mediaQueryLists; 6148 for (let mq in mqs) this.matches[mq] = mqs[mq].matches; 6149 const returned = cb(this); 6150 scope.current = activeScope; 6151 scope.root = activeRoot; 6152 globals.defaults = activeDefaults; 6153 return returned; 6154 } 6155 6156 /** 6157 * @return {this} 6158 */ 6159 refresh() { 6160 this.onceIndex = 0; 6161 this.execute(() => { 6162 let i = this.revertibles.length; 6163 let y = this.revertConstructors.length; 6164 while (i--) this.revertibles[i].revert(); 6165 while (y--) this.revertConstructors[y](this); 6166 this.revertibles.length = 0; 6167 this.revertConstructors.length = 0; 6168 this.constructors.forEach((/** @type {ScopeConstructorCallback} */constructor) => { 6169 const revertConstructor = constructor(this); 6170 if (isFnc(revertConstructor)) { 6171 this.revertConstructors.push(revertConstructor); 6172 } 6173 }); 6174 }); 6175 return this; 6176 } 6177 6178 /** 6179 * @overload 6180 * @param {String} a1 6181 * @param {ScopeMethod} a2 6182 * @return {this} 6183 * 6184 * @overload 6185 * @param {ScopeConstructorCallback} a1 6186 * @return {this} 6187 * 6188 * @param {String|ScopeConstructorCallback} a1 6189 * @param {ScopeMethod} [a2] 6190 */ 6191 add(a1, a2) { 6192 this.once = false; 6193 if (isFnc(a1)) { 6194 const constructor = /** @type {ScopeConstructorCallback} */(a1); 6195 this.constructors.push(constructor); 6196 this.execute(() => { 6197 const revertConstructor = constructor(this); 6198 if (isFnc(revertConstructor)) { 6199 this.revertConstructors.push(revertConstructor); 6200 } 6201 }); 6202 } else { 6203 this.methods[/** @type {String} */(a1)] = (/** @type {any} */...args) => this.execute(() => a2(...args)); 6204 } 6205 return this; 6206 } 6207 6208 /** 6209 * @param {ScopeConstructorCallback} scopeConstructorCallback 6210 * @return {this} 6211 */ 6212 addOnce(scopeConstructorCallback) { 6213 this.once = true; 6214 if (isFnc(scopeConstructorCallback)) { 6215 const currentIndex = this.onceIndex++; 6216 const tracked = this.constructorsOnce[currentIndex]; 6217 if (tracked) return this; 6218 const constructor = /** @type {ScopeConstructorCallback} */(scopeConstructorCallback); 6219 this.constructorsOnce[currentIndex] = constructor; 6220 this.execute(() => { 6221 const revertConstructor = constructor(this); 6222 if (isFnc(revertConstructor)) { 6223 this.revertConstructorsOnce.push(revertConstructor); 6224 } 6225 }); 6226 } 6227 return this; 6228 } 6229 6230 /** 6231 * @param {(scope: this) => Tickable} cb 6232 * @return {Tickable} 6233 */ 6234 keepTime(cb) { 6235 this.once = true; 6236 const currentIndex = this.onceIndex++; 6237 const tracked = /** @type {(scope: this) => Tickable} */(this.constructorsOnce[currentIndex]); 6238 if (isFnc(tracked)) return tracked(this); 6239 const constructor = /** @type {(scope: this) => Tickable} */(keepTime(cb)); 6240 this.constructorsOnce[currentIndex] = constructor; 6241 let trackedTickable; 6242 this.execute(() => { 6243 trackedTickable = constructor(this); 6244 }); 6245 return trackedTickable; 6246 } 6247 6248 /** 6249 * @param {Event} e 6250 */ 6251 handleEvent(e) { 6252 switch (e.type) { 6253 case 'change': 6254 this.refresh(); 6255 break; 6256 } 6257 } 6258 6259 revert() { 6260 const revertibles = this.revertibles; 6261 const revertConstructors = this.revertConstructors; 6262 const revertiblesOnce = this.revertiblesOnce; 6263 const revertConstructorsOnce = this.revertConstructorsOnce; 6264 const mqs = this.mediaQueryLists; 6265 let i = revertibles.length; 6266 let j = revertConstructors.length; 6267 let k = revertiblesOnce.length; 6268 let l = revertConstructorsOnce.length; 6269 while (i--) revertibles[i].revert(); 6270 while (j--) revertConstructors[j](this); 6271 while (k--) revertiblesOnce[k].revert(); 6272 while (l--) revertConstructorsOnce[l](this); 6273 for (let mq in mqs) mqs[mq].removeEventListener('change', this); 6274 revertibles.length = 0; 6275 revertConstructors.length = 0; 6276 this.constructors.length = 0; 6277 revertiblesOnce.length = 0; 6278 revertConstructorsOnce.length = 0; 6279 this.constructorsOnce.length = 0; 6280 this.onceIndex = 0; 6281 this.matches = {}; 6282 this.methods = {}; 6283 this.mediaQueryLists = {}; 6284 this.data = {}; 6285 } 6286 } 6287 6288 /** 6289 * @param {ScopeParams} [params] 6290 * @return {Scope} 6291 */ 6292 const createScope = params => new Scope(params); 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 /** 6303 * @return {Number} 6304 */ 6305 const getMaxViewHeight = () => { 6306 const $el = doc.createElement('div'); 6307 doc.body.appendChild($el); 6308 $el.style.height = '100lvh'; 6309 const height = $el.offsetHeight; 6310 doc.body.removeChild($el); 6311 return height; 6312 }; 6313 6314 /** 6315 * @template {ScrollThresholdValue|String|Number|Boolean|Function|Object} T 6316 * @param {T | ((observer: ScrollObserver) => T)} value 6317 * @param {ScrollObserver} scroller 6318 * @return {T} 6319 */ 6320 const parseScrollObserverFunctionParameter = (value, scroller) => value && isFnc(value) ? /** @type {Function} */(value)(scroller) : /** @type {T} */(value); 6321 6322 const scrollContainers = new Map(); 6323 6324 class ScrollContainer { 6325 /** 6326 * @param {HTMLElement} $el 6327 */ 6328 constructor($el) { 6329 /** @type {HTMLElement} */ 6330 this.element = $el; 6331 /** @type {Boolean} */ 6332 this.useWin = this.element === doc.body; 6333 /** @type {Number} */ 6334 this.winWidth = 0; 6335 /** @type {Number} */ 6336 this.winHeight = 0; 6337 /** @type {Number} */ 6338 this.width = 0; 6339 /** @type {Number} */ 6340 this.height = 0; 6341 /** @type {Number} */ 6342 this.left = 0; 6343 /** @type {Number} */ 6344 this.top = 0; 6345 /** @type {Number} */ 6346 this.scale = 1; 6347 /** @type {Number} */ 6348 this.zIndex = 0; 6349 /** @type {Number} */ 6350 this.scrollX = 0; 6351 /** @type {Number} */ 6352 this.scrollY = 0; 6353 /** @type {Number} */ 6354 this.prevScrollX = 0; 6355 /** @type {Number} */ 6356 this.prevScrollY = 0; 6357 /** @type {Number} */ 6358 this.scrollWidth = 0; 6359 /** @type {Number} */ 6360 this.scrollHeight = 0; 6361 /** @type {Number} */ 6362 this.velocity = 0; 6363 /** @type {Boolean} */ 6364 this.backwardX = false; 6365 /** @type {Boolean} */ 6366 this.backwardY = false; 6367 /** @type {Timer} */ 6368 this.scrollTicker = new Timer({ 6369 autoplay: false, 6370 onBegin: () => this.dataTimer.resume(), 6371 onUpdate: () => { 6372 const backwards = this.backwardX || this.backwardY; 6373 forEachChildren(this, (/** @type {ScrollObserver} */child) => child.handleScroll(), backwards); 6374 }, 6375 onComplete: () => this.dataTimer.pause() 6376 }).init(); 6377 /** @type {Timer} */ 6378 this.dataTimer = new Timer({ 6379 autoplay: false, 6380 frameRate: 30, 6381 onUpdate: (/** @type {Timer} */self) => { 6382 const dt = self.deltaTime; 6383 const px = this.prevScrollX; 6384 const py = this.prevScrollY; 6385 const nx = this.scrollX; 6386 const ny = this.scrollY; 6387 const dx = px - nx; 6388 const dy = py - ny; 6389 this.prevScrollX = nx; 6390 this.prevScrollY = ny; 6391 if (dx) this.backwardX = px > nx; 6392 if (dy) this.backwardY = py > ny; 6393 this.velocity = round$1(dt > 0 ? Math.sqrt(dx * dx + dy * dy) / dt : 0, 5); 6394 } 6395 }).init(); 6396 /** @type {Timer} */ 6397 this.resizeTicker = new Timer({ 6398 autoplay: false, 6399 duration: 250 * globals.timeScale, 6400 onComplete: () => { 6401 this.updateWindowBounds(); 6402 this.refreshScrollObservers(); 6403 this.handleScroll(); 6404 } 6405 }).init(); 6406 /** @type {Timer} */ 6407 this.wakeTicker = new Timer({ 6408 autoplay: false, 6409 duration: 500 * globals.timeScale, 6410 onBegin: () => { 6411 this.scrollTicker.resume(); 6412 }, 6413 onComplete: () => { 6414 this.scrollTicker.pause(); 6415 } 6416 }).init(); 6417 /** @type {ScrollObserver} */ 6418 this._head = null; 6419 /** @type {ScrollObserver} */ 6420 this._tail = null; 6421 this.updateScrollCoords(); 6422 this.updateWindowBounds(); 6423 this.updateBounds(); 6424 this.refreshScrollObservers(); 6425 this.handleScroll(); 6426 this.resizeObserver = new ResizeObserver(() => this.resizeTicker.restart()); 6427 this.resizeObserver.observe(this.element); 6428 (this.useWin ? win : this.element).addEventListener('scroll', this, false); 6429 } 6430 6431 updateScrollCoords() { 6432 const useWin = this.useWin; 6433 const $el = this.element; 6434 this.scrollX = round$1(useWin ? win.scrollX : $el.scrollLeft, 0); 6435 this.scrollY = round$1(useWin ? win.scrollY : $el.scrollTop, 0); 6436 } 6437 6438 updateWindowBounds() { 6439 this.winWidth = win.innerWidth; 6440 this.winHeight = getMaxViewHeight(); 6441 } 6442 6443 updateBounds() { 6444 const style = getComputedStyle(this.element); 6445 const $el = this.element; 6446 this.scrollWidth = $el.scrollWidth + parseFloat(style.marginLeft) + parseFloat(style.marginRight); 6447 this.scrollHeight = $el.scrollHeight + parseFloat(style.marginTop) + parseFloat(style.marginBottom); 6448 this.updateWindowBounds(); 6449 let width, height; 6450 if (this.useWin) { 6451 width = this.winWidth; 6452 height = this.winHeight; 6453 } else { 6454 const elRect = $el.getBoundingClientRect(); 6455 width = $el.clientWidth; 6456 height = $el.clientHeight; 6457 this.top = elRect.top; 6458 this.left = elRect.left; 6459 this.scale = elRect.width ? width / elRect.width : (elRect.height ? height / elRect.height : 1); 6460 } 6461 this.width = width; 6462 this.height = height; 6463 } 6464 6465 refreshScrollObservers() { 6466 forEachChildren(this, (/** @type {ScrollObserver} */child) => { 6467 if (
6467child._debug) { 6468 child.removeDebug(); 6469 } 6470 }); 6471 this.updateBounds(); 6472 forEachChildren(this, (/** @type {ScrollObserver} */child) => { 6473 child.refresh(
vendor: 7,877 bytes, lines 6473-6685
6473); 6474 if (child._debug) { 6475 child.debug(); 6476 } 6477 }); 6478 } 6479 6480 refresh() { 6481 this.updateWindowBounds(); 6482 this.updateBounds(); 6483 this.refreshScrollObservers(); 6484 this.handleScroll(); 6485 } 6486 6487 handleScroll() { 6488 this.updateScrollCoords(); 6489 this.wakeTicker.restart(); 6490 } 6491 6492 /** 6493 * @param {Event} e 6494 */ 6495 handleEvent(e) { 6496 switch (e.type) { 6497 case 'scroll': 6498 this.handleScroll(); 6499 break; 6500 } 6501 } 6502 6503 revert() { 6504 this.scrollTicker.cancel(); 6505 this.dataTimer.cancel(); 6506 this.resizeTicker.cancel(); 6507 this.wakeTicker.cancel(); 6508 this.resizeObserver.disconnect(); 6509 (this.useWin ? win : this.element).removeEventListener('scroll', this); 6510 scrollContainers.delete(this.element); 6511 } 6512 } 6513 6514 /** 6515 * @param {TargetsParam} target 6516 * @return {ScrollContainer} 6517 */ 6518 const registerAndGetScrollContainer = target => { 6519 const $el = /** @type {HTMLElement} */(target ? parseTargets(target)[0] || doc.body : doc.body); 6520 let scrollContainer = scrollContainers.get($el); 6521 if (!scrollContainer) { 6522 scrollContainer = new ScrollContainer($el); 6523 scrollContainers.set($el, scrollContainer); 6524 } 6525 return scrollContainer; 6526 }; 6527 6528 /** 6529 * @param {HTMLElement} $el 6530 * @param {Number|string} v 6531 * @param {Number} size 6532 * @param {Number} [under] 6533 * @param {Number} [over] 6534 * @return {Number} 6535 */ 6536 const convertValueToPx = ($el, v, size, under, over) => { 6537 const clampMin = v === 'min'; 6538 const clampMax = v === 'max'; 6539 const value = v === 'top' || v === 'left' || v === 'start' || clampMin ? 0 : 6540 v === 'bottom' || v === 'right' || v === 'end' || clampMax ? '100%' : 6541 v === 'center' ? '50%' : 6542 v; 6543 const { n, u } = decomposeRawValue(value, decomposedOriginalValue); 6544 let px = n; 6545 if (u === '%') { 6546 px = (n / 100) * size; 6547 } else if (u) { 6548 px = convertValueUnit($el, decomposedOriginalValue, 'px', true).n; 6549 } 6550 if (clampMax && under < 0) px += under; 6551 if (clampMin && over > 0) px += over; 6552 return px; 6553 }; 6554 6555 /** 6556 * @param {HTMLElement} $el 6557 * @param {ScrollThresholdValue} v 6558 * @param {Number} size 6559 * @param {Number} [under] 6560 * @param {Number} [over] 6561 * @return {Number} 6562 */ 6563 const parseBoundValue = ($el, v, size, under, over) => { 6564 /** @type {Number} */ 6565 let value; 6566 if (isStr(v)) { 6567 const matchedOperator = relativeValuesExecRgx.exec(/** @type {String} */(v)); 6568 if (matchedOperator) { 6569 const splitter = matchedOperator[0]; 6570 const operator = splitter[0]; 6571 const splitted = /** @type {String} */(v).split(splitter); 6572 const clampMin = splitted[0] === 'min'; 6573 const clampMax = splitted[0] === 'max'; 6574 const valueAPx = convertValueToPx($el, splitted[0], size, under, over); 6575 const valueBPx = convertValueToPx($el, splitted[1], size, under, over); 6576 if (clampMin) { 6577 const min = getRelativeValue(convertValueToPx($el, 'min', size), valueBPx, operator); 6578 value = min < valueAPx ? valueAPx : min; 6579 } else if (clampMax) { 6580 const max = getRelativeValue(convertValueToPx($el, 'max', size), valueBPx, operator); 6581 value = max > valueAPx ? valueAPx : max; 6582 } else { 6583 value = getRelativeValue(valueAPx, valueBPx, operator); 6584 } 6585 } else { 6586 value = convertValueToPx($el, v, size, under, over); 6587 } 6588 } else { 6589 value = /** @type {Number} */(v); 6590 } 6591 return round$1(value, 0); 6592 }; 6593 6594 /** 6595 * @param {JSAnimation} linked 6596 * @return {HTMLElement} 6597 */ 6598 const getAnimationDomTarget = linked => { 6599 let $linkedTarget; 6600 const linkedTargets = linked.targets; 6601 for (let i = 0, l = linkedTargets.length; i < l; i++) { 6602 const target = linkedTargets[i]; 6603 if (target[isDomSymbol]) { 6604 $linkedTarget = /** @type {HTMLElement} */(target); 6605 break; 6606 } 6607 } 6608 return $linkedTarget; 6609 }; 6610 6611 let scrollerIndex = 0; 6612 6613 const debugColors$1 = ['#FF4B4B','#FF971B','#FFC730','#F9F640','#7AFF5A','#18FF74','#17E09B','#3CFFEC','#05DBE9','#33B3F1','#638CF9','#C563FE','#FF4FCF','#F93F8A']; 6614 6615 class ScrollObserver { 6616 /** 6617 * @param {ScrollObserverParams} parameters 6618 */ 6619 constructor(parameters = {}) { 6620 if (scope.current) scope.current.register(this); 6621 const syncMode = setValue(parameters.sync, 'play pause'); 6622 const ease = syncMode ? parseEase(/** @type {EasingParam} */(syncMode)) : null; 6623 const isLinear = syncMode && (syncMode === 'linear' || syncMode === none); 6624 const isEase = syncMode && !(ease === none && !isLinear); 6625 const isSmooth = syncMode && (isNum(syncMode) || syncMode === true || isLinear); 6626 const isMethods = syncMode && (isStr(syncMode) && !isEase && !isSmooth); 6627 const syncMethods = isMethods ? /** @type {String} */(syncMode).split(' ').map( 6628 (/** @type {String} */m) => () => { 6629 const linked = this.linked; 6630 return linked && linked[m] ? linked[m]() : null; 6631 } 6632 ) : null; 6633 const biDirSync = isMethods && syncMethods.length > 2; 6634 /** @type {Number} */ 6635 this.index = scrollerIndex++; 6636 /** @type {String|Number} */ 6637 this.id = !isUnd(parameters.id) ? parameters.id : this.index; 6638 /** @type {ScrollContainer} */ 6639 this.container = registerAndGetScrollContainer(parameters.container); 6640 /** @type {HTMLElement} */ 6641 this.target = null; 6642 /** @type {Tickable|WAAPIAnimation} */ 6643 this.linked = null; 6644 /** @type {Boolean} */ 6645 this.repeat = null; 6646 /** @type {Boolean} */ 6647 this.horizontal = null; 6648 /** @type {ScrollThresholdParam|ScrollThresholdValue|ScrollThresholdCallback} */ 6649 this.enter = null; 6650 /** @type {ScrollThresholdParam|ScrollThresholdValue|ScrollThresholdCallback} */ 6651 this.leave = null; 6652 /** @type {Boolean} */ 6653 this.sync = isEase || isSmooth || !!syncMethods; 6654 /** @type {EasingFunction} */ 6655 this.syncEase = isEase ? ease : null; 6656 /** @type {Number} */ 6657 this.syncSmooth = isSmooth ? syncMode === true || isLinear ? 1 : /** @type {Number} */(syncMode) : null; 6658 /** @type {Callback<ScrollObserver>} */ 6659 this.onSyncEnter = syncMethods && !biDirSync && syncMethods[0] ? syncMethods[0] : noop; 6660 /** @type {Callback<ScrollObserver>} */ 6661 this.onSyncLeave = syncMethods && !biDirSync && syncMethods[1] ? syncMethods[1] : noop; 6662 /** @type {Callback<ScrollObserver>} */ 6663 this.onSyncEnterForward = syncMethods && biDirSync && syncMethods[0] ? syncMethods[0] : noop; 6664 /** @type {Callback<ScrollObserver>} */ 6665 this.onSyncLeaveForward = syncMethods && biDirSync && syncMethods[1] ? syncMethods[1] : noop; 6666 /** @type {Callback<ScrollObserver>} */ 6667 this.onSyncEnterBackward = syncMethods && biDirSync && syncMethods[2] ? syncMethods[2] : noop; 6668 /** @type {Callback<ScrollObserver>} */ 6669 this.onSyncLeaveBackward = syncMethods && biDirSync && syncMethods[3] ? syncMethods[3] : noop; 6670 /** @type {Callback<ScrollObserver>} */ 6671 this.onEnter = parameters.onEnter || noop; 6672 /** @type {Callback<ScrollObserver>} */ 6673 this.onLeave = parameters.onLeave || noop; 6674 /** @type {Callback<ScrollObserver>} */ 6675 this.onEnterForward = parameters.onEnterForward || noop; 6676 /** @type {Callback<ScrollObserver>} */ 6677 this.onLeaveForward = parameters.onLeaveForward || noop; 6678 /** @type {Callback<ScrollObserver>} */ 6679 this.onEnterBackward = parameters.onEnterBackward || noop; 6680 /** @type {Callback<ScrollObserver>} */ 6681 this.onLeaveBackward = parameters.onLeaveBackward || noop; 6682 /** @type {Callback<ScrollObserver>} */ 6683 this.onUpdate = parameters.onUpdate || noop; 6684 /** @type {Callback<ScrollObserver>} */ 6685 this.on
vendor: 2,172 bytes, lines 6685-6748
6685SyncComplete = parameters.onSyncComplete || noop; 6686 /** @type {Boolean} */ 6687 this.reverted = false; 6688 /** @type {Boolean} */ 6689 this.ready = false; 6690 /** @type {Boolean} */ 6691 this.completed = false; 6692 /** @type {Boolean} */ 6693 this.began = false; 6694 /** @type {Boolean} */ 6695 this.isInView = false; 6696 /** @type {Boolean} */ 6697 this.forceEnter = false; 6698 /** @type {Boolean} */ 6699 this.hasEntered = false; 6700 /** @type {Number} */ 6701 this.offset = 0; 6702 /** @type {Number} */ 6703 this.offsetStart = 0; 6704 /** @type {Number} */ 6705 this.offsetEnd = 0; 6706 /** @type {Number} */ 6707 this.distance = 0; 6708 /** @type {Number} */ 6709 this.prevProgress = 0; 6710 /** @type {Array} */ 6711 this.thresholds = ['start', 'end', 'end', 'start']; 6712 /** @type {[Number, Number, Number, Number]} */ 6713 this.coords = [0, 0, 0, 0]; 6714 /** @type {JSAnimation} */ 6715 this.debugStyles = null; 6716 /** @type {HTMLElement} */ 6717 this.$debug = null; 6718 /** @type {ScrollObserverParams} */ 6719 this._params = parameters; 6720 /** @type {Boolean} */ 6721 this._debug = setValue(parameters.debug, false); 6722 /** @type {ScrollObserver} */ 6723 this._next = null; 6724 /** @type {ScrollObserver} */ 6725 this._prev = null; 6726 addChild(this.container, this); 6727 // Wait for the next frame to add to the container in order to handle calls to link() 6728 sync(() => { 6729 if (this.reverted) return; 6730 if (!this.target) { 6731 const target = /** @type {HTMLElement} */(parseTargets(parameters.target)[0]); 6732 this.target = target || doc.body; 6733 this.refresh(); 6734 } 6735 if (this._debug) this.debug(); 6736 }); 6737 } 6738 6739 /** 6740 * @param {Tickable|WAAPIAnimation} linked 6741 */ 6742 link(linked) { 6743 if (linked) { 6744 // Make sure to pause the linked object in case it's added later 6745 linked.pause(); 6746 this.linked = linked; 6747 // Forces WAAPI Animation to persist; otherwise, they will stop syncing on finish. 6748 if (!isUnd(/** @type {WAAPIAnimation} */(linked))) /** @type {WAAPIAnimation} */(linked).persist = true;
vendor: 8,702 bytes, lines 6748-6950
6748 6749 // Try to use a target of the linked object if no target parameters specified 6750 if (!this._params.target) { 6751 /** @type {HTMLElement} */ 6752 let $linkedTarget; 6753 if (!isUnd(/** @type {JSAnimation} */(linked).targets)) { 6754 $linkedTarget = getAnimationDomTarget(/** @type {JSAnimation} */(linked)); 6755 } else { 6756 forEachChildren(/** @type {Timeline} */(linked), (/** @type {JSAnimation} */child) => { 6757 if (child.targets && !$linkedTarget) { 6758 $linkedTarget = getAnimationDomTarget(/** @type {JSAnimation} */(child)); 6759 } 6760 }); 6761 } 6762 // Fallback to body if no target found 6763 this.target = $linkedTarget || doc.body; 6764 this.refresh(); 6765 } 6766 } 6767 return this; 6768 } 6769 6770 get velocity() { 6771 return this.container.velocity; 6772 } 6773 6774 get backward() { 6775 return this.horizontal ? this.container.backwardX : this.container.backwardY; 6776 } 6777 6778 get scroll() { 6779 return this.horizontal ? this.container.scrollX : this.container.scrollY; 6780 } 6781 6782 get progress() { 6783 const p = (this.scroll - this.offsetStart) / this.distance; 6784 return p === Infinity || isNaN(p) ? 0 : round$1(clamp$1(p, 0, 1), 6); 6785 } 6786 6787 refresh() { 6788 // This flag is used to prevent running handleScroll() outside of this.refresh() with values not yet calculated 6789 this.ready = true; 6790 this.reverted = false; 6791 const params = this._params; 6792 this.repeat = setValue(parseScrollObserverFunctionParameter(params.repeat, this), true); 6793 this.horizontal = setValue(parseScrollObserverFunctionParameter(params.axis, this), 'y') === 'x'; 6794 this.enter = setValue(parseScrollObserverFunctionParameter(params.enter, this), 'end start'); 6795 this.leave = setValue(parseScrollObserverFunctionParameter(params.leave, this), 'start end'); 6796 this.updateBounds(); 6797 this.handleScroll(); 6798 return this; 6799 } 6800 6801 removeDebug() { 6802 if (this.$debug) { 6803 this.$debug.parentNode.removeChild(this.$debug); 6804 this.$debug = null; 6805 } 6806 if (this.debugStyles) { 6807 this.debugStyles.revert(); 6808 this.$debug = null; 6809 } 6810 return this; 6811 } 6812 6813 debug() { 6814 this.removeDebug(); 6815 const container = this.container; 6816 const isHori = this.horizontal; 6817 const $existingDebug = container.element.querySelector(':scope > .animejs-onscroll-debug'); 6818 const $debug = doc.createElement('div'); 6819 const $thresholds = doc.createElement('div'); 6820 const $triggers = doc.createElement('div'); 6821 const color = debugColors$1[this.index % debugColors$1.length]; 6822 const useWin = container.useWin; 6823 const containerWidth = useWin ? container.winWidth : container.width; 6824 const containerHeight = useWin ? container.winHeight : container.height; 6825 const scrollWidth = container.scrollWidth; 6826 const scrollHeight = container.scrollHeight; 6827 const size = this.container.width > 360 ? 320 : 260; 6828 const offLeft = isHori ? 0 : 10; 6829 const offTop = isHori ? 10 : 0; 6830 const half = isHori ? 24 : size / 2; 6831 const labelHeight = isHori ? half : 15; 6832 const labelWidth = isHori ? 60 : half; 6833 const labelSize = isHori ? labelWidth : labelHeight; 6834 const repeat = isHori ? 'repeat-x' : 'repeat-y'; 6835 /** 6836 * @param {Number} v 6837 * @return {String} 6838 */ 6839 const gradientOffset = v => isHori ? '0px '+(v)+'px' : (v)+'px'+' 2px'; 6840 /** 6841 * @param {String} c 6842 * @return {String} 6843 */ 6844 const lineCSS = (c) => `linear-gradient(${isHori ? 90 : 0}deg, ${c} 2px, transparent 1px)`; 6845 /** 6846 * @param {String} p 6847 * @param {Number} l 6848 * @param {Number} t 6849 * @param {Number} w 6850 * @param {Number} h 6851 * @return {String} 6852 */ 6853 const baseCSS = (p, l, t, w, h) => `position:${p};left:${l}px;top:${t}px;width:${w}px;height:${h}px;`; 6854 $debug.style.cssText = `${baseCSS('absolute', offLeft, offTop, isHori ? scrollWidth : size, isHori ? size : scrollHeight)} 6855 pointer-events: none; 6856 z-index: ${this.container.zIndex++}; 6857 display: flex; 6858 flex-direction: ${isHori ? 'column' : 'row'}; 6859 filter: drop-shadow(0px 1px 0px rgba(0,0,0,.75)); 6860 `; 6861 $thresholds.style.cssText = `${baseCSS('sticky', 0, 0, isHori ? containerWidth : half, isHori ? half : containerHeight)}`; 6862 if (!$existingDebug) { 6863 $thresholds.style.cssText += `background: 6864 ${lineCSS('#FFFF')}${gradientOffset(half-10)} / ${isHori ? '100px 100px' : '100px 100px'} ${repeat}, 6865 ${lineCSS('#FFF8')}${gradientOffset(half-10)} / ${isHori ? '10px 10px' : '10px 10px'} ${repeat}; 6866 `; 6867 } 6868 $triggers.style.cssText = `${baseCSS('relative', 0, 0, isHori ? scrollWidth : half, isHori ? half : scrollHeight)}`; 6869 if (!$existingDebug) { 6870 $triggers.style.cssText += `background: 6871 ${lineCSS('#FFFF')}${gradientOffset(0)} / ${isHori ? '100px 10px' : '10px 100px'} ${repeat}, 6872 ${lineCSS('#FFF8')}${gradientOffset(0)} / ${isHori ? '10px 0px' : '0px 10px'} ${repeat}; 6873 `; 6874 } 6875 const labels = [' enter: ', ' leave: ']; 6876 this.coords.forEach((v, i) => { 6877 const isView = i > 1; 6878 const value = (isView ? 0 : this.offset) + v; 6879 const isTail = i % 2; 6880 const isFirst = value < labelSize; 6881 const isOver = value > (isView ? isHori ? containerWidth : containerHeight : isHori ? scrollWidth : scrollHeight) - labelSize; 6882 const isFlip = (isView ? isTail && !isFirst : !isTail && !isFirst) || isOver; 6883 const $label = doc.createElement('div'); 6884 const $text = doc.createElement('div'); 6885 const dirProp = isHori ? isFlip ? 'right' : 'left' : isFlip ? 'bottom' : 'top'; 6886 const flipOffset = isFlip ? (isHori ? labelWidth : labelHeight) + (!isView ? isHori ? -1 : -2 : isHori ? -1 : isOver ? 0 : -2) : !isView ? isHori ? 1 : 0 : isHori ? 1 : 0; 6887 // $text.innerHTML = `${!isView ? '' : labels[isTail] + ' '}${this.id}: ${this.thresholds[i]} ${isView ? '' : labels[isTail]}`; 6888 $text.innerHTML = `${this.id}${labels[isTail]}${this.thresholds[i]}`; 6889 $label.style.cssText = `${baseCSS('absolute', 0, 0, labelWidth, labelHeight)} 6890 display: flex; 6891 flex-direction: ${isHori ? 'column' : 'row'}; 6892 justify-content: flex-${isView ? 'start' : 'end'}; 6893 align-items: flex-${isFlip ? 'end' : 'start'}; 6894 border-${dirProp}: 2px ${isTail ? 'solid' : 'solid'} ${color}; 6895 `; 6896 $text.style.cssText = ` 6897 overflow: hidden; 6898 max-width: ${(size / 2) - 10}px; 6899 height: ${labelHeight}; 6900 margin-${isHori ? isFlip ? 'right' : 'left' : isFlip ? 'bottom' : 'top'}: -2px; 6901 padding: 1px; 6902 font-family: ui-monospace, monospace; 6903 font-size: 10px; 6904 letter-spacing: -.025em; 6905 line-height: 9px; 6906 font-weight: 600; 6907 text-align: ${isHori && isFlip || !isHori && !isView ? 'right' : 'left'}; 6908 white-space: pre; 6909 text-overflow: ellipsis; 6910 color: ${isTail ? color : 'rgba(0,0,0,.75)'}; 6911 background-color: ${isTail ? 'rgba(0,0,0,.65)' : color}; 6912 border: 2px solid ${isTail ? color : 'transparent'}; 6913 border-${isHori ? isFlip ? 'top-left' : 'top-right' : isFlip ? 'top-left' : 'bottom-left'}-radius: 5px; 6914 border-${isHori ? isFlip ? 'bottom-left' : 'bottom-right' : isFlip ? 'top-right' : 'bottom-right'}-radius: 5px; 6915 `; 6916 $label.appendChild($text); 6917 let position = value - flipOffset + (isHori ? 1 : 0); 6918 $label.style[isHori ? 'left' : 'top'] = `${position}px`; 6919 // $label.style[isHori ? 'left' : 'top'] = value - flipOffset + (!isFlip && isFirst && !isView ? 1 : isFlip ? 0 : -2) + 'px'; 6920 (isView ? $thresholds : $triggers).appendChild($label); 6921 }); 6922 6923 $debug.appendChild($thresholds); 6924 $debug.appendChild($triggers); 6925 container.element.appendChild($debug); 6926 6927 if (!$existingDebug) $debug.classList.add('animejs-onscroll-debug'); 6928 this.$debug = $debug; 6929 const containerPosition = get(container.element, 'position'); 6930 if (containerPosition === 'static') { 6931 this.debugStyles = set(container.element, { position: 'relative '}); 6932 } 6933 6934 } 6935 6936 updateBounds() { 6937 if (this._debug) { 6938 this.removeDebug(); 6939 } 6940 let stickys; 6941 const $target = this.target; 6942 const container = this.container; 6943 const isHori = this.horizontal; 6944 const linked = this.linked; 6945 let linkedTime; 6946 let $el = $target; 6947 // let offsetX = 0; 6948 // let offsetY = 0; 6949 // let $offsetParent = $el; 6950
6950/** @type {Element} */ 6951 if (linked) { 6952 linkedTime = linked.currentTime; 6953 linked.seek(0, true); 6954 } 6955 /*
vendor: 15,180 bytes, lines 6955-7408
6955 Old implementation to get offset and targetSize before fixing https://github.com/juliangarnier/anime/issues/1021 6956 // const isContainerStatic = get(container.element, 'position') === 'static' ? set(container.element, { position: 'relative '}) : false; 6957 // while ($el && $el !== container.element && $el !== doc.body) { 6958 // const isSticky = get($el, 'position') === 'sticky' ? 6959 // set($el, { position: 'static' }) : 6960 // false; 6961 // if ($el === $offsetParent) { 6962 // offsetX += $el.offsetLeft || 0; 6963 // offsetY += $el.offsetTop || 0; 6964 // $offsetParent = $el.offsetParent; 6965 // } 6966 // $el = /** @type {HTMLElement} */($el.parentElement); 6967 // if (isSticky) { 6968 // if (!stickys) stickys = []; 6969 // stickys.push(isSticky); 6970 // } 6971 // } 6972 // if (isContainerStatic) isContainerStatic.revert(); 6973 // const offset = isHori ? offsetX : offsetY; 6974 // const targetSize = isHori ? $target.offsetWidth : $target.offsetHeight; 6975 6976 while ($el && $el !== container.element && $el !== doc.body) { 6977 const isSticky = get($el, 'position') === 'sticky' ? set($el, { position: 'static' }) : false; 6978 $el = $el.parentElement; 6979 if (isSticky) { 6980 if (!stickys) stickys = []; 6981 stickys.push(isSticky); 6982 } 6983 } 6984 const rect = $target.getBoundingClientRect(); 6985 const scale = container.scale; 6986 const offset = (isHori ? rect.left + container.scrollX - container.left : rect.top + container.scrollY - container.top) * scale; 6987 const targetSize = (isHori ? rect.width : rect.height) * scale; 6988 const containerSize = isHori ? container.width : container.height; 6989 const scrollSize = isHori ? container.scrollWidth : container.scrollHeight; 6990 const maxScroll = scrollSize - containerSize; 6991 const enter = this.enter; 6992 const leave = this.leave; 6993 6994 /** @type {ScrollThresholdValue} */ 6995 let enterTarget = 'start'; 6996 /** @type {ScrollThresholdValue} */ 6997 let leaveTarget = 'end'; 6998 /** @type {ScrollThresholdValue} */ 6999 let enterContainer = 'end'; 7000 /** @type {ScrollThresholdValue} */ 7001 let leaveContainer = 'start'; 7002 7003 if (isStr(enter)) { 7004 const splitted = /** @type {String} */(enter).split(' '); 7005 enterContainer = splitted[0]; 7006 enterTarget = splitted.length > 1 ? splitted[1] : enterTarget; 7007 } else if (isObj(enter)) { 7008 const e = /** @type {ScrollThresholdParam} */(enter); 7009 if (!isUnd(e.container)) enterContainer = e.container; 7010 if (!isUnd(e.target)) enterTarget = e.target; 7011 } else if (isNum(enter)) { 7012 enterContainer = /** @type {Number} */(enter); 7013 } 7014 7015 if (isStr(leave)) { 7016 const splitted = /** @type {String} */(leave).split(' '); 7017 leaveContainer = splitted[0]; 7018 leaveTarget = splitted.length > 1 ? splitted[1] : leaveTarget; 7019 } else if (isObj(leave)) { 7020 const t = /** @type {ScrollThresholdParam} */(leave); 7021 if (!isUnd(t.container)) leaveContainer = t.container; 7022 if (!isUnd(t.target)) leaveTarget = t.target; 7023 } else if (isNum(leave)) { 7024 leaveContainer = /** @type {Number} */(leave); 7025 } 7026 7027 const parsedEnterTarget = parseBoundValue($target, enterTarget, targetSize); 7028 const parsedLeaveTarget = parseBoundValue($target, leaveTarget, targetSize); 7029 const under = (parsedEnterTarget + offset) - containerSize; 7030 const over = (parsedLeaveTarget + offset) - maxScroll; 7031 const parsedEnterContainer = parseBoundValue($target, enterContainer, containerSize, under, over); 7032 const parsedLeaveContainer = parseBoundValue($target, leaveContainer, containerSize, under, over); 7033 const offsetStart = parsedEnterTarget + offset - parsedEnterContainer; 7034 const offsetEnd = parsedLeaveTarget + offset - parsedLeaveContainer; 7035 const scrollDelta = offsetEnd - offsetStart; 7036 this.offset = offset; 7037 this.offsetStart = offsetStart; 7038 this.offsetEnd = offsetEnd; 7039 this.distance = scrollDelta <= 0 ? 0 : scrollDelta; 7040 this.thresholds = [enterTarget, leaveTarget, enterContainer, leaveContainer]; 7041 this.coords = [parsedEnterTarget, parsedLeaveTarget, parsedEnterContainer, parsedLeaveContainer]; 7042 if (stickys) { 7043 stickys.forEach(sticky => sticky.revert()); 7044 } 7045 if (linked) { 7046 linked.seek(linkedTime, true); 7047 } 7048 if (this._debug) { 7049 this.debug(); 7050 } 7051 } 7052 7053 handleScroll() { 7054 if (!this.ready) return; 7055 const linked = this.linked; 7056 const sync = this.sync; 7057 const syncEase = this.syncEase; 7058 const syncSmooth = this.syncSmooth; 7059 const shouldSeek = linked && (syncEase || syncSmooth); 7060 const isHori = this.horizontal; 7061 const container = this.container; 7062 const scroll = this.scroll; 7063 const isBefore = scroll <= this.offsetStart; 7064 const isAfter = scroll >= this.offsetEnd; 7065 const isInView = !isBefore && !isAfter; 7066 const isOnTheEdge = scroll === this.offsetStart || scroll === this.offsetEnd; 7067 const forceEnter = !this.hasEntered && isOnTheEdge; 7068 const $debug = this._debug && this.$debug; 7069 let hasUpdated = false; 7070 let syncCompleted = false; 7071 let p = this.progress; 7072 7073 if (isBefore && this.began) { 7074 this.began = false; 7075 } 7076 7077 if (p > 0 && !this.began) { 7078 this.began = true; 7079 } 7080 7081 if (shouldSeek) { 7082 const lp = linked.progress; 7083 if (syncSmooth && isNum(syncSmooth)) { 7084 if (/** @type {Number} */(syncSmooth) < 1) { 7085 const step = 0.0001; 7086 const snap = lp < p && p === 1 ? step : lp > p && !p ? -step : 0; 7087 p = round$1(lerp$1(lp, p, lerp$1(.01, .2, /** @type {Number} */(syncSmooth))) + snap, 6); 7088 } 7089 } else if (syncEase) { 7090 p = syncEase(p); 7091 } 7092 hasUpdated = p !== this.prevProgress; 7093 syncCompleted = lp === 1; 7094 if (hasUpdated && !syncCompleted && (syncSmooth && lp)) { 7095 container.wakeTicker.restart(); 7096 } 7097 } 7098 7099 if ($debug) { 7100 const sticky = isHori ? container.scrollY : container.scrollX; 7101 $debug.style[isHori ? 'top' : 'left'] = sticky + 10 + 'px'; 7102 } 7103 7104 // Trigger enter callbacks if already in view or when entering the view 7105 if ((isInView && !this.isInView) || (forceEnter && !this.forceEnter && !this.hasEntered)) { 7106 if (isInView) this.isInView = true; 7107 if (!this.forceEnter || !this.hasEntered) { 7108 if ($debug && isInView) $debug.style.zIndex = `${this.container.zIndex++}`; 7109 this.onSyncEnter(this); 7110 this.onEnter(this); 7111 if (this.backward) { 7112 this.onSyncEnterBackward(this); 7113 this.onEnterBackward(this); 7114 } else { 7115 this.onSyncEnterForward(this); 7116 this.onEnterForward(this); 7117 } 7118 this.hasEntered = true; 7119 if (forceEnter) this.forceEnter = true; 7120 } else if (isInView) { 7121 this.forceEnter = false; 7122 } 7123 } 7124 7125 if (isInView || !isInView && this.isInView) { 7126 hasUpdated = true; 7127 } 7128 7129 if (hasUpdated) { 7130 if (shouldSeek) linked.seek(linked.duration * p); 7131 this.onUpdate(this); 7132 } 7133 7134 if (!isInView && this.isInView) { 7135 this.isInView = false; 7136 this.onSyncLeave(this); 7137 this.onLeave(this); 7138 if (this.backward) { 7139 this.onSyncLeaveBackward(this); 7140 this.onLeaveBackward(this); 7141 } else { 7142 this.onSyncLeaveForward(this); 7143 this.onLeaveForward(this); 7144 } 7145 if (sync && !syncSmooth) { 7146 syncCompleted = true; 7147 } 7148 } 7149 7150 if (p >= 1 && this.began && !this.completed && (sync && syncCompleted || !sync)) { 7151 if (sync) { 7152 this.onSyncComplete(this); 7153 } 7154 this.completed = true; 7155 if ((!this.repeat && !linked) || (!this.repeat && linked && linked.completed)) { 7156 this.revert(); 7157 } 7158 } 7159 7160 if (p < 1 && this.completed) { 7161 this.completed = false; 7162 } 7163 7164 this.prevProgress = p; 7165 } 7166 7167 revert() { 7168 if (this.reverted) return; 7169 const container = this.container; 7170 removeChild(container, this); 7171 if (!container._head) { 7172 container.revert(); 7173 } 7174 if (this._debug) { 7175 this.removeDebug(); 7176 } 7177 this.reverted = true; 7178 this.ready = false; 7179 return this; 7180 } 7181 7182 } 7183 7184 /** 7185 * @param {ScrollObserverParams} [parameters={}] 7186 * @return {ScrollObserver} 7187 */ 7188 const onScroll = (parameters = {}) => new ScrollObserver(parameters); 7189 7190 7191 7192 /** 7193 * Cubic Bezier solver adapted from https://github.com/gre/bezier-easing 7194 * (c) 2014 Gaëtan Renaudeau 7195 */ 7196 7197 /** 7198 * @param {Number} aT 7199 * @param {Number} aA1 7200 * @param {Number} aA2 7201 * @return {Number} 7202 */ 7203 const calcBezier = (aT, aA1, aA2) => (((1 - 3 * aA2 + 3 * aA1) * aT + (3 * aA2 - 6 * aA1)) * aT + (3 * aA1)) * aT; 7204 7205 /** 7206 * @param {Number} aX 7207 * @param {Number} mX1 7208 * @param {Number} mX2 7209 * @return {Number} 7210 */ 7211 const binarySubdivide = (aX, mX1, mX2) => { 7212 let aA = 0, aB = 1, currentX, currentT, i = 0; 7213 do { 7214 currentT = aA + (aB - aA) / 2; 7215 currentX = calcBezier(currentT, mX1, mX2) - aX; 7216 if (currentX > 0) { 7217 aB = currentT; 7218 } else { 7219 aA = currentT; 7220 } 7221 } while (abs(currentX) > .0000001 && ++i < 100); 7222 return currentT; 7223 }; 7224 7225 /** 7226 * @param {Number} [mX1] The x coordinate of the first point 7227 * @param {Number} [mY1] The y coordinate of the first point 7228 * @param {Number} [mX2] The x coordinate of the second point 7229 * @param {Number} [mY2] The y coordinate of the second point 7230 * @return {EasingFunction} 7231 */ 7232 7233 const cubicBezier = (mX1 = 0.5, mY1 = 0.0, mX2 = 0.5, mY2 = 1.0) => (mX1 === mY1 && mX2 === mY2) ? none : 7234 t => t === 0 || t === 1 ? t : 7235 calcBezier(binarySubdivide(t, mX1, mX2), mY1, mY2); 7236 7237 7238 7239 /** 7240 * Steps ease implementation https://developer.mozilla.org/fr/docs/Web/CSS/transition-timing-function 7241 * Only covers 'end' and 'start' jumpterms 7242 * @param {Number} steps 7243 * @param {Boolean} [fromStart] 7244 * @return {EasingFunction} 7245 */ 7246 const steps = (steps = 10, fromStart) => { 7247 const roundMethod = fromStart ? ceil : floor; 7248 return t => roundMethod(clamp$1(t, 0, 1) * steps) * (1 / steps); 7249 }; 7250 7251 7252 7253 /** 7254 * Without parameters, the linear function creates a non-eased transition. 7255 * Parameters, if used, creates a piecewise linear easing by interpolating linearly between the specified points. 7256 * 7257 * @param {...(String|Number)} args - Points 7258 * @return {EasingFunction} 7259 */ 7260 const linear = (...args) => { 7261 const argsLength = args.length; 7262 if (!argsLength) return none; 7263 const totalPoints = argsLength - 1; 7264 const firstArg = args[0]; 7265 const lastArg = args[totalPoints]; 7266 const xPoints = [0]; 7267 const yPoints = [parseNumber(firstArg)]; 7268 for (let i = 1; i < totalPoints; i++) { 7269 const arg = args[i]; 7270 const splitValue = isStr(arg) ? 7271 /** @type {String} */(arg).trim().split(' ') : 7272 [arg]; 7273 const value = splitValue[0]; 7274 const percent = splitValue[1]; 7275 xPoints.push(!isUnd(percent) ? parseNumber(percent) / 100 : i / totalPoints); 7276 yPoints.push(parseNumber(value)); 7277 } 7278 yPoints.push(parseNumber(lastArg)); 7279 xPoints.push(1); 7280 return function easeLinear(t) { 7281 for (let i = 1, l = xPoints.length; i < l; i++) { 7282 const currentX = xPoints[i]; 7283 if (t <= currentX) { 7284 const prevX = xPoints[i - 1]; 7285 const prevY = yPoints[i - 1]; 7286 return prevY + (yPoints[i] - prevY) * (t - prevX) / (currentX - prevX); 7287 } 7288 } 7289 return yPoints[yPoints.length - 1]; 7290 } 7291 }; 7292 7293 7294 7295 /** 7296 * Generate random steps 7297 * @param {Number} [length] - The number of steps 7298 * @param {Number} [randomness] - How strong the randomness is 7299 * @return {EasingFunction} 7300 */ 7301 const irregular = (length = 10, randomness = 1) => { 7302 const values = [0]; 7303 const total = length - 1; 7304 for (let i = 1; i < total; i++) { 7305 const previousValue = values[i - 1]; 7306 const spacing = i / total; 7307 const segmentEnd = (i + 1) / total; 7308 const randomVariation = spacing + (segmentEnd - spacing) * Math.random(); 7309 // Mix the even spacing and random variation based on the randomness parameter 7310 const randomValue = spacing * (1 - randomness) + randomVariation * randomness; 7311 values.push(clamp$1(randomValue, previousValue, 1)); 7312 } 7313 values.push(1); 7314 return linear(...values); 7315 }; 7316 7317 var index$3 = /*#__PURE__*/Object.freeze({ 7318 __proto__: null, 7319 Spring: Spring, 7320 createSpring: createSpring, 7321 cubicBezier: cubicBezier, 7322 eases: eases, 7323 irregular: irregular, 7324 linear: linear, 7325 spring: spring, 7326 steps: steps 7327 }); 7328 7329 7330 7331 7332 7333 7334 7335 /** 7336 * Converts an easing function into a valid CSS linear() timing function string 7337 * @param {EasingFunction} fn 7338 * @param {number} [samples=100] 7339 * @returns {string} CSS linear() timing function 7340 */ 7341 const easingToLinear = (fn, samples = 100) => { 7342 const points = []; 7343 for (let i = 0; i <= samples; i++) points.push(round$1(fn(i / samples), 4)); 7344 return `linear(${points.join(', ')})`; 7345 }; 7346 7347 const WAAPIEasesLookups = {}; 7348 7349 /** 7350 * @param {EasingParam} ease 7351 * @return {String} 7352 */ 7353 const parseWAAPIEasing = (ease) => { 7354 let parsedEase = WAAPIEasesLookups[ease]; 7355 if (parsedEase) return parsedEase; 7356 parsedEase = 'linear'; 7357 if (isStr(ease)) { 7358 if ( 7359 stringStartsWith(ease, 'linear') || 7360 stringStartsWith(ease, 'cubic-') || 7361 stringStartsWith(ease, 'steps') || 7362 stringStartsWith(ease, 'ease') 7363 ) { 7364 parsedEase = ease; 7365 } else if (stringStartsWith(ease, 'cubicB')) { 7366 parsedEase = toLowerCase(ease); 7367 } else { 7368 const parsed = parseEaseString(ease); 7369 if (isFnc(parsed)) parsedEase = parsed === none ? 'linear' : easingToLinear(parsed); 7370 } 7371 // Only cache string based easing name, otherwise function arguments get lost 7372 WAAPIEasesLookups[ease] = parsedEase; 7373 } else if (isFnc(ease)) { 7374 const easing = easingToLinear(ease); 7375 if (easing) parsedEase = easing; 7376 } else if (/** @type {Spring} */(ease).ease) { 7377 parsedEase = easingToLinear(/** @type {Spring} */(ease).ease); 7378 } 7379 return parsedEase; 7380 }; 7381 7382 const transformsShorthands = ['x', 'y', 'z']; 7383 const commonDefaultPXProperties = [ 7384 'perspective', 7385 'width', 7386 'height', 7387 'margin', 7388 'padding', 7389 'top', 7390 'right', 7391 'bottom', 7392 'left', 7393 'borderWidth', 7394 'fontSize', 7395 'borderRadius', 7396 ...transformsShorthands 7397 ]; 7398 7399 const validIndividualTransforms = /*#__PURE__*/ (() => [...transformsShorthands, ...validTransforms.filter(t => ['X', 'Y', 'Z'].some(axis => t.endsWith(axis)))])(); 7400 7401 let transformsPropertiesRegistered = null; 7402 7403 /** 7404 * @param {String} propName 7405 * @param {WAAPIKeyframeValue} value 7406 * @param {DOMTarget} $el 7407 * @param {Number} i 7408 * @param {
vendor: 9,429 bytes, lines 7408-7596
7408Number} targetsLength 7409 * @return {String} 7410 */ 7411 const normalizeTweenValue = (propName, value, $el, i, targetsLength) => { 7412 // Do not try to compute strings with getFunctionValue otherwise it will convert CSS variables 7413 let v = isStr(value) ? value : getFunctionValue(/** @type {any} */(value), $el, i, targetsLength); 7414 if (!isNum(v)) return v; 7415 if (commonDefaultPXProperties.includes(propName) || stringStartsWith(propName, 'translate')) return `${v}px`; 7416 if (stringStartsWith(propName, 'rotate') || stringStartsWith(propName, 'skew')) return `${v}deg`; 7417 return `${v}`; 7418 }; 7419 7420 /** 7421 * @param {DOMTarget} $el 7422 * @param {String} propName 7423 * @param {WAAPIKeyframeValue} from 7424 * @param {WAAPIKeyframeValue} to 7425 * @param {Number} i 7426 * @param {Number} targetsLength 7427 * @return {WAAPITweenValue} 7428 */ 7429 const parseIndividualTweenValue = ($el, propName, from, to, i, targetsLength) => { 7430 /** @type {WAAPITweenValue} */ 7431 let tweenValue = '0'; 7432 const computedTo = !isUnd(to) ? normalizeTweenValue(propName, to, $el, i, targetsLength) : getComputedStyle($el)[propName]; 7433 if (!isUnd(from)) { 7434 const computedFrom = normalizeTweenValue(propName, from, $el, i, targetsLength); 7435 tweenValue = [computedFrom, computedTo]; 7436 } else { 7437 tweenValue = isArr(to) ? to.map((/** @type {any} */v) => normalizeTweenValue(propName, v, $el, i, targetsLength)) : computedTo; 7438 } 7439 return tweenValue; 7440 }; 7441 7442 class WAAPIAnimation { 7443 /** 7444 * @param {DOMTargetsParam} targets 7445 * @param {WAAPIAnimationParams} params 7446 */ 7447 constructor(targets, params) { 7448 7449 if (scope.current) scope.current.register(this); 7450 7451 // Skip the registration and fallback to no animation in case CSS.registerProperty is not supported 7452 if (isNil(transformsPropertiesRegistered)) { 7453 if (isBrowser && (isUnd(CSS) || !Object.hasOwnProperty.call(CSS, 'registerProperty'))) { 7454 transformsPropertiesRegistered = false; 7455 } else { 7456 validTransforms.forEach(t => { 7457 const isSkew = stringStartsWith(t, 'skew'); 7458 const isScale = stringStartsWith(t, 'scale'); 7459 const isRotate = stringStartsWith(t, 'rotate'); 7460 const isTranslate = stringStartsWith(t, 'translate'); 7461 const isAngle = isRotate || isSkew; 7462 const syntax = isAngle ? '<angle>' : isScale ? "<number>" : isTranslate ? "<length-percentage>" : "*"; 7463 try { 7464 CSS.registerProperty({ 7465 name: '--' + t, 7466 syntax, 7467 inherits: false, 7468 initialValue: isTranslate ? '0px' : isAngle ? '0deg' : isScale ? '1' : '0', 7469 }); 7470 } catch {} }); 7471 transformsPropertiesRegistered = true; 7472 } 7473 } 7474 7475 const parsedTargets = registerTargets(targets); 7476 const targetsLength = parsedTargets.length; 7477 7478 if (!targetsLength) { 7479 console.warn(`No target found. Make sure the element you're trying to animate is accessible before creating your animation.`); 7480 } 7481 7482 const autoplay = setValue(params.autoplay, globals.defaults.autoplay); 7483 const scroll = autoplay && /** @type {ScrollObserver} */(autoplay).link ? autoplay : false; 7484 const alternate = params.alternate && /** @type {Boolean} */(params.alternate) === true; 7485 const reversed = params.reversed && /** @type {Boolean} */(params.reversed) === true; 7486 const loop = setValue(params.loop, globals.defaults.loop); 7487 const iterations = /** @type {Number} */((loop === true || loop === Infinity) ? Infinity : isNum(loop) ? loop + 1 : 1); 7488 /** @type {PlaybackDirection} */ 7489 const direction = alternate ? reversed ? 'alternate-reverse' : 'alternate' : reversed ? 'reverse' : 'normal'; 7490 /** @type {FillMode} */ 7491 const fill = 'both'; // We use 'both' here because the animation can be reversed during playback 7492 const timeScale = (globals.timeScale === 1 ? 1 : K); 7493 7494 /** @type {DOMTargetsArray}] */ 7495 this.targets = parsedTargets; 7496 /** @type {Array<globalThis.Animation>}] */ 7497 this.animations = []; 7498 /** @type {globalThis.Animation}] */ 7499 this.controlAnimation = null; 7500 /** @type {Callback<this>} */ 7501 this.onComplete = params.onComplete || /** @type {Callback<WAAPIAnimation>} */(/** @type {unknown} */(globals.defaults.onComplete)); 7502 /** @type {Number} */ 7503 this.duration = 0; 7504 /** @type {Boolean} */ 7505 this.muteCallbacks = false; 7506 /** @type {Boolean} */ 7507 this.completed = false; 7508 /** @type {Boolean} */ 7509 this.paused = !autoplay || scroll !== false; 7510 /** @type {Boolean} */ 7511 this.reversed = reversed; 7512 /** @type {Boolean} */ 7513 this.persist = setValue(params.persist, globals.defaults.persist); 7514 /** @type {Boolean|ScrollObserver} */ 7515 this.autoplay = autoplay; 7516 /** @type {Number} */ 7517 this._speed = setValue(params.playbackRate, globals.defaults.playbackRate); 7518 /** @type {Function} */ 7519 this._resolve = noop; // Used by .then() 7520 /** @type {Number} */ 7521 this._completed = 0; 7522 /** @type {Array.<Object>} */ 7523 this._inlineStyles = []; 7524 7525 parsedTargets.forEach(($el, i) => { 7526 7527 const cachedTransforms = $el[transformsSymbol]; 7528 const hasIndividualTransforms = validIndividualTransforms.some(t => params.hasOwnProperty(t)); 7529 const elStyle = $el.style; 7530 const inlineStyles = this._inlineStyles[i] = {}; 7531 7532 const easeToParse = setValue(params.ease, globals.defaults.ease); 7533 7534 const easeFunctionResult = getFunctionValue(easeToParse, $el, i, targetsLength); 7535 const keyEasing = isFnc(easeFunctionResult) || isStr(easeFunctionResult) ? easeFunctionResult : easeToParse; 7536 7537 const spring = /** @type {Spring} */(easeToParse).ease && easeToParse; 7538 /** @type {String} */ 7539 const easing = parseWAAPIEasing(keyEasing); 7540 7541 /** @type {Number} */ 7542 const duration = (spring ? /** @type {Spring} */(spring).settlingDuration : getFunctionValue(setValue(params.duration, globals.defaults.duration), $el, i, targetsLength)) * timeScale; 7543 /** @type {Number} */ 7544 const delay = getFunctionValue(setValue(params.delay, globals.defaults.delay), $el, i, targetsLength) * timeScale; 7545 /** @type {CompositeOperation} */ 7546 const composite = /** @type {CompositeOperation} */(setValue(params.composition, 'replace')); 7547 7548 for (let name in params) { 7549 if (!isKey(name)) continue; 7550 /** @type {PropertyIndexedKeyframes} */ 7551 const keyframes = {}; 7552 /** @type {KeyframeAnimationOptions} */ 7553 const tweenParams = { iterations, direction, fill, easing, duration, delay, composite }; 7554 const propertyValue = params[name]; 7555 const individualTransformProperty = hasIndividualTransforms ? validTransforms.includes(name) ? name : shortTransforms.get(name) : false; 7556 7557 const styleName = individualTransformProperty ? 'transform' : name; 7558 if (!inlineStyles[styleName]) { 7559 inlineStyles[styleName] = elStyle[styleName]; 7560 } 7561 7562 let parsedPropertyValue; 7563 if (isObj(propertyValue)) { 7564 const tweenOptions = /** @type {WAAPITweenOptions} */(propertyValue); 7565 const tweenOptionsEase = setValue(tweenOptions.ease, easing); 7566 const tweenOptionsSpring = /** @type {Spring} */(tweenOptionsEase).ease && tweenOptionsEase; 7567 const to = /** @type {WAAPITweenOptions} */(tweenOptions).to; 7568 const from = /** @type {WAAPITweenOptions} */(tweenOptions).from; 7569 /** @type {Number} */ 7570 tweenParams.duration = (tweenOptionsSpring ? /** @type {Spring} */(tweenOptionsSpring).settlingDuration : getFunctionValue(setValue(tweenOptions.duration, duration), $el, i, targetsLength)) * timeScale; 7571 /** @type {Number} */ 7572 tweenParams.delay = getFunctionValue(setValue(tweenOptions.delay, delay), $el, i, targetsLength) * timeScale; 7573 /** @type {CompositeOperation} */ 7574 tweenParams.composite = /** @type {CompositeOperation} */(setValue(tweenOptions.composition, composite)); 7575 /** @type {String} */ 7576 tweenParams.easing = parseWAAPIEasing(tweenOptionsEase); 7577 parsedPropertyValue = parseIndividualTweenValue($el, name, from, to, i, targetsLength); 7578 if (individualTransformProperty) { 7579 keyframes[`--${individualTransformProperty}`] = parsedPropertyValue; 7580 cachedTransforms[individualTransformProperty] = parsedPropertyValue; 7581 } else { 7582 keyframes[name] = parseIndividualTweenValue($el, name, from, to, i, targetsLength); 7583 } 7584 addWAAPIAnimation(this, $el, name, keyframes, tweenParams); 7585 if (!isUnd(from)) { 7586 if (!individualTransformProperty) { 7587 elStyle[name] = keyframes[name][0]; 7588 } else { 7589 const key = `--${individualTransformProperty}`; 7590 elStyle.setProperty(key, keyframes[key][0]); 7591 } 7592 } 7593 } else { 7594 parsedPropertyValue = isArr(propertyValue) ? 7595 propertyValue.map((/** @type {any} */v) => normalizeTweenValue(name, v, $el, i, targetsLength)) : 7596 normalizeTweenValue(name, /** @type {any} */(propertyValue), $el, i, targetsLength
vendor: 6,274 bytes, lines 7596-7806
7596); 7597 if (individualTransformProperty) { 7598 keyframes[`--${individualTransformProperty}`] = parsedPropertyValue; 7599 cachedTransforms[individualTransformProperty] = parsedPropertyValue; 7600 } else { 7601 keyframes[name] = parsedPropertyValue; 7602 } 7603 addWAAPIAnimation(this, $el, name, keyframes, tweenParams); 7604 } 7605 } 7606 if (hasIndividualTransforms) { 7607 let transforms = emptyString; 7608 for (let t in cachedTransforms) { 7609 transforms += `${transformsFragmentStrings[t]}var(--${t})) `; 7610 } 7611 elStyle.transform = transforms; 7612 } 7613 }); 7614 7615 if (scroll) { 7616 /** @type {ScrollObserver} */(this.autoplay).link(this); 7617 } 7618 } 7619 7620 /** 7621 * @callback forEachCallback 7622 * @param {globalThis.Animation} animation 7623 */ 7624 7625 /** 7626 * @param {forEachCallback|String} callback 7627 * @return {this} 7628 */ 7629 forEach(callback) { 7630 try { 7631 const cb = isStr(callback) ? (/** @type {globalThis.Animation} */a) => a[callback]() : callback; 7632 this.animations.forEach(cb); 7633 } catch {} return this; 7634 } 7635 7636 get speed() { 7637 return this._speed; 7638 } 7639 7640 set speed(speed) { 7641 this._speed = +speed; 7642 this.forEach(anim => anim.playbackRate = speed); 7643 } 7644 7645 get currentTime() { 7646 const controlAnimation = this.controlAnimation; 7647 const timeScale = globals.timeScale; 7648 return this.completed ? this.duration : controlAnimation ? +controlAnimation.currentTime * (timeScale === 1 ? 1 : timeScale) : 0; 7649 } 7650 7651 set currentTime(time) { 7652 const t = time * (globals.timeScale === 1 ? 1 : K); 7653 this.forEach(anim => { 7654 // Make sure the animation playState is not 'paused' in order to properly trigger an onfinish callback. 7655 // The "paused" play state supersedes the "finished" play state; if the animation is both paused and finished, the "paused" state is the one that will be reported. 7656 // https://developer.mozilla.org/en-US/docs/Web/API/Animation/finish_event 7657 // This is not needed for persisting animations since they never finish. 7658 if (!this.persist && t >= this.duration) anim.play(); 7659 anim.currentTime = t; 7660 }); 7661 } 7662 7663 get progress() { 7664 return this.currentTime / this.duration; 7665 } 7666 7667 set progress(progress) { 7668 this.forEach(anim => anim.currentTime = progress * this.duration || 0); 7669 } 7670 7671 resume() { 7672 if (!this.paused) return this; 7673 this.paused = false; 7674 // TODO: Store the current time, and seek back to the last position 7675 return this.forEach('play'); 7676 } 7677 7678 pause() { 7679 if (this.paused) return this; 7680 this.paused = true; 7681 return this.forEach('pause'); 7682 } 7683 7684 alternate() { 7685 this.reversed = !this.reversed; 7686 this.forEach('reverse'); 7687 if (this.paused) this.forEach('pause'); 7688 return this; 7689 } 7690 7691 play() { 7692 if (this.reversed) this.alternate(); 7693 return this.resume(); 7694 } 7695 7696 reverse() { 7697 if (!this.reversed) this.alternate(); 7698 return this.resume(); 7699 } 7700 7701 /** 7702 * @param {Number} time 7703 * @param {Boolean} muteCallbacks 7704 */ 7705 seek(time, muteCallbacks = false) { 7706 if (muteCallbacks) this.muteCallbacks = true; 7707 if (time < this.duration) this.completed = false; 7708 this.currentTime = time; 7709 this.muteCallbacks = false; 7710 if (this.paused) this.pause(); 7711 return this; 7712 } 7713 7714 restart() { 7715 this.completed = false; 7716 return this.seek(0, true).resume(); 7717 } 7718 7719 commitStyles() { 7720 return this.forEach('commitStyles'); 7721 } 7722 7723 complete() { 7724 return this.seek(this.duration); 7725 } 7726 7727 cancel() { 7728 this.muteCallbacks = true; // This prevents triggering the onComplete callback and resolving the Promise 7729 this.commitStyles().forEach('cancel'); 7730 this.animations.length = 0; // Needed to release all animations from memory 7731 requestAnimationFrame(() => { 7732 this.targets.forEach(($el) => { // Needed to avoid unecessary inline transorms 7733 if ($el.style.transform === 'none') $el.style.removeProperty('transform'); 7734 }); 7735 }); 7736 return this; 7737 } 7738 7739 revert() { 7740 // NOTE: We need a better way to revert the transforms, since right now the entire transform property value is reverted, 7741 // This means if you have multiple animations animating different transforms on the same target, 7742 // reverting one of them will also override the transform property of the other animations. 7743 // A better approach would be to store the original custom property values if they exist instead of the entire transform value, 7744 // and update the CSS variables with the orignal value 7745 this.cancel().targets.forEach(($el, i) => { 7746 const targetStyle = $el.style; 7747 const targetInlineStyles = this._inlineStyles[i]; 7748 for (let name in targetInlineStyles) { 7749 const originalInlinedValue = targetInlineStyles[name]; 7750 if (isUnd(originalInlinedValue) || originalInlinedValue === emptyString) { 7751 targetStyle.removeProperty(toLowerCase(name)); 7752 } else { 7753 $el.style[name] = originalInlinedValue; 7754 } 7755 } 7756 // Remove style attribute if empty 7757 if ($el.getAttribute('style') === emptyString) $el.removeAttribute('style'); 7758 }); 7759 return this; 7760 } 7761 7762 /** 7763 * @typedef {this & {then: null}} ResolvedWAAPIAnimation 7764 */ 7765 7766 /** 7767 * @param {Callback<ResolvedWAAPIAnimation>} [callback] 7768 * @return Promise<this> 7769 */ 7770 then(callback = noop) { 7771 const then = this.then; 7772 const onResolve = () => { 7773 this.then = null; 7774 callback(/** @type {ResolvedWAAPIAnimation} */(this)); 7775 this.then = then; 7776 this._resolve = noop; 7777 }; 7778 return new Promise(r => { 7779 this._resolve = () => r(onResolve()); 7780 if (this.completed) this._resolve(); 7781 return this; 7782 }); 7783 } 7784 } 7785 7786 const waapi = { 7787 /** 7788 * @param {DOMTargetsParam} targets 7789 * @param {WAAPIAnimationParams} params 7790 * @return {WAAPIAnimation} 7791 */ 7792 animate: (targets, params) => new WAAPIAnimation(targets, params), 7793 convertEase: easingToLinear 7794 }; 7795 7796 7797 7798 7799 7800 7801 7802 7803 7804 7805 7806
7806/** 7807 * @typedef {DOMTargetSelector|Array<DOMTargetSelector>} LayoutChildrenParam 7808 */ 7809 7810 /** 7811 * @typedef {Object} LayoutAnimationTimingsParams 7812 * @property {Number|FunctionValue} [delay] 7813 * @property {Number|FunctionValue} [duration] 7814 * @property {EasingParam|FunctionValue} [ease] 7815 */ 7816 7817 /** 7818 * @typedef {Record<String, Number|String|FunctionValue>} LayoutStateAnimationProperties 7819 */ 7820 7821 /** 7822 * @typedef {LayoutStateAnimationProperties & LayoutAnimationTimingsParams} LayoutStateParams 7823 */ 7824 7825 /** 7826 * @typedef {Object} LayoutSpecificAnimationParams 7827 * @property {Number|FunctionValue} [delay] 7828 * @property {Number|FunctionValue} [duration] 7829 * @property {EasingParam|FunctionValue} [ease] 7830 * @property {EasingParam} [playbackEase] 7831 * @property {LayoutStateParams} [swapAt] 7832 * @property {LayoutStateParams} [enterFrom] 7833 * @property {LayoutStateParams} [leaveTo] 7834 */ 7835 7836 /** 7837 * @typedef {LayoutSpecificAnimationParams & TimerParams & TickableCallbacks<Timeline> & RenderableCallbacks<Timeline>} LayoutAnimationParams 7838 */ 7839 7840 /** 7841 * @typedef {Object} LayoutOptions 7842 * @property {LayoutChildrenParam} [children] 7843 * @property {Array<String>} [properties] 7844 */ 7845 7846 /** 7847 * @typedef {LayoutAnimationParams & LayoutOptions} AutoLayoutParams 7848 */ 7849 7850 /** 7851 * @typedef {Record<String, Number|String|FunctionValue> & { 7852 * transform: String, 7853 * x: Number, 7854 * y: Number, 7855 * left: Number, 7856 * top: Number, 7857 * clientLeft: Number, 7858 * clientTop: Number, 7859 * width: Number, 7860 * height: Number, 7861 * }} LayoutNodeProperties 7862 */ 7863 7864 /** 7865 * @typedef {Object} LayoutNode 7866 * @property {String} id 7867 * @property {DOMTarget} $el 7868 * @property {Number} index 7869 * @property {Number} total 7870 * @property {Number} delay 7871 * @property {Number} duration 7872 * @property {EasingParam} ease 7873 * @property {DOMTarget} $measure 7874 * @property {LayoutSnapshot} state 7875 * @property {AutoLayout} layout 7876 * @property {LayoutNode|null} parentNode 7877 * @property {Boolean} isTarget 7878 * @property {Boolean} isEntering 7879 * @property {Boolean} isLeaving 7880 * @property {Boolean} hasTransform 7881 * @property {Array<String>} inlineStyles 7882 * @property {String|null} inlineTransforms 7883 * @property {String|null} inlineTransition 7884 * @property {Boolean} branchAdded 7885 * @property {Boolean} branchRemoved 7886 * @property {Boolean} branchNotRendered 7887 * @property {Boolean} sizeChanged 7888 * @property {Boolean} isInlined 7889 * @property {Boolean} hasVisibilitySwap 7890 * @property {Boolean} hasDisplayNone 7891 * @property {Boolean} hasVisibilityHidden 7892 * @property {String|null} measuredInlineTransform 7893 * @property {String|null} measuredInlineTransition 7894 * @property {String|null} measuredDisplay 7895 * @property {String|null} measuredVisibility 7896 * @property {String|null} measuredPosition 7897 * @property {Boolean} measuredHasDisplayNone 7898 * @property {Boolean} measuredHasVisibilityHidden 7899 * @property {Boolean} measuredIsVisible 7900 * @property {Boolean} measuredIsRemoved 7901 * @property {Boolean} measuredIsInsideRoot 7902 * @property {LayoutNodeProperties} properties 7903 * @property {LayoutNode|null} _head 7904 * @property {LayoutNode|null} _tail 7905 * @property {LayoutNode|null} _prev 7906 * @property {LayoutNode|null} _next 7907 */ 7908 7909 /** 7910 * @callback LayoutNodeIterator 7911 * @param {LayoutNode} node 7912 * @param {Number} index 7913 * @return {void} 7914 */ 7915 7916 let layoutId = 0; 7917 let nodeId = 0; 7918 7919 /** 7920 * @param {DOMTarget} root 7921 * @param {DOMTarget} $el 7922 * @return {Boolean} 7923 */ 7924 const isElementInRoot = (root, $el) => { 7925 if (!root || !$el) return false;
vendor: 2,430 bytes, lines 7926-8013
7926 return root === $el || root.contains($el); 7927 }; 7928 7929 /** 7930 * @param {DOMTarget|null} $el 7931 * @return {String|null} 7932 */ 7933 const muteElementTransition = $el => { 7934 if (!$el) return null; 7935 const style = $el.style; 7936 const transition = style.transition || ''; 7937 style.setProperty('transition', 'none', 'important'); 7938 return transition; 7939 }; 7940 7941 /** 7942 * @param {DOMTarget|null} $el 7943 * @param {String|null} transition 7944 */ 7945 const restoreElementTransition = ($el, transition) => { 7946 if (!$el) return; 7947 const style = $el.style; 7948 if (transition) { 7949 style.transition = transition; 7950 } else { 7951 style.removeProperty('transition'); 7952 } 7953 }; 7954 7955 /** 7956 * @param {LayoutNode} node 7957 */ 7958 const muteNodeTransition = node => { 7959 const store = node.layout.transitionMuteStore; 7960 const $el = node.$el; 7961 const $measure = node.$measure; 7962 if ($el && !store.has($el)) store.set($el, muteElementTransition($el)); 7963 if ($measure && !store.has($measure)) store.set($measure, muteElementTransition($measure)); 7964 }; 7965 7966 /** 7967 * @param {Map<DOMTarget, String|null>} store 7968 */ 7969 const restoreLayoutTransition = store => { 7970 store.forEach((value, $el) => restoreElementTransition($el, value)); 7971 store.clear(); 7972 }; 7973 7974 const hiddenComputedStyle = /** @type {CSSStyleDeclaration} */({ 7975 display: 'none', 7976 visibility: 'hidden', 7977 opacity: '0', 7978 transform: 'none', 7979 position: 'static', 7980 }); 7981 7982 /** 7983 * @param {LayoutNode|null} node 7984 */ 7985 const detachNode = node => { 7986 if (!node) return; 7987 const parent = node.parentNode; 7988 if (!parent) return; 7989 if (parent._head === node) parent._head = node._next; 7990 if (parent._tail === node) parent._tail = node._prev; 7991 if (node._prev) node._prev._next = node._next; 7992 if (node._next) node._next._prev = node._prev; 7993 node._prev = null; 7994 node._next = null; 7995 node.parentNode = null; 7996 }; 7997 7998 /** 7999 * @param {DOMTarget} $el 8000 * @param {LayoutNode|null} parentNode 8001 * @param {LayoutSnapshot} state 8002 * @param {LayoutNode} recycledNode 8003 * @return {LayoutNode} 8004 */ 8005 const createNode = ($el, parentNode, state, recycledNode) => { 8006 let dataId = $el.dataset.layoutId; 8007 if (!dataId) dataId = $el.dataset.layoutId = `node-${nodeId++}`; 8008 const node = recycledNode ? recycledNode : /** @type {LayoutNode} */({}); 8009 node.$el = $el; 8010 node.$measure = $el; 8011 node.id = dataId; 8012 node.index = 0; 8013 node.total = 1
vendor: 6,851 bytes, lines 8013-8199
8013; 8014 node.delay = 0; 8015 node.duration = 0; 8016 node.ease = null; 8017 node.state = state; 8018 node.layout = state.layout; 8019 node.parentNode = parentNode || null; 8020 node.isTarget = false; 8021 node.isEntering = false; 8022 node.isLeaving = false; 8023 node.isInlined = false; 8024 node.hasTransform = false; 8025 node.inlineStyles = []; 8026 node.inlineTransforms = null; 8027 node.inlineTransition = null; 8028 node.branchAdded = false; 8029 node.branchRemoved = false; 8030 node.branchNotRendered = false; 8031 node.sizeChanged = false; 8032 node.hasVisibilitySwap = false; 8033 node.hasDisplayNone = false; 8034 node.hasVisibilityHidden = false; 8035 node.measuredInlineTransform = null; 8036 node.measuredInlineTransition = null; 8037 node.measuredDisplay = null; 8038 node.measuredVisibility = null; 8039 node.measuredPosition = null; 8040 node.measuredHasDisplayNone = false; 8041 node.measuredHasVisibilityHidden = false; 8042 node.measuredIsVisible = false; 8043 node.measuredIsRemoved = false; 8044 node.measuredIsInsideRoot = false; 8045 node.properties = /** @type {LayoutNodeProperties} */({ 8046 transform: 'none', 8047 x: 0, 8048 y: 0, 8049 left: 0, 8050 top: 0, 8051 clientLeft: 0, 8052 clientTop: 0, 8053 width: 0, 8054 height: 0, 8055 }); 8056 node.layout.properties.forEach(prop => node.properties[prop] = 0); 8057 node._head = null; 8058 node._tail = null; 8059 node._prev = null; 8060 node._next = null; 8061 return node; 8062 }; 8063 8064 /** 8065 * @param {LayoutNode} node 8066 * @param {DOMTarget} $measure 8067 * @param {CSSStyleDeclaration} computedStyle 8068 * @param {Boolean} skipMeasurements 8069 * @return {LayoutNode} 8070 */ 8071 const recordNodeState = (node, $measure, computedStyle, skipMeasurements) => { 8072 const $el = node.$el; 8073 const root = node.layout.root; 8074 const isRoot = root === $el; 8075 const properties = node.properties; 8076 const rootNode = node.state.rootNode; 8077 const parentNode = node.parentNode; 8078 const computedTransforms = computedStyle.transform; 8079 const inlineTransforms = $el.style.transform; 8080 const parentNotRendered = parentNode ? parentNode.measuredIsRemoved : false; 8081 const position = computedStyle.position; 8082 if (isRoot) node.layout.absoluteCoords = position === 'fixed' || position === 'absolute'; 8083 node.$measure = $measure; 8084 node.inlineTransforms = inlineTransforms; 8085 node.hasTransform = computedTransforms && computedTransforms !== 'none'; 8086 node.measuredIsInsideRoot = isElementInRoot(root, $measure); 8087 node.measuredInlineTransform = null; 8088 node.measuredDisplay = computedStyle.display; 8089 node.measuredVisibility = computedStyle.visibility; 8090 node.measuredPosition = position; 8091 node.measuredHasDisplayNone = computedStyle.display === 'none'; 8092 node.measuredHasVisibilityHidden = computedStyle.visibility === 'hidden'; 8093 node.measuredIsVisible = !(node.measuredHasDisplayNone || node.measuredHasVisibilityHidden); 8094 node.measuredIsRemoved = node.measuredHasDisplayNone || node.measuredHasVisibilityHidden || parentNotRendered; 8095 // Check if element has adjacent text that would reflow when taken out of flow 8096 let hasAdjacentText = false; 8097 let s = $el.previousSibling; 8098 while (s && (s.nodeType === Node.COMMENT_NODE || (s.nodeType === Node.TEXT_NODE && !s.textContent.trim()))) s = s.previousSibling; 8099 if (s && s.nodeType === Node.TEXT_NODE) { 8100 hasAdjacentText = true; 8101 } else { 8102 s = $el.nextSibling; 8103 while (s && (s.nodeType === Node.COMMENT_NODE || (s.nodeType === Node.TEXT_NODE && !s.textContent.trim()))) s = s.nextSibling; 8104 hasAdjacentText = s !== null && s.nodeType === Node.TEXT_NODE; 8105 } 8106 node.isInlined = hasAdjacentText; 8107 8108 // Mute transforms (and transition to avoid triggering an animation) before the position calculation 8109 if (node.hasTransform && !skipMeasurements) { 8110 const transitionMuteStore = node.layout.transitionMuteStore; 8111 if (!transitionMuteStore.get($el)) node.inlineTransition = muteElementTransition($el); 8112 if ($measure === $el) { 8113 $el.style.transform = 'none'; 8114 } else { 8115 if (!transitionMuteStore.get($measure)) node.measuredInlineTransition = muteElementTransition($measure); 8116 node.measuredInlineTransform = $measure.style.transform; 8117 $measure.style.transform = 'none'; 8118 } 8119 } 8120 8121 let left = 0; 8122 let top = 0; 8123 let width = 0; 8124 let height = 0; 8125 8126 if (!skipMeasurements) { 8127 const rect = $measure.getBoundingClientRect(); 8128 left = rect.left; 8129 top = rect.top; 8130 width = rect.width; 8131 height = rect.height; 8132 } 8133 8134 for (let name in properties) { 8135 const computedProp = name === 'transform' ? computedTransforms : computedStyle[name] || (computedStyle.getPropertyValue && computedStyle.getPropertyValue(name)); 8136 if (!isUnd(computedProp)) properties[name] = computedProp; 8137 } 8138 8139 properties.left = left; 8140 properties.top = top; 8141 properties.clientLeft = skipMeasurements ? 0 : $measure.clientLeft; 8142 properties.clientTop = skipMeasurements ? 0 : $measure.clientTop; 8143 // Compute local x/y relative to parent 8144 let absoluteLeft, absoluteTop; 8145 if (isRoot) { 8146 if (!node.layout.absoluteCoords) { 8147 absoluteLeft = 0; 8148 absoluteTop = 0; 8149 } else { 8150 absoluteLeft = left; 8151 absoluteTop = top; 8152 } 8153 } else { 8154 const p = parentNode || rootNode; 8155 const parentLeft = p.properties.left; 8156 const parentTop = p.properties.top; 8157 const borderLeft = p.properties.clientLeft; 8158 const borderTop = p.properties.clientTop; 8159 if (!node.layout.absoluteCoords) { 8160 if (p === rootNode) { 8161 const rootLeft = rootNode.properties.left; 8162 const rootTop = rootNode.properties.top; 8163 const rootBorderLeft = rootNode.properties.clientLeft; 8164 const rootBorderTop = rootNode.properties.clientTop; 8165 absoluteLeft = left - rootLeft - rootBorderLeft; 8166 absoluteTop = top - rootTop - rootBorderTop; 8167 } else { 8168 absoluteLeft = left - parentLeft - borderLeft; 8169 absoluteTop = top - parentTop - borderTop; 8170 } 8171 } else { 8172 absoluteLeft = left - parentLeft - borderLeft; 8173 absoluteTop = top - parentTop - borderTop; 8174 } 8175 } 8176 properties.x = absoluteLeft; 8177 properties.y = absoluteTop; 8178 properties.width = width; 8179 properties.height = height; 8180 return node; 8181 }; 8182 8183 /** 8184 * @param {LayoutNode} node 8185 * @param {LayoutStateAnimationProperties} [props] 8186 */ 8187 const updateNodeProperties = (node, props) => { 8188 if (!props) return; 8189 for (let name in props) { 8190 node.properties[name] = props[name]; 8191 } 8192 }; 8193 8194 /** 8195 * @param {LayoutNode} node 8196 * @param {LayoutAnimationTimingsParams} params 8197 */ 8198 const updateNodeTimingParams = (node, params) => { 8199 const easeFunctionResult = getFunctionValue(params.ease, node.$el, node.index, node.t
8199otal); 8200 const keyEasing = isFnc(easeFunctionResult) ? easeFunctionResult : params.ease; 8201 const hasSpring = !isUnd(keyEasing) && !isUnd(/** @type {Spring} */(keyEasing).ease); 8202 node.ease = hasSpring ? /** @type {Spring} */(keyEasing).ease : keyEasing; 8203 node.duration = hasSpring ? /** @type {Spring} */(keyEasing).settlingDuration : getFunctionValue(params.duration, node.$el, node.index, node.total); 8204 node.delay = getFunctionValue(params.delay, node.$el, node.index, node.tota
vendor: 12,531 bytes, lines 8204-8577
8204l); 8205 }; 8206 8207 /** 8208 * @param {LayoutNode} node 8209 */ 8210 const recordNodeInlineStyles = node => { 8211 const style = node.$el.style; 8212 const stylesStore = node.inlineStyles; 8213 stylesStore.length = 0; 8214 node.layout.recordedProperties.forEach(prop => { 8215 stylesStore.push(prop, style[prop] || ''); 8216 }); 8217 }; 8218 8219 /** 8220 * @param {LayoutNode} node 8221 */ 8222 const restoreNodeInlineStyles = node => { 8223 const style = node.$el.style; 8224 const stylesStore = node.inlineStyles; 8225 for (let i = 0, l = stylesStore.length; i < l; i += 2) { 8226 const property = stylesStore[i]; 8227 const styleValue = stylesStore[i + 1]; 8228 if (styleValue && styleValue !== '') { 8229 style[property] = styleValue; 8230 } else { 8231 style[property] = ''; 8232 style.removeProperty(property); 8233 } 8234 } 8235 }; 8236 8237 /** 8238 * @param {LayoutNode} node 8239 */ 8240 const restoreNodeTransform = node => { 8241 const inlineTransforms = node.inlineTransforms; 8242 const nodeStyle = node.$el.style; 8243 if (!node.hasTransform || !inlineTransforms || (node.hasTransform && nodeStyle.transform === 'none') || (inlineTransforms && inlineTransforms === 'none')) { 8244 nodeStyle.removeProperty('transform'); 8245 } else if (inlineTransforms) { 8246 nodeStyle.transform = inlineTransforms; 8247 } 8248 const $measure = node.$measure; 8249 if (node.hasTransform && $measure !== node.$el) { 8250 const measuredStyle = $measure.style; 8251 const measuredInline = node.measuredInlineTransform; 8252 if (measuredInline && measuredInline !== '') { 8253 measuredStyle.transform = measuredInline; 8254 } else { 8255 measuredStyle.removeProperty('transform'); 8256 } 8257 } 8258 node.measuredInlineTransform = null; 8259 if (node.inlineTransition !== null) { 8260 restoreElementTransition(node.$el, node.inlineTransition); 8261 node.inlineTransition = null; 8262 } 8263 if ($measure !== node.$el && node.measuredInlineTransition !== null) { 8264 restoreElementTransition($measure, node.measuredInlineTransition); 8265 node.measuredInlineTransition = null; 8266 } 8267 }; 8268 8269 /** 8270 * @param {LayoutNode} node 8271 */ 8272 const restoreNodeVisualState = node => { 8273 if (node.measuredIsRemoved || node.hasVisibilitySwap) { 8274 node.$el.style.removeProperty('display'); 8275 node.$el.style.removeProperty('visibility'); 8276 if (node.hasVisibilitySwap) { 8277 node.$measure.style.removeProperty('display'); 8278 node.$measure.style.removeProperty('visibility'); 8279 } 8280 } 8281 // if (node.measuredIsRemoved) { 8282 node.layout.pendingRemoval.delete(node.$el); 8283 // } 8284 }; 8285 8286 /** 8287 * @param {LayoutNode} node 8288 * @param {LayoutNode} targetNode 8289 * @param {LayoutSnapshot} newState 8290 * @return {LayoutNode} 8291 */ 8292 const cloneNodeProperties = (node, targetNode, newState) => { 8293 targetNode.properties = /** @type {LayoutNodeProperties} */({ ...node.properties }); 8294 targetNode.state = newState; 8295 targetNode.isTarget = node.isTarget; 8296 targetNode.hasTransform = node.hasTransform; 8297 targetNode.inlineTransforms = node.inlineTransforms; 8298 targetNode.measuredIsVisible = node.measuredIsVisible; 8299 targetNode.measuredDisplay = node.measuredDisplay; 8300 targetNode.measuredIsRemoved = node.measuredIsRemoved; 8301 targetNode.measuredHasDisplayNone = node.measuredHasDisplayNone; 8302 targetNode.measuredHasVisibilityHidden = node.measuredHasVisibilityHidden; 8303 targetNode.hasDisplayNone = node.hasDisplayNone; 8304 targetNode.isInlined = node.isInlined; 8305 targetNode.hasVisibilityHidden = node.hasVisibilityHidden; 8306 return targetNode; 8307 }; 8308 8309 class LayoutSnapshot { 8310 /** 8311 * @param {AutoLayout} layout 8312 */ 8313 constructor(layout) { 8314 /** @type {AutoLayout} */ 8315 this.layout = layout; 8316 /** @type {LayoutNode|null} */ 8317 this.rootNode = null; 8318 /** @type {Set<LayoutNode>} */ 8319 this.rootNodes = new Set(); 8320 /** @type {Map<String, LayoutNode>} */ 8321 this.nodes = new Map(); 8322 /** @type {Number} */ 8323 this.scrollX = 0; 8324 /** @type {Number} */ 8325 this.scrollY = 0; 8326 } 8327 8328 /** 8329 * @return {this} 8330 */ 8331 revert() { 8332 this.forEachNode(node => { 8333 this.layout.pendingRemoval.delete(node.$el); 8334 node.$el.removeAttribute('data-layout-id'); 8335 node.$measure.removeAttribute('data-layout-id'); 8336 }); 8337 this.rootNode = null; 8338 this.rootNodes.clear(); 8339 this.nodes.clear(); 8340 return this; 8341 } 8342 8343 /** 8344 * @param {DOMTarget} $el 8345 * @return {LayoutNode} 8346 */ 8347 getNode($el) { 8348 if (!$el || !$el.dataset) return; 8349 return this.nodes.get($el.dataset.layoutId); 8350 } 8351 8352 /** 8353 * @param {DOMTarget} $el 8354 * @param {String} prop 8355 * @return {Number|String} 8356 */ 8357 getComputedValue($el, prop) { 8358 const node = this.getNode($el); 8359 if (!node) return; 8360 return /** @type {Number|String} */(node.properties[prop]); 8361 } 8362 8363 /** 8364 * @param {LayoutNode|null} rootNode 8365 * @param {LayoutNodeIterator} cb 8366 */ 8367 forEach(rootNode, cb) { 8368 let node = rootNode; 8369 let i = 0; 8370 while (node) { 8371 cb(node, i++); 8372 if (node._head) { 8373 node = node._head; 8374 } else if (node._next) { 8375 node = node._next; 8376 } else { 8377 while (node && !node._next) { 8378 node = node.parentNode; 8379 } 8380 if (node) node = node._next; 8381 } 8382 } 8383 } 8384 8385 /** 8386 * @param {LayoutNodeIterator} cb 8387 */ 8388 forEachRootNode(cb) { 8389 this.forEach(this.rootNode, cb); 8390 } 8391 8392 /** 8393 * @param {LayoutNodeIterator} cb 8394 */ 8395 forEachNode(cb) { 8396 for (const rootNode of this.rootNodes) { 8397 this.forEach(rootNode, cb); 8398 } 8399 } 8400 8401 /** 8402 * @param {DOMTarget} $el 8403 * @param {LayoutNode|null} parentNode 8404 * @return {LayoutNode|null} 8405 */ 8406 registerElement($el, parentNode) { 8407 if (!$el || $el.nodeType !== 1) return null; 8408 8409 if (!this.layout.transitionMuteStore.has($el)) this.layout.transitionMuteStore.set($el, muteElementTransition($el)); 8410 8411 /** @type {Array<DOMTarget|LayoutNode|null>} */ 8412 const stack = [$el, parentNode]; 8413 const root = this.layout.root; 8414 let firstNode = null; 8415 8416 while (stack.length) { 8417 /** @type {LayoutNode|null} */ 8418 const $parent = /** @type {LayoutNode|null} */(stack.pop()); 8419 /** @type {DOMTarget|null} */ 8420 const $current = /** @type {DOMTarget|null} */(stack.pop()); 8421 8422 if (!$current || $current.nodeType !== 1 || isSvg($current)) continue; 8423 8424 const skipMeasurements = $parent ? $parent.measuredIsRemoved : false; 8425 const computedStyle = skipMeasurements ? hiddenComputedStyle : getComputedStyle($current); 8426 const hasDisplayNone = skipMeasurements ? true : computedStyle.display === 'none'; 8427 const hasVisibilityHidden = skipMeasurements ? true : computedStyle.visibility === 'hidden'; 8428 const isVisible = !hasDisplayNone && !hasVisibilityHidden; 8429 const existingId = $current.dataset.layoutId; 8430 const isInsideRoot = isElementInRoot(root, $current); 8431 8432 let node = existingId ? this.nodes.get(existingId) : null; 8433 8434 if (node && node.$el !== $current) { 8435 const nodeInsideRoot = isElementInRoot(root, node.$el); 8436 const measuredVisible = node.measuredIsVisible; 8437 const shouldReassignNode = !nodeInsideRoot && (isInsideRoot || (!isInsideRoot && !measuredVisible && isVisible)); 8438 const shouldReuseMeasurements = nodeInsideRoot && !measuredVisible && isVisible; 8439 // Rebind nodes that move into the root or whose detached twin just became visible 8440 if (shouldReassignNode) { 8441 detachNode(node); 8442 node = createNode($current, $parent, this, node); 8443 // for hidden element with in-root sibling, keep the hidden node but borrow measurements from its visible in-root twin element 8444 } else if (shouldReuseMeasurements) { 8445 recordNodeState(node, $current, computedStyle, skipMeasurements); 8446 let $child = $current.lastElementChild; 8447 while ($child) { 8448 stack.push(/** @type {DOMTarget} */($child), node); 8449 $child = $child.previousElementSibling; 8450 } 8451 if (!firstNode) firstNode = node; 8452 continue; 8453 // No reassignment needed so keep walking descendants under the current parent 8454 } else { 8455 let $child = $current.lastElementChild; 8456 while ($child) { 8457 stack.push(/** @type {DOMTarget} */($child), $parent); 8458 $child = $child.previousElementSibling; 8459 } 8460 if (!firstNode) firstNode = node; 8461 continue; 8462 } 8463 } else { 8464 node = createNode($current, $parent, this, node); 8465 } 8466 8467 node.branchAdded = false; 8468 node.branchRemoved = false; 8469 node.branchNotRendered = false; 8470 node.isTarget = false; 8471 node.sizeChanged = false; 8472 node.hasVisibilityHidden = hasVisibilityHidden; 8473 node.hasDisplayNone = hasDisplayNone; 8474 node.hasVisibilitySwap = (hasVisibilityHidden && !node.measuredHasVisibilityHidden) || (hasDisplayNone && !node.measuredHasDisplayNone); 8475 8476 this.nodes.set(node.id, node); 8477 8478 node.parentNode = $parent || null; 8479 node._prev = null; 8480 node._next = null; 8481 8482 if ($parent) { 8483 this.rootNodes.delete(node); 8484 if (!$parent._head) { 8485 $parent._head = node; 8486 $parent._tail = node; 8487 } else { 8488 $parent._tail._next = node; 8489 node._prev = $parent._tail; 8490 $parent._tail = node; 8491 } 8492 } else { 8493 // Each disconnected subtree becomes its own root in the snapshot graph 8494 this.rootNodes.add(node); 8495 } 8496 8497 recordNodeState(node, node.$el, computedStyle, skipMeasurements); 8498 8499 let $child = $current.lastElementChild; 8500 while ($child) { 8501 stack.push(/** @type {DOMTarget} */($child), node); 8502 $child = $child.previousElementSibling; 8503 } 8504 8505 if (!firstNode) firstNode = node; 8506 } 8507 8508 return firstNode; 8509 } 8510 8511 /** 8512 * @param {DOMTarget} $el 8513 * @param {Set<DOMTarget>} candidates 8514 * @return {LayoutNode|null} 8515 */ 8516 ensureDetachedNode($el, candidates) { 8517 if (!$el || $el === this.layout.root) return null; 8518 const existingId = $el.dataset.layoutId; 8519 const existingNode = existingId ? this.nodes.get(existingId) : null; 8520 if (existingNode && existingNode.$el === $el) return existingNode; 8521 let parentNode = null; 8522 let $ancestor = $el.parentElement; 8523 while ($ancestor && $ancestor !== this.layout.root) { 8524 if (candidates.has($ancestor)) { 8525 parentNode = this.ensureDetachedNode($ancestor, candidates); 8526 break; 8527 } 8528 $ancestor = $ancestor.parentElement; 8529 } 8530 return this.registerElement($el, parentNode); 8531 } 8532 8533 /** 8534 * @return {this} 8535 */ 8536 record() { 8537 const layout = this.layout; 8538 const children = layout.children; 8539 const root = layout.root; 8540 const toParse = isArr(children) ? children : [children]; 8541 const scoped = []; 8542 const scopeRoot = children === '*' ? root : scope.root; 8543 8544 // Mute transition and transforms of root ancestors before recording the state 8545 8546 /** @type {Array<DOMTarget|String|null>} */ 8547 const rootAncestorTransformStore = []; 8548 let $ancestor = root.parentElement; 8549 while ($ancestor && $ancestor.nodeType === 1) { 8550 const computedStyle = getComputedStyle($ancestor); 8551 if (computedStyle.transform && computedStyle.transform !== 'none') { 8552 const inlineTransform = $ancestor.style.transform || ''; 8553 const inlineTransition = muteElementTransition($ancestor); 8554 rootAncestorTransformStore.push($ancestor, inlineTransform, inlineTransition); 8555 $ancestor.style.transform = 'none'; 8556 } 8557 $ancestor = $ancestor.parentElement; 8558 } 8559 8560 for (let i = 0, l = toParse.length; i < l; i++) { 8561 const child = toParse[i]; 8562 scoped[i] = isStr(child) ? scopeRoot.querySelectorAll(child) : child; 8563 } 8564 8565 const parsedChildren = registerTargets(scoped); 8566 8567 this.nodes.clear(); 8568 this.rootNodes.clear(); 8569 8570 const rootNode = this.registerElement(root, null); 8571 // Root node are always targets 8572 rootNode.isTarget = true; 8573 this.rootNode = rootNode; 8574 8575 const inRootNodeIds = new Set(); 8576 // Update index and total for inital timing calculation 8577 let index = 0
8577, total = this.nodes.size;
vendor: 40,764 bytes, lines 8578-9603
8578 this.nodes.forEach((node, id) => { 8579 node.index = index++; 8580 node.total = total; 8581 // Track ids of nodes that belong to the current root to filter detached matches 8582 if (node && node.measuredIsInsideRoot) { 8583 inRootNodeIds.add(id); 8584 } 8585 }); 8586 8587 // Elements with a layout id outside the root that match the children selector 8588 const detachedElementsLookup = new Set(); 8589 const orderedDetachedElements = []; 8590 8591 for (let i = 0, l = parsedChildren.length; i < l; i++) { 8592 const $el = parsedChildren[i]; 8593 if (!$el || $el.nodeType !== 1 || $el === root) continue; 8594 const insideRoot = isElementInRoot(root, $el); 8595 if (!insideRoot) { 8596 const layoutNodeId = $el.dataset.layoutId; 8597 if (!layoutNodeId || !inRootNodeIds.has(layoutNodeId)) continue; 8598 } 8599 if (!detachedElementsLookup.has($el)) { 8600 detachedElementsLookup.add($el); 8601 orderedDetachedElements.push($el); 8602 } 8603 } 8604 8605 for (let i = 0, l = orderedDetachedElements.length; i < l; i++) { 8606 this.ensureDetachedNode(orderedDetachedElements[i], detachedElementsLookup); 8607 } 8608 8609 for (let i = 0, l = parsedChildren.length; i < l; i++) { 8610 const $el = parsedChildren[i]; 8611 const node = this.getNode($el); 8612 if (node) { 8613 let cur = node; 8614 while (cur) { 8615 if (cur.isTarget) break; 8616 cur.isTarget = true; 8617 cur = cur.parentNode; 8618 } 8619 } 8620 } 8621 8622 this.scrollX = window.scrollX; 8623 this.scrollY = window.scrollY; 8624 8625 this.forEachNode(restoreNodeTransform); 8626 8627 // Restore transition and transforms of root ancestors 8628 8629 for (let i = 0, l = rootAncestorTransformStore.length; i < l; i += 3) { 8630 const $el = /** @type {DOMTarget} */(rootAncestorTransformStore[i]); 8631 const inlineTransform = /** @type {String} */(rootAncestorTransformStore[i + 1]); 8632 const inlineTransition = /** @type {String|null} */(rootAncestorTransformStore[i + 2]); 8633 if (inlineTransform && inlineTransform !== '') { 8634 $el.style.transform = inlineTransform; 8635 } else { 8636 $el.style.removeProperty('transform'); 8637 } 8638 restoreElementTransition($el, inlineTransition); 8639 } 8640 8641 return this; 8642 } 8643 } 8644 8645 /** 8646 * @param {LayoutStateParams} params 8647 * @return {[LayoutStateAnimationProperties, LayoutAnimationTimingsParams]} 8648 */ 8649 function splitPropertiesFromParams(params) { 8650 /** @type {LayoutStateAnimationProperties} */ 8651 const properties = {}; 8652 /** @type {LayoutAnimationTimingsParams} */ 8653 const parameters = {}; 8654 for (let name in params) { 8655 const value = params[name]; 8656 const isEase = name === 'ease'; 8657 const isTiming = name === 'duration' || name === 'delay'; 8658 if (isTiming || isEase) { 8659 if (isEase) { 8660 parameters[name] = /** @type {EasingParam} */(value); 8661 } else { 8662 parameters[name] = /** @type {Number|FunctionValue} */(value); 8663 } 8664 } else { 8665 properties[name] = /** @type {Number|String} */(value); 8666 } 8667 } 8668 return [properties, parameters]; 8669 } 8670 8671 class AutoLayout { 8672 /** 8673 * @param {DOMTargetSelector} root 8674 * @param {AutoLayoutParams} [params] 8675 */ 8676 constructor(root, params = {}) { 8677 if (scope.current) scope.current.register(this); 8678 const swapAtSplitParams = splitPropertiesFromParams(params.swapAt); 8679 const enterFromSplitParams = splitPropertiesFromParams(params.enterFrom); 8680 const leaveToSplitParams = splitPropertiesFromParams(params.leaveTo); 8681 const transitionProperties = params.properties; 8682 /** @type {Number|FunctionValue} */ 8683 params.duration = setValue(params.duration, 350); 8684 /** @type {Number|FunctionValue} */ 8685 params.delay = setValue(params.delay, 0); 8686 /** @type {EasingParam|FunctionValue} */ 8687 params.ease = setValue(params.ease, 'inOut(3.5)'); 8688 /** @type {AutoLayoutParams} */ 8689 this.params = params; 8690 /** @type {DOMTarget} */ 8691 this.root = /** @type {DOMTarget} */(registerTargets(root)[0]); 8692 /** @type {Number} */ 8693 this.id = layoutId++; 8694 /** @type {LayoutChildrenParam} */ 8695 this.children = params.children || '*'; 8696 /** @type {Boolean} */ 8697 this.absoluteCoords = false; 8698 /** @type {LayoutStateParams} */ 8699 this.swapAtParams = mergeObjects(params.swapAt || { opacity: 0 }, { ease: 'inOut(1.75)' }); 8700 /** @type {LayoutStateParams} */ 8701 this.enterFromParams = params.enterFrom || { opacity: 0 }; 8702 /** @type {LayoutStateParams} */ 8703 this.leaveToParams = params.leaveTo || { opacity: 0 }; 8704 /** @type {Set<String>} */ 8705 this.properties = new Set([ 8706 'opacity', 8707 'fontSize', 8708 'color', 8709 'backgroundColor', 8710 'borderRadius', 8711 'border', 8712 'filter', 8713 'clipPath', 8714 ]); 8715 if (swapAtSplitParams[0]) for (let name in swapAtSplitParams[0]) this.properties.add(name); 8716 if (enterFromSplitParams[0]) for (let name in enterFromSplitParams[0]) this.properties.add(name); 8717 if (leaveToSplitParams[0]) for (let name in leaveToSplitParams[0]) this.properties.add(name); 8718 if (transitionProperties) for (let i = 0, l = transitionProperties.length; i < l; i++) this.properties.add(transitionProperties[i]); 8719 /** @type {Set<String>} */ 8720 this.recordedProperties = new Set([ 8721 'display', 8722 'visibility', 8723 'translate', 8724 'position', 8725 'left', 8726 'top', 8727 'marginLeft', 8728 'marginTop', 8729 'width', 8730 'height', 8731 'maxWidth', 8732 'maxHeight', 8733 'minWidth', 8734 'minHeight', 8735 ]); 8736 this.properties.forEach(prop => this.recordedProperties.add(prop)); 8737 /** @type {WeakSet<DOMTarget>} */ 8738 this.pendingRemoval = new WeakSet(); 8739 /** @type {Map<DOMTarget, String|null>} */ 8740 this.transitionMuteStore = new Map(); 8741 /** @type {LayoutSnapshot} */ 8742 this.oldState = new LayoutSnapshot(this); 8743 /** @type {LayoutSnapshot} */ 8744 this.newState = new LayoutSnapshot(this); 8745 /** @type {Timeline} */ 8746 this.timeline = null; 8747 /** @type {WAAPIAnimation} */ 8748 this.transformAnimation = null; 8749 /** @type {Array<DOMTarget>} */ 8750 this.animating = []; 8751 /** @type {Array<DOMTarget>} */ 8752 this.swapping = []; 8753 /** @type {Array<DOMTarget>} */ 8754 this.leaving = []; 8755 /** @type {Array<DOMTarget>} */ 8756 this.entering = []; 8757 // Record the current state as the old state to init the data attributes and allow imediate .animate() 8758 this.oldState.record(); 8759 // And all layout transition muted during the record 8760 restoreLayoutTransition(this.transitionMuteStore); 8761 } 8762 8763 /** 8764 * @return {this} 8765 */ 8766 revert() { 8767 this.root.classList.remove('is-animated'); 8768 if (this.timeline) { 8769 this.timeline.complete(); 8770 this.timeline = null; 8771 } 8772 if (this.transformAnimation) { 8773 this.transformAnimation.complete(); 8774 this.transformAnimation = null; 8775 } 8776 this.animating.length = this.swapping.length = this.leaving.length = this.entering.length = 0; 8777 this.oldState.revert(); 8778 this.newState.revert(); 8779 requestAnimationFrame(() => restoreLayoutTransition(this.transitionMuteStore)); 8780 return this; 8781 } 8782 8783 /** 8784 * @return {this} 8785 */ 8786 record() { 8787 // Commit transforms before measuring 8788 if (this.transformAnimation) { 8789 this.transformAnimation.cancel(); 8790 this.transformAnimation = null; 8791 } 8792 // Record the old state 8793 this.oldState.record(); 8794 // Cancel any running timeline 8795 if (this.timeline) { 8796 this.timeline.cancel(); 8797 this.timeline = null; 8798 } 8799 // Restore previously captured inline styles 8800 this.newState.forEachRootNode(restoreNodeInlineStyles); 8801 return this; 8802 } 8803 8804 /** 8805 * @param {LayoutAnimationParams} [params] 8806 * @return {Timeline} 8807 */ 8808 animate(params = {}) { 8809 /** @type { LayoutAnimationTimingsParams } */ 8810 const animationTimings = { 8811 ease: setValue(params.ease, this.params.ease), 8812 delay: setValue(params.delay, this.params.delay), 8813 duration: setValue(params.duration, this.params.duration), 8814 }; 8815 /** @type {TimelineParams} */ 8816 const tlParams = {}; 8817 const onComplete = setValue(params.onComplete, this.params.onComplete); 8818 const onPause = setValue(params.onPause, this.params.onPause); 8819 for (let name in defaults) { 8820 if (name !== 'ease' && name !== 'duration' && name !== 'delay') { 8821 if (!isUnd(params[name])) { 8822 tlParams[name] = params[name]; 8823 } else if (!isUnd(this.params[name])) { 8824 tlParams[name] = this.params[name]; 8825 } 8826 } 8827 } 8828 tlParams.onComplete = () => { 8829 // Make sure to call .cancel() after restoreNodeInlineStyles(node); otehrwise the commited styles get reverted 8830 if (this.transformAnimation) this.transformAnimation.cancel(); 8831 newState.forEachRootNode(node => { 8832 restoreNodeVisualState(node); 8833 restoreNodeInlineStyles(node); 8834 }); 8835 for (let i = 0, l = transformed.length; i < l; i++) { 8836 const $el = transformed[i]; 8837 $el.style.transform = newState.getComputedValue($el, 'transform'); 8838 } 8839 if (this.root.classList.contains('is-animated')) { 8840 this.root.classList.remove('is-animated'); 8841 if (onComplete) onComplete(this.timeline); 8842 } 8843 // Avoid CSS transitions at the end of the animation by restoring them on the next frame 8844 requestAnimationFrame(() => { 8845 if (this.root.classList.contains('is-animated')) return; 8846 restoreLayoutTransition(this.transitionMuteStore); 8847 }); 8848 }; 8849 tlParams.onPause = () => { 8850 if (!this.root.classList.contains('is-animated')) return; 8851 if (this.transformAnimation) this.transformAnimation.cancel(); 8852 newState.forEachRootNode(restoreNodeVisualState); 8853 this.root.classList.remove('is-animated'); 8854 if (onComplete) onComplete(this.timeline); 8855 if (onPause) onPause(this.timeline); 8856 }; 8857 tlParams.composition = false; 8858 8859 const swapAtParams = mergeObjects(mergeObjects(params.swapAt || {}, this.swapAtParams), animationTimings); 8860 const enterFromParams = mergeObjects(mergeObjects(params.enterFrom || {}, this.enterFromParams), animationTimings); 8861 const leaveToParams = mergeObjects(mergeObjects(params.leaveTo || {}, this.leaveToParams), animationTimings); 8862 const [ swapAtProps, swapAtTimings ] = splitPropertiesFromParams(swapAtParams); 8863 const [ enterFromProps, enterFromTimings ] = splitPropertiesFromParams(enterFromParams); 8864 const [ leaveToProps, leaveToTimings ] = splitPropertiesFromParams(leaveToParams); 8865 8866 const oldState = this.oldState; 8867 const newState = this.newState; 8868 const animating = this.animating; 8869 const swapping = this.swapping; 8870 const entering = this.entering; 8871 const leaving = this.leaving; 8872 const pendingRemoval = this.pendingRemoval; 8873 8874 animating.length = swapping.length = entering.length = leaving.length = 0; 8875 8876 // Mute old state CSS transitions to prevent wrong properties calculation 8877 oldState.forEachRootNode(muteNodeTransition); 8878 // Capture the new state before animation 8879 newState.record(); 8880 newState.forEachRootNode(recordNodeInlineStyles); 8881 8882 const targets = []; 8883 const animated = []; 8884 const transformed = []; 8885 const animatedSwap = []; 8886 const rootNode = newState.rootNode; 8887 const $root = rootNode.$el; 8888 8889 newState.forEachRootNode(node => { 8890 8891 const $el = node.$el; 8892 const id = node.id; 8893 const parent = node.parentNode; 8894 const parentAdded = parent ? parent.branchAdded : false; 8895 const parentRemoved = parent ? parent.branchRemoved : false; 8896 const parentNotRendered = parent ? parent.branchNotRendered : false; 8897 8898 let oldStateNode = oldState.nodes.get(id); 8899 8900 const hasNoOldState = !oldStateNode; 8901 8902 if (hasNoOldState) { 8903 oldStateNode = cloneNodeProperties(node, /** @type {LayoutNode} */({}), oldState); 8904 oldState.nodes.set(id, oldStateNode); 8905 oldStateNode.measuredIsRemoved = true; 8906 } else if (oldStateNode.measuredIsRemoved && !node.measuredIsRemoved) { 8907 cloneNodeProperties(node, oldStateNode, oldState); 8908 oldStateNode.measuredIsRemoved = true; 8909 } 8910 8911 const oldParentNode = oldStateNode.parentNode; 8912 const oldParentId = oldParentNode ? oldParentNode.id : null; 8913 const newParentId = parent ? parent.id : null; 8914 const parentChanged = oldParentId !== newParentId; 8915 const elementChanged = oldStateNode.$el !== node.$el; 8916 const wasRemovedBefore = oldStateNode.measuredIsRemoved; 8917 const isRemovedNow = node.measuredIsRemoved; 8918 8919 // Recalculate postion relative to their parent for elements that have been moved 8920 if (!oldStateNode.measuredIsRemoved && !isRemovedNow && !hasNoOldState && (parentChanged || elementChanged)) { 8921 const oldAbsoluteLeft = oldStateNode.properties.left; 8922 const oldAbsoluteTop = oldStateNode.properties.top; 8923 const newParent = parent || newState.rootNode; 8924 const oldParent = newParent.id ? oldState.nodes.get(newParent.id) : null; 8925 const parentLeft = oldParent ? oldParent.properties.left : newParent.properties.left; 8926 const parentTop = oldParent ? oldParent.properties.top : newParent.properties.top; 8927 const borderLeft = oldParent ? oldParent.properties.clientLeft : newParent.properties.clientLeft; 8928 const borderTop = oldParent ? oldParent.properties.clientTop : newParent.properties.clientTop; 8929 oldStateNode.properties.x = oldAbsoluteLeft - parentLeft - borderLeft; 8930 oldStateNode.properties.y = oldAbsoluteTop - parentTop - borderTop; 8931 } 8932 8933 if (node.hasVisibilitySwap) { 8934 if (node.hasVisibilityHidden) { 8935 node.$el.style.visibility = 'visible'; 8936 node.$measure.style.visibility = 'hidden'; 8937 } 8938 if (node.hasDisplayNone) { 8939 node.$el.style.display = oldStateNode.measuredDisplay || node.measuredDisplay || ''; 8940 // Setting visibility 'hidden' instead of display none to avoid calculation issues 8941 node.$measure.style.visibility = 'hidden'; 8942 // @TODO: check why setting display here can cause calculation issues 8943 // node.$measure.style.display = 'none'; 8944 } 8945 } 8946 8947 const wasPendingRemoval = pendingRemoval.has($el); 8948 const wasVisibleBefore = oldStateNode.measuredIsVisible; 8949 const isVisibleNow = node.measuredIsVisible; 8950 const becomeVisible = !wasVisibleBefore && isVisibleNow && !parentNotRendered; 8951 const topLevelAdded = !isRemovedNow && (wasRemovedBefore || wasPendingRemoval) && !parentAdded; 8952 const newlyRemoved = isRemovedNow && !wasRemovedBefore && !parentRemoved; 8953 const topLevelRemoved = newlyRemoved || isRemovedNow && wasPendingRemoval && !parentRemoved; 8954 8955 node.branchAdded = parentAdded || topLevelAdded; 8956 node.branchRemoved = parentRemoved || topLevelRemoved; 8957 node.branchNotRendered = parentNotRendered || isRemovedNow; 8958 8959 if (isRemovedNow && wasVisibleBefore) { 8960 node.$el.style.display = oldStateNode.measuredDisplay; 8961 node.$el.style.visibility = 'visible'; 8962 cloneNodeProperties(oldStateNode, node, newState); 8963 } 8964 8965 // Node is leaving 8966 if (newlyRemoved) { 8967 if (node.isTarget) { 8968 leaving.push($el); 8969 node.isLeaving = true; 8970 } 8971 pendingRemoval.add($el); 8972 } else if (!isRemovedNow && wasPendingRemoval) { 8973 pendingRemoval.delete($el); 8974 } 8975 8976 // Node is entering 8977 if ((topLevelAdded && !parentNotRendered) || becomeVisible) { 8978 updateNodeProperties(oldStateNode, enterFromProps); 8979 if (node.isTarget) { 8980 entering.push($el); 8981 node.isEntering = true; 8982 } 8983 // Node is leaving 8984 } else if (topLevelRemoved && !parentNotRendered) { 8985 updateNodeProperties(node, leaveToProps); 8986 } 8987 8988 // Node is animating 8989 // The animating array is used only to calculate delays and duration on root children 8990 if (node !== rootNode && node.isTarget && !node.isEntering && !node.isLeaving) { 8991 animating.push($el); 8992 } 8993 8994 targets.push($el); 8995 8996 }); 8997 8998 let enteringIndex = 0; 8999 let leavingIndex = 0; 9000 let animatingIndex = 0; 9001 9002 newState.forEachRootNode(node => { 9003 9004 const $el = node.$el; 9005 const parent = node.parentNode; 9006 const oldStateNode = oldState.nodes.get(node.id); 9007 const nodeProperties = node.properties; 9008 const oldStateNodeProperties = oldStateNode.properties; 9009 9010 // Use closest animated parent index and total values so that children staggered delays are in sync with their parent 9011 let animatedParent = parent !== rootNode && parent; 9012 while (animatedParent && !animatedParent.isTarget && animatedParent !== rootNode) { 9013 animatedParent = animatedParent.parentNode; 9014 } 9015 9016 const animatingTotal = animating.length; 9017 9018 // Root is always animated first in sync with the first child (animating.length is the total of children) 9019 if (node === rootNode) { 9020 node.index = 0; 9021 node.total = animatingTotal; 9022 updateNodeTimingParams(node, animationTimings); 9023 } else if (node.isEntering) { 9024 node.index = animatedParent ? animatedParent.index : enteringIndex; 9025 node.total = animatedParent ? animatingTotal : entering.length; 9026 updateNodeTimingParams(node, enterFromTimings); 9027 enteringIndex++; 9028 } else if (node.isLeaving) { 9029 node.index = animatedParent ? animatedParent.index : leavingIndex; 9030 node.total = animatedParent ? animatingTotal : leaving.length; 9031 leavingIndex++; 9032 updateNodeTimingParams(node, leaveToTimings); 9033 } else if (node.isTarget) { 9034 node.index = animatingIndex++; 9035 node.total = animatingTotal; 9036 updateNodeTimingParams(node, animationTimings); 9037 } else { 9038 node.index = animatedParent ? animatedParent.index : 0; 9039 node.total = animatingTotal; 9040 updateNodeTimingParams(node, swapAtTimings); 9041 } 9042 9043 // Make sure the old state node has its inex and total values up to date for valid "from" function values calculation 9044 oldStateNode.index = node.index; 9045 oldStateNode.total = node.total; 9046 9047 // Computes all values up front so we can check for changes and we don't have to re-compute them inside the animation props 9048 for (let prop in nodeProperties) { 9049 nodeProperties[prop] = getFunctionValue(nodeProperties[prop], $el, node.index, node.total); 9050 oldStateNodeProperties[prop] = getFunctionValue(oldStateNodeProperties[prop], $el, oldStateNode.index, oldStateNode.total); 9051 } 9052 9053 // Use a 1px tolerance to detect dimensions changes to prevent width / height animations on barelly visible elements 9054 const sizeTolerance = 1; 9055 const widthChanged = Math.abs(nodeProperties.width - oldStateNodeProperties.width) > sizeTolerance; 9056 const heightChanged = Math.abs(nodeProperties.height - oldStateNodeProperties.height) > sizeTolerance; 9057 9058 node.sizeChanged = (widthChanged || heightChanged); 9059 9060 // const hiddenStateChanged = (topLevelAdded || newlyRemoved) && wasRemovedBefore !== isRemovedNow; 9061 9062 if (node.isTarget && (!node.measuredIsRemoved && oldStateNode.measuredIsVisible || node.measuredIsRemoved && node.measuredIsVisible)) { 9063 if (!node.isInlined && (nodeProperties.transform !== 'none' || oldStateNodeProperties.transform !== 'none')) { 9064 node.hasTransform = true; 9065 transformed.push($el); 9066 } 9067 for (let prop in nodeProperties) { 9068 // if (prop !== 'transform' && (nodeProperties[prop] !== oldStateNodeProperties[prop] || hiddenStateChanged)) { 9069 if (prop !== 'transform' && (nodeProperties[prop] !== oldStateNodeProperties[prop])) { 9070 animated.push($el); 9071 break; 9072 } 9073 } 9074 } 9075 9076 if (!node.isTarget) { 9077 swapping.push($el); 9078 if (node.sizeChanged && parent && parent.isTarget && parent.sizeChanged) { 9079 if (!node.isInlined && swapAtProps.transform) { 9080 node.hasTransform = true; 9081 transformed.push($el); 9082 } 9083 animatedSwap.push($el); 9084 } 9085 } 9086 9087 }); 9088 9089 const timingParams = { 9090 delay: (/** @type {HTMLElement} */$el) => newState.getNode($el).delay, 9091 duration: (/** @type {HTMLElement} */$el) => newState.getNode($el).duration, 9092 ease: (/** @type {HTMLElement} */$el) => newState.getNode($el).ease, 9093 }; 9094 9095 tlParams.defaults = timingParams; 9096 9097 this.timeline = createTimeline(tlParams); 9098 9099 // Imediatly return the timeline if no layout changes detected 9100 if (!animated.length && !transformed.length && !swapping.length) { 9101 // Make sure to restore all CSS transition if no animation 9102 restoreLayoutTransition(this.transitionMuteStore); 9103 return this.timeline.complete(); 9104 } 9105 9106 if (targets.length) { 9107 9108 this.root.classList.add('is-animated'); 9109 9110 for (let i = 0, l = targets.length; i < l; i++) { 9111 const $el = targets[i]; 9112 const id = $el.dataset.layoutId; 9113 const oldNode = oldState.nodes.get(id); 9114 const newNode = newState.nodes.get(id); 9115 const oldNodeState = oldNode.properties; 9116 9117 // muteNodeTransition(newNode); 9118 9119 // Don't animate dimensions and positions of inlined elements 9120 if (!newNode.isInlined) { 9121 // Display grid can mess with the absolute positioning, so set it to block during transition 9122 if (oldNode.measuredDisplay === 'grid' || newNode.measuredDisplay === 'grid') $el.style.setProperty('display', 'block', 'important'); 9123 // All children must be in position absolute or fixed 9124 if ($el !== $root || this.absoluteCoords) { 9125 $el.style.position = this.absoluteCoords ? 'fixed' : 'absolute'; 9126 $el.style.left = '0px'; 9127 $el.style.top = '0px'; 9128 $el.style.marginLeft = '0px'; 9129 $el.style.marginTop = '0px'; 9130 $el.style.translate = `${oldNodeState.x}px ${oldNodeState.y}px`; 9131 } 9132 if ($el === $root && newNode.measuredPosition === 'static') { 9133 $el.style.position = 'relative'; 9134 // Cancel left / trop in case the static element had muted values now activated by potision relative 9135 $el.style.left = '0px'; 9136 $el.style.top = '0px'; 9137 } 9138 $el.style.width = `${oldNodeState.width}px`; 9139 $el.style.height = `${oldNodeState.height}px`; 9140 // Overrides user defined min and max to prevents width and height clamping 9141 $el.style.minWidth = `auto`; 9142 $el.style.minHeight = `auto`; 9143 $el.style.maxWidth = `none`; 9144 $el.style.maxHeight = `none`; 9145 } 9146 } 9147 9148 // Restore the scroll position if the oldState differs from the current state 9149 if (oldState.scrollX !== window.scrollX || oldState.scrollY !== window.scrollY) { 9150 // Restoring in the next frame avoids race conditions if for example a waapi animation commit styles that affect the root height 9151 requestAnimationFrame(() => window.scrollTo(oldState.scrollX, oldState.scrollY)); 9152 } 9153 9154 for (let i = 0, l = animated.length; i < l; i++) { 9155 const $el = animated[i]; 9156 const id = $el.dataset.layoutId; 9157 const oldNode = oldState.nodes.get(id); 9158 const newNode = newState.nodes.get(id); 9159 const oldNodeState = oldNode.properties; 9160 const newNodeState = newNode.properties; 9161 let nodeHasChanged = false; 9162 /** @type {AnimationParams} */ 9163 const animatedProps = { 9164 composition: 'none', 9165 }; 9166 if (!newNode.isInlined) { 9167 if (oldNodeState.width !== newNodeState.width) { 9168 animatedProps.width = [oldNodeState.width, newNodeState.width]; 9169 nodeHasChanged = true; 9170 } 9171 if (oldNodeState.height !== newNodeState.height) { 9172 animatedProps.height = [oldNodeState.height, newNodeState.height]; 9173 nodeHasChanged = true; 9174 } 9175 // If the node has transforms we handle the translate animation in wappi otherwise translate and other transforms can be out of sync 9176 // Always animate translate 9177 if (!newNode.hasTransform) { 9178 animatedProps.translate = [`${oldNodeState.x}px ${oldNodeState.y}px`, `${newNodeState.x}px ${newNodeState.y}px`]; 9179 nodeHasChanged = true; 9180 } 9181 } 9182 this.properties.forEach(prop => { 9183 const oldVal = oldNodeState[prop]; 9184 const newVal = newNodeState[prop]; 9185 if (prop !== 'transform' && oldVal !== newVal) { 9186 animatedProps[prop] = [oldVal, newVal]; 9187 nodeHasChanged = true; 9188 } 9189 }); 9190 if (nodeHasChanged) { 9191 this.timeline.add($el, animatedProps, 0); 9192 } 9193 } 9194 9195 } 9196 9197 if (swapping.length) { 9198 9199 for (let i = 0, l = swapping.length; i < l; i++) { 9200 const $el = swapping[i]; 9201 const oldNode = oldState.getNode($el); 9202 if (!oldNode.isInlined) { 9203 const oldNodeProps = oldNode.properties; 9204 $el.style.width = `${oldNodeProps.width}px`; 9205 $el.style.height = `${oldNodeProps.height}px`; 9206 // Overrides user defined min and max to prevents width and height clamping 9207 $el.style.minWidth = `auto`; 9208 $el.style.minHeight = `auto`; 9209 $el.style.maxWidth = `none`; 9210 $el.style.maxHeight = `none`; 9211 $el.style.translate = `${oldNodeProps.x}px ${oldNodeProps.y}px`; 9212 } 9213 this.properties.forEach(prop => { 9214 if (prop !== 'transform') { 9215 $el.style[prop] = `${oldState.getComputedValue($el, prop)}`; 9216 } 9217 }); 9218 } 9219 9220 for (let i = 0, l = swapping.length; i < l; i++) { 9221 const $el = swapping[i]; 9222 const newNode = newState.getNode($el); 9223 const newNodeProps = newNode.properties; 9224 this.timeline.call(() => { 9225 if (!newNode.isInlined) { 9226 $el.style.width = `${newNodeProps.width}px`; 9227 $el.style.height = `${newNodeProps.height}px`; 9228 // Overrides user defined min and max to prevents width and height clamping 9229 $el.style.minWidth = `auto`; 9230 $el.style.minHeight = `auto`; 9231 $el.style.maxWidth = `none`; 9232 $el.style.maxHeight = `none`; 9233 $el.style.translate = `${newNodeProps.x}px ${newNodeProps.y}px`; 9234 } 9235 this.properties.forEach(prop => { 9236 if (prop !== 'transform') { 9237 $el.style[prop] = `${newState.getComputedValue($el, prop)}`; 9238 } 9239 }); 9240 }, newNode.delay + newNode.duration / 2); 9241 } 9242 9243 if (animatedSwap.length) { 9244 const ease = parseEase(newState.nodes.get(animatedSwap[0].dataset.layoutId).ease); 9245 const inverseEased = t => 1 - ease(1 - t); 9246 const animatedSwapParams = /** @type {AnimationParams} */({}); 9247 if (swapAtProps) { 9248 for (let prop in swapAtProps) { 9249 if (prop !== 'transform') { 9250 animatedSwapParams[prop] = [ 9251 { from: (/** @type {HTMLElement} */$el) => oldState.getComputedValue($el, prop), to: swapAtProps[prop] }, 9252 { from: swapAtProps[prop], to: (/** @type {HTMLElement} */$el) => newState.getComputedValue($el, prop), ease: inverseEased } 9253 ]; 9254 } 9255 } 9256 } 9257 this.timeline.add(animatedSwap, animatedSwapParams, 0); 9258 } 9259 9260 } 9261 9262 const transformedLength = transformed.length; 9263 9264 if (transformedLength) { 9265 // We only need to set the transform property here since translate is alread defined in the targets loop 9266 for (let i = 0; i < transformedLength; i++) { 9267 const $el = transformed[i]; 9268 $el.style.translate = `${oldState.getComputedValue($el, 'x')}px ${oldState.getComputedValue($el, 'y')}px`, 9269 $el.style.transform = oldState.getComputedValue($el, 'transform'); 9270 if (animatedSwap.includes($el)) { 9271 const node = newState.getNode($el); 9272 node.ease = getFunctionValue(swapAtParams.ease, $el, node.index, node.total); 9273 node.duration = getFunctionValue(swapAtParams.duration, $el, node.index, node.total); 9274 } 9275 } 9276 this.transformAnimation = waapi.animate(transformed, { 9277 translate: (/** @type {HTMLElement} */$el) => `${newState.getComputedValue($el, 'x')}px ${newState.getComputedValue($el, 'y')}px`, 9278 transform: (/** @type {HTMLElement} */$el) => { 9279 const newValue = newState.getComputedValue($el, 'transform'); 9280 if (!animatedSwap.includes($el)) return newValue; 9281 const oldValue = oldState.getComputedValue($el, 'transform'); 9282 const node = newState.getNode($el); 9283 return [oldValue, getFunctionValue(swapAtProps.transform, $el, node.index, node.total), newValue] 9284 }, 9285 autoplay: false, 9286 persist: true, 9287 ...timingParams, 9288 }); 9289 this.timeline.sync(this.transformAnimation, 0); 9290 } 9291 9292 return this.timeline.init(); 9293 } 9294 9295 /** 9296 * @param {(layout: this) => void} callback 9297 * @param {LayoutAnimationParams} [params] 9298 * @return {Timeline} 9299 */ 9300 update(callback, params = {}) { 9301 this.record(); 9302 callback(this); 9303 return this.animate(params); 9304 } 9305 } 9306 9307 /** 9308 * @param {DOMTargetSelector} root 9309 * @param {AutoLayoutParams} [params] 9310 * @return {AutoLayout} 9311 */ 9312 const createLayout = (root, params) => new AutoLayout(root, params); 9313 9314 // Chain-able utilities 9315 9316 const numberUtils = numberImports; // Needed to keep the import when bundling 9317 9318 const chainables = {}; 9319 9320 /** 9321 * @callback UtilityFunction 9322 * @param {...*} args 9323 * @return {Number|String} 9324 * 9325 * @param {UtilityFunction} fn 9326 * @param {Number} [last=0] 9327 * @return {function(...(Number|String)): function(Number|String): (Number|String)} 9328 */ 9329 const curry = (fn, last = 0) => (...args) => last ? v => fn(...args, v) : v => fn(v, ...args); 9330 9331 /** 9332 * @param {Function} fn 9333 * @return {function(...(Number|String))} 9334 */ 9335 const chain = fn => { 9336 return (...args) => { 9337 const result = fn(...args); 9338 return new Proxy(noop, { 9339 apply: (_, __, [v]) => result(v), 9340 get: (_, prop) => chain(/**@param {...Number|String} nextArgs */(...nextArgs) => { 9341 const nextResult = chainables[prop](...nextArgs); 9342 return (/**@type {Number|String} */v) => nextResult(result(v)); 9343 }) 9344 }); 9345 } 9346 }; 9347 9348 /** 9349 * @param {UtilityFunction} fn 9350 * @param {String} name 9351 * @param {Number} [right] 9352 * @return {function(...(Number|String)): UtilityFunction} 9353 */ 9354 const makeChainable = (name, fn, right = 0) => { 9355 const chained = (...args) => (args.length < fn.length ? chain(curry(fn, right)) : fn)(...args); 9356 if (!chainables[name]) chainables[name] = chained; 9357 return chained; 9358 }; 9359 9360 /** 9361 * @typedef {Object} ChainablesMap 9362 * @property {ChainedClamp} clamp 9363 * @property {ChainedRound} round 9364 * @property {ChainedSnap} snap 9365 * @property {ChainedWrap} wrap 9366 * @property {ChainedLerp} lerp 9367 * @property {ChainedDamp} damp 9368 * @property {ChainedMapRange} mapRange 9369 * @property {ChainedRoundPad} roundPad 9370 * @property {ChainedPadStart} padStart 9371 * @property {ChainedPadEnd} padEnd 9372 * @property {ChainedDegToRad} degToRad 9373 * @property {ChainedRadToDeg} radToDeg 9374 */ 9375 9376 /** 9377 * @callback ChainedUtilsResult 9378 * @param {Number} value - The value to process through the chained operations 9379 * @return {Number} The processed result 9380 */ 9381 9382 /** 9383 * @typedef {ChainablesMap & ChainedUtilsResult} ChainableUtil 9384 */ 9385 9386 // Chainable 9387 9388 /** 9389 * @callback ChainedRoundPad 9390 * @param {Number} decimalLength - Number of decimal places 9391 * @return {ChainableUtil} 9392 */ 9393 const roundPad = /** @type {typeof numberUtils.roundPad & ChainedRoundPad} */(makeChainable('roundPad', numberUtils.roundPad)); 9394 9395 /** 9396 * @callback ChainedPadStart 9397 * @param {Number} totalLength - Target length 9398 * @param {String} padString - String to pad with 9399 * @return {ChainableUtil} 9400 */ 9401 const padStart = /** @type {typeof numberUtils.padStart & ChainedPadStart} */(makeChainable('padStart', numberUtils.padStart)); 9402 9403 /** 9404 * @callback ChainedPadEnd 9405 * @param {Number} totalLength - Target length 9406 * @param {String} padString - String to pad with 9407 * @return {ChainableUtil} 9408 */ 9409 const padEnd = /** @type {typeof numberUtils.padEnd & ChainedPadEnd} */(makeChainable('padEnd', numberUtils.padEnd)); 9410 9411 /** 9412 * @callback ChainedWrap 9413 * @param {Number} min - Minimum boundary 9414 * @param {Number} max - Maximum boundary 9415 * @return {ChainableUtil} 9416 */ 9417 const wrap = /** @type {typeof numberUtils.wrap & ChainedWrap} */(makeChainable('wrap', numberUtils.wrap)); 9418 9419 /** 9420 * @callback ChainedMapRange 9421 * @param {Number} inLow - Input range minimum 9422 * @param {Number} inHigh - Input range maximum 9423 * @param {Number} outLow - Output range minimum 9424 * @param {Number} outHigh - Output range maximum 9425 * @return {ChainableUtil} 9426 */ 9427 const mapRange = /** @type {typeof numberUtils.mapRange & ChainedMapRange} */(makeChainable('mapRange', numberUtils.mapRange)); 9428 9429 /** 9430 * @callback ChainedDegToRad 9431 * @return {ChainableUtil} 9432 */ 9433 const degToRad = /** @type {typeof numberUtils.degToRad & ChainedDegToRad} */(makeChainable('degToRad', numberUtils.degToRad)); 9434 9435 /** 9436 * @callback ChainedRadToDeg 9437 * @return {ChainableUtil} 9438 */ 9439 const radToDeg = /** @type {typeof numberUtils.radToDeg & ChainedRadToDeg} */(makeChainable('radToDeg', numberUtils.radToDeg)); 9440 9441 /** 9442 * @callback ChainedSnap 9443 * @param {Number|Array<Number>} increment - Step size or array of snap points 9444 * @return {ChainableUtil} 9445 */ 9446 const snap = /** @type {typeof numberUtils.snap & ChainedSnap} */(makeChainable('snap', numberUtils.snap)); 9447 9448 /** 9449 * @callback ChainedClamp 9450 * @param {Number} min - Minimum boundary 9451 * @param {Number} max - Maximum boundary 9452 * @return {ChainableUtil} 9453 */ 9454 const clamp = /** @type {typeof numberUtils.clamp & ChainedClamp} */(makeChainable('clamp', numberUtils.clamp)); 9455 9456 /** 9457 * @callback ChainedRound 9458 * @param {Number} decimalLength - Number of decimal places 9459 * @return {ChainableUtil} 9460 */ 9461 const round = /** @type {typeof numberUtils.round & ChainedRound} */(makeChainable('round', numberUtils.round)); 9462 9463 /** 9464 * @callback ChainedLerp 9465 * @param {Number} start - Starting value 9466 * @param {Number} end - Ending value 9467 * @return {ChainableUtil} 9468 */ 9469 const lerp = /** @type {typeof numberUtils.lerp & ChainedLerp} */(makeChainable('lerp', numberUtils.lerp, 1)); 9470 9471 /** 9472 * @callback ChainedDamp 9473 * @param {Number} start - Starting value 9474 * @param {Number} end - Target value 9475 * @param {Number} deltaTime - Delta time in ms 9476 * @return {ChainableUtil} 9477 */ 9478 const damp = /** @type {typeof numberUtils.damp & ChainedDamp} */(makeChainable('damp', numberUtils.damp, 1)); 9479 9480 /** 9481 * Generate a random number between optional min and max (inclusive) and decimal precision 9482 * 9483 * @callback RandomNumberGenerator 9484 * @param {Number} [min=0] - The minimum value (inclusive) 9485 * @param {Number} [max=1] - The maximum value (inclusive) 9486 * @param {Number} [decimalLength=0] - Number of decimal places to round to 9487 * @return {Number} A random number between min and max 9488 */ 9489 9490 /** 9491 * Generates a random number between min and max (inclusive) with optional decimal precision 9492 * 9493 * @type {RandomNumberGenerator} 9494 */ 9495 const random = (min = 0, max = 1, decimalLength = 0) => { 9496 const m = 10 ** decimalLength; 9497 return Math.floor((Math.random() * (max - min + (1 / m)) + min) * m) / m; 9498 }; 9499 9500 let _seed = 0; 9501 9502 /** 9503 * Creates a seeded pseudorandom number generator function 9504 * 9505 * @param {Number} [seed] - The seed value for the random number generator 9506 * @param {Number} [seededMin=0] - The minimum default value (inclusive) of the returned function 9507 * @param {Number} [seededMax=1] - The maximum default value (inclusive) of the returned function 9508 * @param {Number} [seededDecimalLength=0] - Default number of decimal places to round to of the returned function 9509 * @return {RandomNumberGenerator} A function to generate a random number between optional min and max (inclusive) and decimal precision 9510 */ 9511 const createSeededRandom = (seed, seededMin = 0, seededMax = 1, seededDecimalLength = 0) => { 9512 let t = seed === undefined ? _seed++ : seed; 9513 return (min = seededMin, max = seededMax, decimalLength = seededDecimalLength) => { 9514 t += 0x6D2B79F5; 9515 t = Math.imul(t ^ t >>> 15, t | 1); 9516 t ^= t + Math.imul(t ^ t >>> 7, t | 61); 9517 const m = 10 ** decimalLength; 9518 return Math.floor(((((t ^ t >>> 14) >>> 0) / 4294967296) * (max - min + (1 / m)) + min) * m) / m; 9519 } 9520 }; 9521 9522 /** 9523 * Picks a random element from an array or a string 9524 * 9525 * @template T 9526 * @param {String|Array<T>} items - The array or string to pick from 9527 * @return {String|T} A random element from the array or character from the string 9528 */ 9529 const randomPick = items => items[random(0, items.length - 1)]; 9530 9531 /** 9532 * Shuffles an array in-place using the Fisher-Yates algorithm 9533 * Adapted from https://bost.ocks.org/mike/shuffle/ 9534 * 9535 * @param {Array} items - The array to shuffle (will be modified in-place) 9536 * @return {Array} The same array reference, now shuffled 9537 */ 9538 const shuffle = items => { 9539 let m = items.length, t, i; 9540 while (m) { i = random(0, --m); t = items[m]; items[m] = items[i]; items[i] = t; } 9541 return items; 9542 }; 9543 9544 9545 9546 9547 9548 /** 9549 * @overload 9550 * @param {Number} val 9551 * @param {StaggerParams} [params] 9552 * @return {StaggerFunction<Number>} 9553 */ 9554 /** 9555 * @overload 9556 * @param {String} val 9557 * @param {StaggerParams} [params] 9558 * @return {StaggerFunction<String>} 9559 */ 9560 /** 9561 * @overload 9562 * @param {[Number, Number]} val 9563 * @param {StaggerParams} [params] 9564 * @return {StaggerFunction<Number>} 9565 */ 9566 /** 9567 * @overload 9568 * @param {[String, String]} val 9569 * @param {StaggerParams} [params] 9570 * @return {StaggerFunction<String>} 9571 */ 9572 /** 9573 * @param {Number|String|[Number, Number]|[String, String]} val The staggered value or range 9574 * @param {StaggerParams} [params] The stagger parameters 9575 * @return {StaggerFunction<Number|String>} 9576 */ 9577 const stagger = (val, params = {}) => { 9578 let values = []; 9579 let maxValue = 0; 9580 const from = params.from; 9581 const reversed = params.reversed; 9582 const ease = params.ease; 9583 const hasEasing = !isUnd(ease); 9584 const hasSpring = hasEasing && !isUnd(/** @type {Spring} */(ease).ease); 9585 const staggerEase = hasSpring ? /** @type {Spring} */(ease).ease : hasEasing ? parseEase(ease) : null; 9586 const grid = params.grid; 9587 const axis = params.axis; 9588 const customTotal = params.total; 9589 const fromFirst = isUnd(from) || from === 0 || from === 'first'; 9590 const fromCenter = from === 'center'; 9591 const fromLast = from === 'last'; 9592 const fromRandom = from === 'random'; 9593 const isRange = isArr(val); 9594 const useProp = params.use; 9595 const val1 = isRange ? parseNumber(val[0]) : parseNumber(val); 9596 const val2 = isRange ? parseNumber(val[1]) : 0; 9597 const unitMatch = unitsExecRgx.exec((isRange ? val[1] : val) + emptyString); 9598 const start = params.start || 0 + (isRange ? val1 : 0); 9599 let fromIndex = fromFirst ? 0 : isNum(from) ? from : 0; 9600 return (target, i, t, tl) => { 9601 const [ registeredTarget ] = registerTargets(target); 9602 const total = isUnd(customTotal) ? t : customTotal; 9603 const customIndex = !isUnd(useProp) ? isFnc(useProp) ? useProp(registeredTarget, i, total) : getOriginalAnimatableValue(registeredTarget, useProp) : false;
9604 const staggerIndex = isNum(customIndex) || isStr(customIndex) && isNum(+customIndex) ? +customIndex : i; 9605 if (fromCenter) fromIndex = (total - 1) / 2; 9606 if (fromLast) fromIndex = total - 1; 9607 if (!values.length) { 9608 for (let index = 0; index < total; index++) { 9609 if (!grid) { 9610 values.push(abs(fromIndex - index)); 9611 } else { 9612 const fromX = !fromCenter ? fromIndex % grid[0] : (grid[0] - 1) / 2; 9613 const fromY = !fromCenter ? floor(fromIndex / grid[0]) : (grid[1] - 1) / 2; 9614 const toX = index % grid[0]; 9615 const toY = floor(index / grid[0]); 9616 const distanceX = fromX - toX; 9617 const distanceY = fromY - toY; 9618 let value = sqrt(distanceX * distanceX + distanceY * distanceY); 9619 if (axis === 'x') value = -distanceX; 9620 if (axis === 'y') value = -distanceY; 9621 values.push(value); 9622 } 9623 maxValue = max(...values); 9624 } 9625 if (staggerEase) values = values.map(val => staggerEase(val / maxValue) * maxValue); 9626 if (reversed) values = values.map(val => axis ? (val < 0) ? val * -1 : -val : abs(maxValue - val)); 9627 if (fromRandom) values = shuffle(values); 9628 } 9629 const spacing = isRange ? (val2 - val1) / maxValue : val1; 9630 const offset = tl ? parseTimelinePosition(tl, isUnd(params.start) ? tl.iterationDuration : start) : /** @type {Number} */(start); 9631 /** @type {String|Number} */ 9632 let output = offset + ((spacing * round$1(values[staggerIndex], 2)) || 0); 9633 if (params.modifier) output = params.modifier(output); 9634 if (unitMatch) output = `${output}${unitMatch[2]}`; 9635 return output; 9636 } 9637 }; 9638 9639 var index$2 = /*#__PURE__*/Object.freeze({ 9640 __proto__: null, 9641 $: registerTargets, 9642 clamp: clamp, 9643 cleanInlineStyles: cleanInlineStyles, 9644 createSeededRandom: createSeededRandom, 9645 damp: damp, 9646 degToRad: degToRad, 9647 get: get, 9648 keepTime: keepTime, 9649 lerp: lerp, 9650 mapRange: mapRange, 9651 padEnd: padEnd, 9652 padStart: padStart, 9653 radToDeg: radToDeg, 9654 random: random, 9655 randomPick: randomPick, 9656 remove: remove, 9657 round: round, 9658 roundPad: roundPad, 9659 set: set, 9660 shuffle: shuffle, 9661 snap: snap, 9662 stagger: stagger, 9663 sync: sync, 9664 wrap: wrap 9665 }); 9666 9667 9668 9669 /** 9670 * @param {TargetsParam} path 9671 * @return {SVGGeometryElement|void} 9672 */ 9673 const getPath = path => { 9674 const parsedTargets = parseTargets(path); 9675 const $parsedSvg = /** @type {SVGGeometryElement} */(parsedTargets[0]); 9676 if (!$parsedSvg || !isSvg($parsedSvg)) return console.warn(`${path} is not a valid SVGGeometryElement`); 9677 return $parsedSvg; 9678 }; 9679 9680 9681 9682 // Motion path animation 9683 9684 /** 9685 * @param {SVGGeometryElement} $path 9686 * @param {Number} totalLength 9687 * @param {Number} progress 9688 * @param {Number} lookup 9689 * @param {Boolean} shouldClamp 9690 * @return {DOMPoint} 9691 */ 9692 const getPathPoint = ($path, totalLength, progress, lookup, shouldClamp) => { 9693 const point = progress + lookup; 9694 const pointOnPath = shouldClamp 9695 ? Math.max(0, Math.min(point, totalLength)) // Clamp between 0 and totalLength 9696 : (point % totalLength + totalLength) % totalLength; // Wrap around 9697 return $path.getPointAtLength(pointOnPath); 9698 }; 9699 9700 /** 9701 * @param {SVGGeometryElement} $path 9702 * @param {String} pathProperty 9703 * @param {Number} [offset=0] 9704 * @return {FunctionValue} 9705 */ 9706 const getPathProgess = ($path, pathProperty, offset = 0) => { 9707 return $el => { 9708 const totalLength = +($path.getTotalLength()); 9709 const inSvg = $el[isSvgSymbol]; 9710 const ctm = $path.getCTM(); 9711 const shouldClamp = offset === 0; 9712 /** @type {TweenObjectValue} */ 9713 return { 9714 from: 0, 9715 to: totalLength, 9716 /** @type {TweenModifier} */ 9717 modifier: progress => { 9718 const offsetLength = offset * totalLength; 9719 const newProgress = progress + offsetLength; 9720 if (pathProperty === 'a') { 9721 const p0 = getPathPoint($path, totalLength, newProgress, -1, shouldClamp); 9722 const p1 = getPathPoint($path, totalLength, newProgress, 1, shouldClamp); 9723 return atan2(p1.y - p0.y, p1.x - p0.x) * 180 / PI; 9724 } else { 9725 const p = getPathPoint($path, totalLength, newProgress, 0, shouldClamp); 9726 return pathProperty === 'x' ? 9727 inSvg || !ctm ? p.x : p.x * ctm.a + p.y * ctm.c + ctm.e : 9728 inSvg || !ctm ? p.y : p.x * ctm.b + p.y * ctm.d + ctm.f 9729 } 9730 } 9731 } 9732 } 9733 }; 9734 9735 /** 9736 * @param {TargetsParam} path 9737 * @param {Number} [offset=0] 9738 */ 9739 const createMotionPath = (path, offset = 0) => { 9740 const $path = getPath(path); 9741 if (!$path) return; 9742 return { 9743 translateX: getPathProgess($path, 'x', offset), 9744 translateY: getPathProgess($path, 'y', offset), 9745 rotate: getPathProgess($path, 'a', offset), 9746 } 9747 }; 9748 9749 9750 9751 /** 9752 * @param {SVGGeometryElement} [$el] 9753 * @return {Number} 9754 */ 9755 const getScaleFactor = $el => { 9756 let scaleFactor = 1; 9757 if ($el && $el.getCTM) { 9758 const ctm = $el.getCTM(); 9759 if (ctm) { 9760 const scaleX = sqrt(ctm.a * ctm.a + ctm.b * ctm.b); 9761 const scaleY = sqrt(ctm.c * ctm.c + ctm.d * ctm.d); 9762 scaleFactor = (scaleX + scaleY) / 2; 9763 } 9764 } 9765 return scaleFactor; 9766 }; 9767 9768 /** 9769 * Creates a proxy that wraps an SVGGeometryElement and adds drawing functionality. 9770 * @param {SVGGeometryElement} $el - The SVG element to transform into a drawable 9771 * @param {number} start - Starting position (0-1) 9772 * @param {number} end - Ending position (0-1) 9773 * @return {DrawableSVGGeometry} - Returns a proxy that preserves the original element's type with additional 'draw' attribute functionality 9774 */ 9775 const createDrawableProxy = ($el, start, end) => { 9776 const pathLength = K; 9777 const computedStyles = getComputedStyle($el); 9778 const strokeLineCap = computedStyles.strokeLinecap; 9779 // @ts-ignore 9780 const $scalled = computedStyles.vectorEffect === 'non-scaling-stroke' ? $el : null;
vendor: 15,024 bytes, lines 9781-10156
9781 let currentCap = strokeLineCap; 9782 9783 const proxy = new Proxy($el, { 9784 get(target, property) { 9785 const value = target[property]; 9786 if (property === proxyTargetSymbol) return target; 9787 if (property === 'setAttribute') { 9788 return (...args) => { 9789 if (args[0] === 'draw') { 9790 const value = args[1]; 9791 const values = value.split(' '); 9792 const v1 = +values[0]; 9793 const v2 = +values[1]; 9794 // TOTO: Benchmark if performing two slices is more performant than one split 9795 // const spaceIndex = value.indexOf(' '); 9796 // const v1 = round(+value.slice(0, spaceIndex), precision); 9797 // const v2 = round(+value.slice(spaceIndex + 1), precision); 9798 const scaleFactor = getScaleFactor($scalled); 9799 const os = v1 * -pathLength * scaleFactor; 9800 const d1 = (v2 * pathLength * scaleFactor) + os; 9801 const d2 = (pathLength * scaleFactor + 9802 ((v1 === 0 && v2 === 1) || (v1 === 1 && v2 === 0) ? 0 : 10 * scaleFactor) - d1); 9803 if (strokeLineCap !== 'butt') { 9804 const newCap = v1 === v2 ? 'butt' : strokeLineCap; 9805 if (currentCap !== newCap) { 9806 target.style.strokeLinecap = `${newCap}`; 9807 currentCap = newCap; 9808 } 9809 } 9810 target.setAttribute('stroke-dashoffset', `${os}`); 9811 target.setAttribute('stroke-dasharray', `${d1} ${d2}`); 9812 } 9813 return Reflect.apply(value, target, args); 9814 }; 9815 } 9816 9817 if (isFnc(value)) { 9818 return (...args) => Reflect.apply(value, target, args); 9819 } else { 9820 return value; 9821 } 9822 } 9823 }); 9824 9825 if ($el.getAttribute('pathLength') !== `${pathLength}`) { 9826 $el.setAttribute('pathLength', `${pathLength}`); 9827 proxy.setAttribute('draw', `${start} ${end}`); 9828 } 9829 9830 return /** @type {DrawableSVGGeometry} */(proxy); 9831 }; 9832 9833 /** 9834 * Creates drawable proxies for multiple SVG elements. 9835 * @param {TargetsParam} selector - CSS selector, SVG element, or array of elements and selectors 9836 * @param {number} [start=0] - Starting position (0-1) 9837 * @param {number} [end=0] - Ending position (0-1) 9838 * @return {Array<DrawableSVGGeometry>} - Array of proxied elements with drawing functionality 9839 */ 9840 const createDrawable = (selector, start = 0, end = 0) => { 9841 const els = parseTargets(selector); 9842 return els.map($el => createDrawableProxy( 9843 /** @type {SVGGeometryElement} */($el), 9844 start, 9845 end 9846 )); 9847 }; 9848 9849 9850 9851 /** 9852 * @param {TargetsParam} path2 9853 * @param {Number} [precision] 9854 * @return {FunctionValue} 9855 */ 9856 const morphTo = (path2, precision = .33) => ($path1) => { 9857 const tagName1 = ($path1.tagName || '').toLowerCase(); 9858 if (!tagName1.match(/^(path|polygon|polyline)$/)) { 9859 throw new Error(`Can't morph a <${$path1.tagName}> SVG element. Use <path>, <polygon> or <polyline>.`); 9860 } 9861 const $path2 = /** @type {SVGGeometryElement} */(getPath(path2)); 9862 if (!$path2) { 9863 throw new Error("Can't morph to an invalid target. 'path2' must resolve to an existing <path>, <polygon> or <polyline> SVG element."); 9864 } 9865 const tagName2 = ($path2.tagName || '').toLowerCase(); 9866 if (!tagName2.match(/^(path|polygon|polyline)$/)) { 9867 throw new Error(`Can't morph a <${$path2.tagName}> SVG element. Use <path>, <polygon> or <polyline>.`); 9868 } 9869 const isPath = $path1.tagName === 'path'; 9870 const separator = isPath ? ' ' : ','; 9871 const previousPoints = $path1[morphPointsSymbol]; 9872 if (previousPoints) $path1.setAttribute(isPath ? 'd' : 'points', previousPoints); 9873 9874 let v1 = '', v2 = ''; 9875 9876 if (!precision) { 9877 v1 = $path1.getAttribute(isPath ? 'd' : 'points'); 9878 v2 = $path2.getAttribute(isPath ? 'd' : 'points'); 9879 } else { 9880 const length1 = /** @type {SVGGeometryElement} */($path1).getTotalLength(); 9881 const length2 = $path2.getTotalLength(); 9882 const maxPoints = Math.max(Math.ceil(length1 * precision), Math.ceil(length2 * precision)); 9883 for (let i = 0; i < maxPoints; i++) { 9884 const t = i / (maxPoints - 1); 9885 const pointOnPath1 = /** @type {SVGGeometryElement} */($path1).getPointAtLength(length1 * t); 9886 const pointOnPath2 = $path2.getPointAtLength(length2 * t); 9887 const prefix = isPath ? (i === 0 ? 'M' : 'L') : ''; 9888 v1 += prefix + round$1(pointOnPath1.x, 3) + separator + pointOnPath1.y + ' '; 9889 v2 += prefix + round$1(pointOnPath2.x, 3) + separator + pointOnPath2.y + ' '; 9890 } 9891 } 9892 9893 $path1[morphPointsSymbol] = v2; 9894 9895 return [v1, v2]; 9896 }; 9897 9898 var index$1 = /*#__PURE__*/Object.freeze({ 9899 __proto__: null, 9900 createDrawable: createDrawable, 9901 createMotionPath: createMotionPath, 9902 morphTo: morphTo 9903 }); 9904 9905 9906 9907 const segmenter = (typeof Intl !== 'undefined') && Intl.Segmenter; 9908 const valueRgx = /\{value\}/g; 9909 const indexRgx = /\{i\}/g; 9910 const whiteSpaceGroupRgx = /(\s+)/; 9911 const whiteSpaceRgx = /^\s+$/; 9912 const lineType = 'line'; 9913 const wordType = 'word'; 9914 const charType = 'char'; 9915 const dataLine = `data-line`; 9916 9917 /** 9918 * @typedef {Object} Segment 9919 * @property {String} segment 9920 * @property {Boolean} [isWordLike] 9921 */ 9922 9923 /** 9924 * @typedef {Object} Segmenter 9925 * @property {function(String): Iterable<Segment>} segment 9926 */ 9927 9928 /** @type {Segmenter} */ 9929 let wordSegmenter = null; 9930 /** @type {Segmenter} */ 9931 let graphemeSegmenter = null; 9932 let $splitTemplate = null; 9933 9934 /** 9935 * @param {Segment} seg 9936 * @return {Boolean} 9937 */ 9938 const isSegmentWordLike = seg => { 9939 return seg.isWordLike || 9940 seg.segment === ' ' || // Consider spaces as words first, then handle them diffrently later 9941 isNum(+seg.segment); // Safari doesn't considers numbers as words 9942 }; 9943 9944 /** 9945 * @param {HTMLElement} $el 9946 */ 9947 const setAriaHidden = $el => $el.setAttribute('aria-hidden', 'true'); 9948 9949 /** 9950 * @param {DOMTarget} $el 9951 * @param {String} type 9952 * @return {Array<HTMLElement>} 9953 */ 9954 const getAllTopLevelElements = ($el, type) => [.../** @type {*} */($el.querySelectorAll(`[data-${type}]:not([data-${type}] [data-${type}])`))]; 9955 9956 const debugColors = { line: '#00D672', word: '#FF4B4B', char: '#5A87FF' }; 9957 9958 /** 9959 * @param {HTMLElement} $el 9960 */ 9961 const filterEmptyElements = $el => { 9962 if (!$el.childElementCount && !$el.textContent.trim()) { 9963 const $parent = $el.parentElement; 9964 $el.remove(); 9965 if ($parent) filterEmptyElements($parent); 9966 } 9967 }; 9968 9969 /** 9970 * @param {HTMLElement} $el 9971 * @param {Number} lineIndex 9972 * @param {Set<HTMLElement|Node>} bin 9973 * @returns {Set<HTMLElement|Node>} 9974 */ 9975 const filterLineElements = ($el, lineIndex, bin) => { 9976 const dataLineAttr = $el.getAttribute(dataLine); 9977 if (dataLineAttr !== null && +dataLineAttr !== lineIndex || $el.tagName === 'BR') { 9978 bin.add($el); 9979 // Also remove adjacent whitespace-only text nodes 9980 const prev = $el.previousSibling; 9981 const next = $el.nextSibling; 9982 if (prev && prev.nodeType === 3 && whiteSpaceRgx.test(prev.textContent)) { 9983 bin.add(prev); 9984 } 9985 if (next && next.nodeType === 3 && whiteSpaceRgx.test(next.textContent)) { 9986 bin.add(next); 9987 } 9988 } 9989 let i = $el.childElementCount; 9990 while (i--) filterLineElements(/** @type {HTMLElement} */($el.children[i]), lineIndex, bin); 9991 return bin; 9992 }; 9993 9994 /** 9995 * @param {'line'|'word'|'char'} type 9996 * @param {SplitTemplateParams} params 9997 * @return {String} 9998 */ 9999 const generateTemplate = (type, params = {}) => { 10000 let template = ``; 10001 const classString = isStr(params.class) ? ` class="${params.class}"` : ''; 10002 const cloneType = setValue(params.clone, false); 10003 const wrapType = setValue(params.wrap, false); 10004 const overflow = wrapType ? wrapType === true ? 'clip' : wrapType : cloneType ? 'clip' : false; 10005 if (wrapType) template += `<span${overflow ? ` style="overflow:${overflow};"` : ''}>`; 10006 template += `<span${classString}${cloneType ? ` style="position:relative;"` : ''} data-${type}="{i}">`; 10007 if (cloneType) { 10008 const left = cloneType === 'left' ? '-100%' : cloneType === 'right' ? '100%' : '0'; 10009 const top = cloneType === 'top' ? '-100%' : cloneType === 'bottom' ? '100%' : '0'; 10010 template += `<span>{value}</span>`; 10011 template += `<span inert style="position:absolute;top:${top};left:${left};white-space:nowrap;">{value}</span>`; 10012 } else { 10013 template += `{value}`; 10014 } 10015 template += `</span>`; 10016 if (wrapType) template += `</span>`; 10017 return template; 10018 }; 10019 10020 /** 10021 * @param {String|SplitFunctionValue} htmlTemplate 10022 * @param {Array<HTMLElement>} store 10023 * @param {Node|HTMLElement} node 10024 * @param {DocumentFragment} $parentFragment 10025 * @param {'line'|'word'|'char'} type 10026 * @param {Boolean} debug 10027 * @param {Number} lineIndex 10028 * @param {Number} [wordIndex] 10029 * @param {Number} [charIndex] 10030 * @return {HTMLElement} 10031 */ 10032 const processHTMLTemplate = (htmlTemplate, store, node, $parentFragment, type, debug, lineIndex, wordIndex, charIndex) => { 10033 const isLine = type === lineType; 10034 const isChar = type === charType; 10035 const className = `_${type}_`; 10036 const template = isFnc(htmlTemplate) ? htmlTemplate(node) : htmlTemplate; 10037 const displayStyle = isLine ? 'block' : 'inline-block'; 10038 $splitTemplate.innerHTML = template 10039 .replace(valueRgx, `<i class="${className}"></i>`) 10040 .replace(indexRgx, `${isChar ? charIndex : isLine ? lineIndex : wordIndex}`); 10041 const $content = $splitTemplate.content; 10042 const $highestParent = /** @type {HTMLElement} */($content.firstElementChild); 10043 const $split = /** @type {HTMLElement} */($content.querySelector(`[data-${type}]`)) || $highestParent; 10044 const $replacables = /** @type {NodeListOf<HTMLElement>} */($content.querySelectorAll(`i.${className}`)); 10045 const replacablesLength = $replacables.length; 10046 if (replacablesLength) { 10047 $highestParent.style.display = displayStyle; 10048 $split.style.display = displayStyle; 10049 $split.setAttribute(dataLine, `${lineIndex}`); 10050 if (!isLine) { 10051 $split.setAttribute('data-word', `${wordIndex}`); 10052 if (isChar) $split.setAttribute('data-char', `${charIndex}`); 10053 } 10054 let i = replacablesLength; 10055 while (i--) { 10056 const $replace = $replacables[i]; 10057 const $closestParent = $replace.parentElement; 10058 $closestParent.style.display = displayStyle; 10059 if (isLine) { 10060 $closestParent.innerHTML = /** @type {HTMLElement} */(node).innerHTML; 10061 } else { 10062 $closestParent.replaceChild(node.cloneNode(true), $replace); 10063 } 10064 } 10065 store.push($split); 10066 $parentFragment.appendChild($content); 10067 } else { 10068 console.warn(`The expression "{value}" is missing from the provided template.`); 10069 } 10070 if (debug) $highestParent.style.outline = `1px dotted ${debugColors[type]}`; 10071 return $highestParent; 10072 }; 10073 10074 /** 10075 * A class that splits text into words and wraps them in span elements while preserving the original HTML structure. 10076 * @class 10077 */ 10078 class TextSplitter { 10079 /** 10080 * @param {HTMLElement|NodeList|String|Array<HTMLElement>} target 10081 * @param {TextSplitterParams} [parameters] 10082 */ 10083 constructor(target, parameters = {}) { 10084 // Only init segmenters when needed 10085 if (!wordSegmenter) wordSegmenter = segmenter ? new segmenter([], { granularity: wordType }) : { 10086 segment: (text) => { 10087 const segments = []; 10088 const words = text.split(whiteSpaceGroupRgx); 10089 for (let i = 0, l = words.length; i < l; i++) { 10090 const segment = words[i]; 10091 segments.push({ 10092 segment, 10093 isWordLike: !whiteSpaceRgx.test(segment), // Consider non-whitespace as word-like 10094 }); 10095 } 10096 return segments; 10097 } 10098 }; 10099 if (!graphemeSegmenter) graphemeSegmenter = segmenter ? new segmenter([], { granularity: 'grapheme' }) : { 10100 segment: text => [...text].map(char => ({ segment: char })) 10101 }; 10102 if (!$splitTemplate && isBrowser) $splitTemplate = doc.createElement('template'); 10103 if (scope.current) scope.current.register(this); 10104 const { words, chars, lines, accessible, includeSpaces, debug } = parameters; 10105 const $target = /** @type {HTMLElement} */((target = isArr(target) ? target[0] : target) && /** @type {Node} */(target).nodeType ? target : (getNodeList(target) || [])[0]); 10106 const lineParams = lines === true ? {} : lines; 10107 const wordParams = words === true || isUnd(words) ? {} : words; 10108 const charParams = chars === true ? {} : chars; 10109 this.debug = setValue(debug, false); 10110 this.includeSpaces = setValue(includeSpaces, false); 10111 this.accessible = setValue(accessible, true); 10112 this.linesOnly = lineParams && (!wordParams && !charParams); 10113 /** @type {String|false|SplitFunctionValue} */ 10114 this.lineTemplate = isObj(lineParams) ? generateTemplate(lineType, /** @type {SplitTemplateParams} */(lineParams)) : lineParams; 10115 /** @type {String|false|SplitFunctionValue} */ 10116 this.wordTemplate = isObj(wordParams) || this.linesOnly ? generateTemplate(wordType, /** @type {SplitTemplateParams} */(wordParams)) : wordParams; 10117 /** @type {String|false|SplitFunctionValue} */ 10118 this.charTemplate = isObj(charParams) ? generateTemplate(charType, /** @type {SplitTemplateParams} */(charParams)) : charParams; 10119 this.$target = $target; 10120 this.html = $target && $target.innerHTML; 10121 this.lines = []; 10122 this.words = []; 10123 this.chars = []; 10124 this.effects = []; 10125 this.effectsCleanups = []; 10126 this.cache = null; 10127 this.ready = false; 10128 this.width = 0; 10129 this.resizeTimeout = null; 10130 const handleSplit = () => this.html && (lineParams || wordParams || charParams) && this.split(); 10131 // Make sure this is declared before calling handleSplit() in case revert() is called inside an effect callback 10132 this.resizeObserver = new ResizeObserver(() => { 10133 // Use a setTimeout instead of a Timer for better tree shaking 10134 clearTimeout(this.resizeTimeout); 10135 this.resizeTimeout = setTimeout(() => { 10136 const currentWidth = /** @type {HTMLElement} */($target).offsetWidth; 10137 if (currentWidth === this.width) return; 10138 this.width = currentWidth; 10139 handleSplit(); 10140 }, 150); 10141 }); 10142 // Only declare the font ready promise when splitting by lines and not alreay split 10143 if (this.lineTemplate && !this.ready) { 10144 doc.fonts.ready.then(handleSplit); 10145 } else { 10146 handleSplit(); 10147 } 10148 $target ? this.resizeObserver.observe($target) : console.warn('No Text Splitter target found.'); 10149 } 10150 10151 /** 10152 * @param {(...args: any[]) => Tickable | (() => void)} effect 10153 * @return this 10154 */ 10155 addEffect(effect) { 10156 if (!isFnc(effect)) return console.warn('Effect must return a function.');
vendor: 9,452 bytes, lines 10156-10362
10156 10157 const refreshableEffect = keepTime(effect); 10158 this.effects.push(refreshableEffect); 10159 if (this.ready) this.effectsCleanups[this.effects.length - 1] = refreshableEffect(this); 10160 return this; 10161 } 10162 10163 revert() { 10164 clearTimeout(this.resizeTimeout); 10165 this.lines.length = this.words.length = this.chars.length = 0; 10166 this.resizeObserver.disconnect(); 10167 // Make sure to revert the effects after disconnecting the resizeObserver to avoid triggering it in the process 10168 this.effectsCleanups.forEach(cleanup => isFnc(cleanup) ? cleanup(this) : cleanup.revert && cleanup.revert()); 10169 this.$target.innerHTML = this.html; 10170 return this; 10171 } 10172 10173 /** 10174 * Recursively processes a node and its children 10175 * @param {Node} node 10176 */ 10177 splitNode(node) { 10178 const wordTemplate = this.wordTemplate; 10179 const charTemplate = this.charTemplate; 10180 const includeSpaces = this.includeSpaces; 10181 const debug = this.debug; 10182 const nodeType = node.nodeType; 10183 if (nodeType === 3) { 10184 const nodeText = node.nodeValue; 10185 // If the nodeText is only whitespace, leave it as is 10186 if (nodeText.trim()) { 10187 const tempWords = []; 10188 const words = this.words; 10189 const chars = this.chars; 10190 const wordSegments = wordSegmenter.segment(nodeText); 10191 const $wordsFragment = doc.createDocumentFragment(); 10192 let prevSeg = null; 10193 for (const wordSegment of wordSegments) { 10194 const segment = wordSegment.segment; 10195 const isWordLike = isSegmentWordLike(wordSegment); 10196 // Determine if this segment should be a new word, first segment always becomes a new word 10197 if (!prevSeg || (isWordLike && (prevSeg && (isSegmentWordLike(prevSeg))))) { 10198 tempWords.push(segment); 10199 } else { 10200 // Only concatenate if both current and previous are non-word-like and don't contain spaces 10201 const lastWordIndex = tempWords.length - 1; 10202 const lastWord = tempWords[lastWordIndex]; 10203 if (!whiteSpaceGroupRgx.test(lastWord) && !whiteSpaceGroupRgx.test(segment)) { 10204 tempWords[lastWordIndex] += segment; 10205 } else { 10206 tempWords.push(segment); 10207 } 10208 } 10209 prevSeg = wordSegment; 10210 } 10211 10212 for (let i = 0, l = tempWords.length; i < l; i++) { 10213 const word = tempWords[i]; 10214 if (!word.trim()) { 10215 // Preserve whitespace only if includeSpaces is false and if the current space is not the first node 10216 if (i && includeSpaces) continue; 10217 $wordsFragment.appendChild(doc.createTextNode(word)); 10218 } else { 10219 const nextWord = tempWords[i + 1]; 10220 const hasWordFollowingSpace = includeSpaces && nextWord && !nextWord.trim(); 10221 const wordToProcess = word; 10222 const charSegments = charTemplate ? graphemeSegmenter.segment(wordToProcess) : null; 10223 const $charsFragment = charTemplate ? doc.createDocumentFragment() : doc.createTextNode(hasWordFollowingSpace ? word + '\xa0' : word); 10224 if (charTemplate) { 10225 const charSegmentsArray = [...charSegments]; 10226 for (let j = 0, jl = charSegmentsArray.length; j < jl; j++) { 10227 const charSegment = charSegmentsArray[j]; 10228 const isLastChar = j === jl - 1; 10229 // If this is the last character and includeSpaces is true with a following space, append the space 10230 const charText = isLastChar && hasWordFollowingSpace ? charSegment.segment + '\xa0' : charSegment.segment; 10231 const $charNode = doc.createTextNode(charText); 10232 processHTMLTemplate(charTemplate, chars, $charNode, /** @type {DocumentFragment} */($charsFragment), charType, debug, -1, words.length, chars.length); 10233 } 10234 } 10235 if (wordTemplate) { 10236 processHTMLTemplate(wordTemplate, words, $charsFragment, $wordsFragment, wordType, debug, -1, words.length, chars.length); 10237 // Chars elements must be re-parsed in the split() method if both words and chars are parsed 10238 } else if (charTemplate) { 10239 $wordsFragment.appendChild($charsFragment); 10240 } else { 10241 $wordsFragment.appendChild(doc.createTextNode(word)); 10242 } 10243 // Skip the next iteration if we included a space 10244 if (hasWordFollowingSpace) i++; 10245 } 10246 } 10247 node.parentNode.replaceChild($wordsFragment, node); 10248 } 10249 } else if (nodeType === 1) { 10250 // Converting to an array is necessary to work around childNodes pottential mutation 10251 const childNodes = /** @type {Array<Node>} */([.../** @type {*} */(node.childNodes)]); 10252 for (let i = 0, l = childNodes.length; i < l; i++) this.splitNode(childNodes[i]); 10253 } 10254 } 10255 10256 /** 10257 * @param {Boolean} clearCache 10258 * @return {this} 10259 */ 10260 split(clearCache = false) { 10261 const $el = this.$target; 10262 const isCached = !!this.cache && !clearCache; 10263 const lineTemplate = this.lineTemplate; 10264 const wordTemplate = this.wordTemplate; 10265 const charTemplate = this.charTemplate; 10266 const fontsReady = doc.fonts.status !== 'loading'; 10267 const canSplitLines = lineTemplate && fontsReady; 10268 this.ready = !lineTemplate || fontsReady; 10269 if (canSplitLines || clearCache) { 10270 // No need to revert effects animations here since it's already taken care by the refreshable 10271 this.effectsCleanups.forEach(cleanup => isFnc(cleanup) && cleanup(this)); 10272 } 10273 if (!isCached) { 10274 if (clearCache) { 10275 $el.innerHTML = this.html; 10276 this.words.length = this.chars.length = 0; 10277 } 10278 this.splitNode($el); 10279 this.cache = $el.innerHTML; 10280 } 10281 if (canSplitLines) { 10282 if (isCached) $el.innerHTML = this.cache; 10283 this.lines.length = 0; 10284 if (wordTemplate) this.words = getAllTopLevelElements($el, wordType); 10285 } 10286 // Always reparse characters after a line reset or if both words and chars are activated 10287 if (charTemplate && (canSplitLines || wordTemplate)) { 10288 this.chars = getAllTopLevelElements($el, charType); 10289 } 10290 // Words are used when lines only and prioritized over chars 10291 const elementsArray = this.words.length ? this.words : this.chars; 10292 let y, linesCount = 0; 10293 for (let i = 0, l = elementsArray.length; i < l; i++) { 10294 const $el = elementsArray[i]; 10295 const { top, height } = $el.getBoundingClientRect(); 10296 if (!isUnd(y) && top - y > height * .5) linesCount++; 10297 $el.setAttribute(dataLine, `${linesCount}`); 10298 const nested = $el.querySelectorAll(`[${dataLine}]`); 10299 let c = nested.length; 10300 while (c--) nested[c].setAttribute(dataLine, `${linesCount}`); 10301 y = top; 10302 } 10303 if (canSplitLines) { 10304 const linesFragment = doc.createDocumentFragment(); 10305 const parents = new Set(); 10306 const clones = []; 10307 for (let lineIndex = 0; lineIndex < linesCount + 1; lineIndex++) { 10308 const $clone = /** @type {HTMLElement} */($el.cloneNode(true)); 10309 filterLineElements($clone, lineIndex, new Set()).forEach($el => { 10310 const $parent = $el.parentNode; 10311 if ($parent) { 10312 if ($el.nodeType === 1) parents.add(/** @type {HTMLElement} */($parent)); 10313 $parent.removeChild($el); 10314 } 10315 }); 10316 clones.push($clone); 10317 } 10318 parents.forEach(filterEmptyElements); 10319 for (let cloneIndex = 0, clonesLength = clones.length; cloneIndex < clonesLength; cloneIndex++) { 10320 processHTMLTemplate(lineTemplate, this.lines, clones[cloneIndex], linesFragment, lineType, this.debug, cloneIndex); 10321 } 10322 $el.innerHTML = ''; 10323 $el.appendChild(linesFragment); 10324 if (wordTemplate) this.words = getAllTopLevelElements($el, wordType); 10325 if (charTemplate) this.chars = getAllTopLevelElements($el, charType); 10326 } 10327 10328 // Remove the word wrappers and clear the words array if lines split only 10329 if (this.linesOnly) { 10330 const words = this.words; 10331 let w = words.length; 10332 while (w--) { 10333 const $word = words[w]; 10334 $word.replaceWith($word.textContent); 10335 } 10336 words.length = 0; 10337 } 10338 if (this.accessible && (canSplitLines || !isCached)) { 10339 const $accessible = doc.createElement('span'); 10340 // Make the accessible element visually-hidden (https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html) 10341 $accessible.style.cssText = `position:absolute;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);width:1px;height:1px;white-space:nowrap;`; 10342 // $accessible.setAttribute('tabindex', '-1'); 10343 $accessible.innerHTML = this.html; 10344 $el.insertBefore($accessible, $el.firstChild); 10345 this.lines.forEach(setAriaHidden); 10346 this.words.forEach(setAriaHidden); 10347 this.chars.forEach(setAriaHidden); 10348 } 10349 this.width = /** @type {HTMLElement} */($el).offsetWidth; 10350 if (canSplitLines || clearCache) { 10351 this.effects.forEach((effect, i) => this.effectsCleanups[i] = effect(this)); 10352 } 10353 return this; 10354 } 10355 10356 refresh() { 10357 this.split(true); 10358 } 10359 } 10360 10361 /** 10362 * @param {
10362HTMLElement|NodeList|String|Array<HTMLElement>} target 10363 * @param {TextSplitterParams} [parameters] 10364 * @return {TextSplitter} 10365 */ 10366 const splitText = (target, parameters) => new TextSplitter(target, parameters); 10367 10368 /** 10369 * @deprecated text.split() is deprecated, import splitText() directly, or text.splitText() 10370 * 10371 * @param {HTMLElement|NodeList|String|Array<HTMLElement>} target 10372 * @param {TextSplitterParams} [parameters] 10373 * @return {TextSplitter} 10374 */ 10375 const split = (target, parameters) => { 10376 console.warn('text.split() is deprecated, import splitText() directly, or text.splitText()'); 10377 return new TextSplitter(target, parameters); 10378 }; 10379 10380 var index = /*#__PURE__*/Object.freeze({ 10381 __proto__: null, 10382 TextSplitter: TextSplitter, 10383 split: split, 10384 splitText: splitText 10385 }); 10386 10387 exports.$ = registerTargets; 10388 exports.Animatable = Animatable; 10389 exports.AutoLayout = AutoLayout; 10390 exports.Draggable = Draggable; 10391 exports.JSAnimation = JSAnimation; 10392 exports.Scope = Scope; 10393 exports.ScrollObserver = ScrollObserver; 10394 exports.Spring = Spring; 10395 exports.TextSplitter = TextSplitter; 10396 exports.Timeline = Timeline; 10397 exports.Timer = Timer; 10398 exports.WAAPIAnimation = WAAPIAnimation; 10399 exports.animate = animate; 10400 exports.clamp = clamp; 10401 exports.cleanInlineStyles = cleanInlineStyles; 10402 exports.createAnimatable = createAnimatable; 10403 exports.createDraggable = createDraggable; 10404 exports.createDrawable = createDrawable; 10405 exports.createLayout = createLayout; 10406 exports.createMotionPath = createMotionPath; 10407 exports.createScope = createScope; 10408 exports.createSeededRandom = createSeededRandom; 10409 exports.createSpring = createSpring; 10410 exports.createTimeline = createTimeline; 10411 exports.createTimer = createTimer; 10412 exports.cubicBezier = cubicBezier; 10413 exports.damp = damp; 10414 exports.degToRad = degToRad; 10415 exports.eases = eases; 10416 exports.easings = index$3; 10417 exports.engine = engine; 10418 exports.get = get; 10419 exports.irregular = irregular; 10420 exports.keepTime = keepTime; 10421 exports.lerp = lerp; 10422 exports.linear = linear; 10423 exports.mapRange = mapRange; 10424 exports.morphTo = morphTo; 10425 exports.onScroll = onScroll; 10426 exports.padEnd = padEnd; 10427 exports.padStart = padStart; 10428 exports.radToDeg = radToDeg; 10429 exports.random = random; 10430 exports.randomPick = randomPick; 10431 exports.remove = remove; 10432 exports.round = round; 10433 exports.roundPad = roundPad; 10434 exports.scrollContainers = scrollContainers; 10435 exports.set = set; 10436 exports.shuffle = shuffle; 10437 exports.snap = snap; 10438 exports.split = split; 10439 exports.splitText = splitText; 10440 exports.spring = spring; 10441 exports.stagger = stagger; 10442 exports.steps = steps; 10443 exports.svg = index$1; 10444 exports.sync = sync; 10445 exports.text = index; 10446 exports.utils = index$2; 10447 exports.waapi = waapi; 10448 exports.wrap = wrap; 10449 10450}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.