import { CssAnimationProperty } from '../core/properties'; import { KeyframeAnimationInfo, KeyframeDeclaration, KeyframeInfo, UnparsedKeyframe } from '../animation/keyframe-animation'; import { timeConverter, animationTimingFunctionConverter } from '../styling/converters'; import { transformConverter } from '../styling/style-properties'; const ANIMATION_PROPERTY_HANDLERS = Object.freeze({ 'animation-name': (info: any, value: any) => (info.name = value), 'animation-duration': (info: any, value: any) => (info.duration = timeConverter(value)), 'animation-delay': (info: any, value: any) => (info.delay = timeConverter(value)), 'animation-timing-function': (info: any, value: any) => (info.curve = animationTimingFunctionConverter(value)), 'animation-iteration-count': (info: any, value: any) => (info.iterations = value === 'infinite' ? Number.MAX_VALUE : parseFloat(value)), 'animation-direction': (info: any, value: any) => (info.isReverse = value === 'reverse'), 'animation-fill-mode': (info: any, value: any) => (info.isForwards = value === 'forwards'), }); export class CssAnimationParser { public static keyframeAnimationsFromCSSDeclarations(declarations: KeyframeDeclaration[]): KeyframeAnimationInfo[] { if (declarations === null || declarations === undefined) { return undefined; } const animations = new Array(); let animationInfo: KeyframeAnimationInfo = undefined; declarations.forEach(({ property, value }) => { if (property === 'animation') { keyframeAnimationsFromCSSProperty(value, animations); } else { const propertyHandler = ANIMATION_PROPERTY_HANDLERS[property]; if (propertyHandler) { if (animationInfo === undefined) { animationInfo = new KeyframeAnimationInfo(); animations.push(animationInfo); } propertyHandler(animationInfo, value); } } }); return animations.length === 0 ? undefined : animations; } public static keyframesArrayFromCSS(keyframes: UnparsedKeyframe[]): KeyframeInfo[] { const parsedKeyframes = new Array(); for (const keyframe of keyframes) { const declarations = parseKeyframeDeclarations(keyframe.declarations); for (let time of keyframe.values) { if (time === 'from') { time = 0; } else if (time === 'to') { time = 1; } else { time = parseFloat(time) / 100; if (time < 0) { time = 0; } if (time > 100) { time = 100; } } let current = parsedKeyframes[time]; if (current === undefined) { current = {}; current.duration = time; parsedKeyframes[time] = current; } for (const declaration of keyframe.declarations) { if (declaration.property === 'animation-timing-function') { current.curve = animationTimingFunctionConverter(declaration.value); } } current.declarations = declarations; } } const array = []; for (const parsedKeyframe in parsedKeyframes) { array.push(parsedKeyframes[parsedKeyframe]); } array.sort(function (a, b) { return a.duration - b.duration; }); return array; } } function keyframeAnimationsFromCSSProperty(value: any, animations: KeyframeAnimationInfo[]) { if (typeof value === 'string') { const values = value.split(/[,]+/); for (const parsedValue of values) { const animationInfo = new KeyframeAnimationInfo(); const arr = (parsedValue).trim().split(/[ ]+/); if (arr.length > 0) { animationInfo.name = arr[0]; } if (arr.length > 1) { animationInfo.duration = timeConverter(arr[1]); } if (arr.length > 2) { animationInfo.curve = animationTimingFunctionConverter(arr[2]); } if (arr.length > 3) { animationInfo.delay = timeConverter(arr[3]); } if (arr.length > 4) { animationInfo.iterations = parseInt(arr[4]); } if (arr.length > 5) { animationInfo.isReverse = arr[4] === 'reverse'; } if (arr.length > 6) { animationInfo.isForwards = arr[5] === 'forwards'; } if (arr.length > 7) { throw new Error('Invalid value for animation: ' + value); } animations.push(animationInfo); } } } export function parseKeyframeDeclarations(unparsedKeyframeDeclarations: KeyframeDeclaration[]): KeyframeDeclaration[] { const declarations = unparsedKeyframeDeclarations.reduce((declarations, { property: unparsedProperty, value: unparsedValue }) => { const property = CssAnimationProperty._getByCssName(unparsedProperty); if (typeof unparsedProperty === 'string' && property && property._valueConverter) { declarations[property.name] = property._valueConverter(unparsedValue); } else if (typeof unparsedValue === 'string' && unparsedProperty === 'transform') { const transformations = transformConverter(unparsedValue); Object.assign(declarations, transformations); } return declarations; }, {}); return Object.keys(declarations).map((property) => ({ property, value: declarations[property], })); }