import { Pair } from "../animation"; import { Color } from "../../color"; import { KeyframeAnimationInfo, KeyframeInfo, KeyframeDeclaration } from "../animation/keyframe-animation"; import { timeConverter, numberConverter, transformConverter, animationTimingFunctionConverter } from "../styling/converters"; interface TransformInfo { scale: Pair; translate: Pair; } let animationProperties = { "animation-name": (info, declaration) => info.name = declaration.value, "animation-duration": (info, declaration) => info.duration = timeConverter(declaration.value), "animation-delay": (info, declaration) => info.delay = timeConverter(declaration.value), "animation-timing-function": (info, declaration) => info.curve = animationTimingFunctionConverter(declaration.value), "animation-iteration-count": (info, declaration) => declaration.value === "infinite" ? info.iterations = Number.MAX_VALUE : info.iterations = numberConverter(declaration.value), "animation-direction": (info, declaration) => info.isReverse = declaration.value === "reverse", "animation-fill-mode": (info, declaration) => info.isForwards = declaration.value === "forwards" }; export class CssAnimationParser { public static keyframeAnimationsFromCSSDeclarations(declarations: { property: string, value: string }[]): Array { let animations: Array = new Array(); let animationInfo: KeyframeAnimationInfo = undefined; if (declarations === null || declarations === undefined) { return undefined; } for (let declaration of declarations) { if (declaration.property === "animation") { keyframeAnimationsFromCSSProperty(declaration.value, animations); } else { let propertyHandler = animationProperties[declaration.property]; if (propertyHandler) { if (animationInfo === undefined) { animationInfo = new KeyframeAnimationInfo(); animations.push(animationInfo); } propertyHandler(animationInfo, declaration); } } } return animations.length === 0 ? undefined : animations; } public static keyframesArrayFromCSS(cssKeyframes: Object): Array { let parsedKeyframes = new Array(); for (let keyframe of (cssKeyframes).keyframes) { let declarations = parseKeyframeDeclarations(keyframe); 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 (let declaration of keyframe.declarations) { if (declaration.property === "animation-timing-function") { current.curve = animationTimingFunctionConverter(declaration.value); } } current.declarations = declarations; } } let array = new Array(); for (let parsedKeyframe in parsedKeyframes) { array.push(parsedKeyframes[parsedKeyframe]); } array.sort(function (a, b) { return a.duration - b.duration; }); return array; } } function keyframeAnimationsFromCSSProperty(value: any, animations: Array) { if (typeof value === "string") { let values = value.split(/[,]+/); for (let parsedValue of values) { let animationInfo = new KeyframeAnimationInfo(); let 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); } } } function getTransformationValues(value: any): Array<{ propertyName: string, value: number }> { let newTransform = transformConverter(value); let array = new Array<{ propertyName: string, value: number }>(); let values = undefined; for (let transform in newTransform) { switch (transform) { case "scaleX": array.push({ propertyName: "scaleX", value: parseFloat(newTransform[transform]) }); break; case "scaleY": array.push({ propertyName: "scaleY", value: parseFloat(newTransform[transform]) }); break; case "scale": case "scale3d": values = newTransform[transform].split(","); if (values.length === 2 || values.length === 3) { array.push({ propertyName: "scaleX", value: parseFloat(values[0]) }); array.push({ propertyName: "scaleY", value: parseFloat(values[1]) }); } break; case "translateX": array.push({ propertyName: "translateX", value: parseFloat(newTransform[transform]) }); break; case "translateY": array.push({ propertyName: "translateY", value: parseFloat(newTransform[transform]) }); break; case "translate": case "translate3d": values = newTransform[transform].split(","); if (values.length === 2 || values.length === 3) { array.push({ propertyName: "translateX", value: parseFloat(values[0]) }); array.push({ propertyName: "translateY", value: parseFloat(values[1]) }); } break; case "rotate": let text = newTransform[transform]; let val = parseFloat(text); if (text.slice(-3) === "rad") { val = val * (180.0 / Math.PI); } array.push({ propertyName: "rotate", value: val }); break; case "none": array.push({ propertyName: "scaleX", value: 1 }); array.push({ propertyName: "scaleY", value: 1 }); array.push({ propertyName: "translateX", value: 0 }); array.push({ propertyName: "translateY", value: 0 }); array.push({ propertyName: "rotate", value: 0 }); break; } } return array; } function parseKeyframeDeclarations(keyframe: Object): Array { let declarations = {}; let transforms = { scale: undefined, translate: undefined }; for (let declaration of (keyframe).declarations) { let propertyName = declaration.property; let value = declaration.value; if (propertyName === "opacity") { declarations[propertyName] = parseFloat(value); } else if (propertyName === "transform") { let values = getTransformationValues(value); if (values) { for (let pair of values) { if (!preprocessAnimationValues(pair.propertyName, pair.value, transforms)) { declarations[pair.propertyName] = pair.value; } } } delete declarations[propertyName]; } else if (propertyName === "backgroundColor" || propertyName === "background-color") { declarations["backgroundColor"] = new Color(value); } else { declarations[propertyName] = value; } } if (transforms.scale !== undefined) { declarations["scale"] = transforms.scale; } if (transforms.translate !== undefined) { declarations["translate"] = transforms.translate; } let array = new Array(); for (let declaration in declarations) { let keyframeDeclaration = {}; keyframeDeclaration.property = declaration; keyframeDeclaration.value = declarations[declaration]; array.push(keyframeDeclaration); } return array; } function preprocessAnimationValues(propertyName: string, value: number, transforms: TransformInfo) { if (propertyName === "scaleX") { if (transforms.scale === undefined) { transforms.scale = { x: 1, y: 1 }; } transforms.scale.x = value; return true; } if (propertyName === "scaleY") { if (transforms.scale === undefined) { transforms.scale = { x: 1, y: 1 }; } transforms.scale.y = value; return true; } if (propertyName === "translateX") { if (transforms.translate === undefined) { transforms.translate = { x: 0, y: 0 }; } transforms.translate.x = value; return true; } if (propertyName === "translateY") { if (transforms.translate === undefined) { transforms.translate = { x: 0, y: 0 }; } transforms.translate.y = value; return true; } return false; }