/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/core/src/animation/longest_animation.ts
161 строка
6 KB
Jessica Janiuk
refactor(core): Update determineLongestAnimation to account for playback rate
13 мар 2026, 02:36
13 мар 2026, 02:36
17d8a88
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import {LongestAnimation} from './interfaces'; /** Parses a CSS time value to milliseconds. */ function parseCssTimeUnitsToMs(value: string | undefined): number { if (!value) return 0; // Some browsers will return it in seconds, whereas others will return milliseconds. const multiplier = value.toLowerCase().indexOf('ms') > -1 ? 1 : 1000; return parseFloat(value) * multiplier; } /** Parses out multiple values from a computed style into an array. */ function parseCssPropertyValue(computedStyle: CSSStyleDeclaration, name: string): string[] { const value = computedStyle.getPropertyValue(name); return value.split(',').map((part) => part.trim()); } /** Gets the transform transition duration, including the delay, of an element in milliseconds. */ function getLongestComputedTransition(computedStyle: CSSStyleDeclaration): LongestAnimation { const transitionedProperties = parseCssPropertyValue(computedStyle, 'transition-property'); const rawDurations = parseCssPropertyValue(computedStyle, 'transition-duration'); const rawDelays = parseCssPropertyValue(computedStyle, 'transition-delay'); const longest = {propertyName: '', duration: 0, animationName: undefined}; for (let i = 0; i < transitionedProperties.length; i++) { const duration = parseCssTimeUnitsToMs(rawDelays[i]) + parseCssTimeUnitsToMs(rawDurations[i]); if (duration > longest.duration) { longest.propertyName = transitionedProperties[i]; longest.duration = duration; } } return longest; } function getLongestComputedAnimation(computedStyle: CSSStyleDeclaration): LongestAnimation { const rawNames = parseCssPropertyValue(computedStyle, 'animation-name'); const rawDelays = parseCssPropertyValue(computedStyle, 'animation-delay'); const rawDurations = parseCssPropertyValue(computedStyle, 'animation-duration'); const rawIterationCounts = parseCssPropertyValue(computedStyle, 'animation-iteration-count'); const longest: LongestAnimation = {animationName: '', propertyName: undefined, duration: 0}; for (let i = 0; i < rawNames.length; i++) { const duration = parseCssTimeUnitsToMs(rawDelays[i]) + parseCssTimeUnitsToMs(rawDurations[i]); const iterationCount = rawIterationCounts[i]; if (duration > longest.duration && iterationCount !== 'infinite') { longest.animationName = rawNames[i]; longest.duration = duration; } } return longest; } function isShorterThanExistingAnimation( existing: LongestAnimation | undefined, longest: LongestAnimation, ): boolean { return existing !== undefined && existing.duration > longest.duration; } function longestExists(longest: LongestAnimation): boolean { return ( (longest.animationName != undefined || longest.propertyName != undefined) && longest.duration > 0 ); } /** * Determines the longest animation, but with `getComputedStyles` instead of `getAnimations`. This * is ultimately safer than getAnimations because it can be used when recalculations are in * progress. `getAnimations()` will be empty in that case. */ function determineLongestAnimationFromComputedStyles( el: HTMLElement, animationsMap: WeakMap<HTMLElement, LongestAnimation>, ): void { const computedStyle = getComputedStyle(el); const longestAnimation = getLongestComputedAnimation(computedStyle); const longestTransition = getLongestComputedTransition(computedStyle); const longest = longestAnimation.duration > longestTransition.duration ? longestAnimation : longestTransition; if (isShorterThanExistingAnimation(animationsMap.get(el), longest)) return; if (longestExists(longest)) { animationsMap.set(el, longest); } } /** * Multiple animations can be set on an element. This grabs an element and * determines which of those will be the longest duration. If we didn't do * this, elements would be removed whenever the first animation completes. * This ensures we get the longest running animation and only remove when * that animation completes. */ export function determineLongestAnimation( el: HTMLElement, animationsMap: WeakMap<HTMLElement, LongestAnimation>, areAnimationSupported: boolean, ): void { if (!areAnimationSupported) return; const animations = el.getAnimations(); return animations.length === 0 ? // fallback to computed styles if getAnimations is empty. This would happen if styles are // currently recalculating due to a reflow happening elsewhere. determineLongestAnimationFromComputedStyles(el, animationsMap) : determineLongestAnimationFromElementAnimations(el, animationsMap, animations); } function determineLongestAnimationFromElementAnimations( el: HTMLElement, animationsMap: WeakMap<HTMLElement, LongestAnimation>, animations: Animation[], ): void { let longest: LongestAnimation = { animationName: undefined, propertyName: undefined, duration: 0, }; for (const animation of animations) { const timing = animation.effect?.getTiming(); if (timing?.iterations === Infinity) { continue; } // duration can be a string 'auto' or a number. const animDuration = typeof timing?.duration === 'number' ? timing.duration : 0; let duration = (timing?.delay ?? 0) + animDuration; // Account for playback rate if it is set const playbackRate = animation.playbackRate; if (playbackRate !== undefined && playbackRate !== 0 && playbackRate !== 1) { duration /= Math.abs(playbackRate); } let propertyName: string | undefined; let animationName: string | undefined; if ((animation as CSSAnimation).animationName) { animationName = (animation as CSSAnimation).animationName; } else { // Check for CSSTransition specific property propertyName = (animation as CSSTransition).transitionProperty; } if (duration >= longest.duration) { longest = {animationName, propertyName, duration}; } } if (isShorterThanExistingAnimation(animationsMap.get(el), longest)) return; if (longestExists(longest)) { animationsMap.set(el, longest); } } /** Tracks the IDs of LViews with leaving animations. */ export const allLeavingAnimations = new Set<number>();