UNPKG

8.18 kBJavaScriptView Raw
1import { CssAnimationProperty } from '../core/properties';
2import { KeyframeAnimationInfo } from '../animation/keyframe-animation';
3import { timeConverter, animationTimingFunctionConverter } from '../styling/converters';
4import { transformConverter } from '../styling/style-properties';
5import { cleanupImportantFlags } from './css-utils';
6const ANIMATION_PROPERTY_HANDLERS = Object.freeze({
7 'animation-name': (info, value) => (info.name = value.replace(/['"]/g, '')),
8 'animation-duration': (info, value) => (info.duration = timeConverter(value)),
9 'animation-delay': (info, value) => (info.delay = timeConverter(value)),
10 'animation-timing-function': (info, value) => (info.curve = animationTimingFunctionConverter(value)),
11 'animation-iteration-count': (info, value) => (info.iterations = value === 'infinite' ? Number.POSITIVE_INFINITY : parseFloat(value)),
12 'animation-direction': (info, value) => (info.isReverse = value === 'reverse'),
13 'animation-fill-mode': (info, value) => (info.isForwards = value === 'forwards' || value === 'both'),
14});
15export class CssAnimationParser {
16 static keyframeAnimationsFromCSSDeclarations(declarations) {
17 if (declarations === null || declarations === undefined) {
18 return undefined;
19 }
20 const animations = new Array();
21 let animationInfo = undefined;
22 declarations.forEach(({ property, value }) => {
23 if (property === 'animation') {
24 keyframeAnimationsFromCSSProperty(value, animations);
25 }
26 else {
27 const propertyHandler = ANIMATION_PROPERTY_HANDLERS[property];
28 if (propertyHandler) {
29 if (animationInfo === undefined) {
30 animationInfo = new KeyframeAnimationInfo();
31 animations.push(animationInfo);
32 }
33 propertyHandler(animationInfo, value);
34 }
35 }
36 });
37 return animations.length === 0 ? undefined : animations;
38 }
39 static keyframesArrayFromCSS(keyframes) {
40 const parsedKeyframes = new Array();
41 for (const keyframe of keyframes) {
42 const declarations = parseKeyframeDeclarations(keyframe.declarations);
43 for (let time of keyframe.values) {
44 if (time === 'from') {
45 time = 0;
46 }
47 else if (time === 'to') {
48 time = 1;
49 }
50 else {
51 time = parseFloat(time) / 100;
52 if (time < 0) {
53 time = 0;
54 }
55 if (time > 100) {
56 time = 100;
57 }
58 }
59 let current = parsedKeyframes[time];
60 if (current === undefined) {
61 current = {};
62 current.duration = time;
63 current.declarations = [];
64 parsedKeyframes[time] = current;
65 }
66 for (const declaration of keyframe.declarations) {
67 if (declaration.property === 'animation-timing-function') {
68 current.curve = animationTimingFunctionConverter(declaration.value);
69 }
70 }
71 current.declarations = current.declarations.concat(declarations);
72 }
73 }
74 const array = [];
75 for (const parsedKeyframe in parsedKeyframes) {
76 array.push(parsedKeyframes[parsedKeyframe]);
77 }
78 array.sort(function (a, b) {
79 return a.duration - b.duration;
80 });
81 return array;
82 }
83}
84/**
85 * @see https://w3c.github.io/csswg-drafts/css-animations/#propdef-animation
86 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/animation
87 * @internal - exported for testing
88 * @param value
89 * @param animations
90 */
91export function keyframeAnimationsFromCSSProperty(value, animations) {
92 if (typeof value !== 'string') {
93 return;
94 }
95 if (value.trim().length === 0) {
96 return;
97 }
98 /**
99 * Matches whitespace except if the whitespace is contained in parenthesis - ex. cubic-bezier(1, 1, 1, 1).
100 */
101 const VALUE_SPLIT_RE = /\s(?![^(]*\))/;
102 /**
103 * Matches commas except if the comma is contained in parenthesis - ex. cubic-bezier(1, 1, 1, 1).
104 */
105 const MULTIPLE_SPLIT_RE = /,(?![^(]*\))/;
106 const isTime = (v) => !!v.match(/\dm?s$/g);
107 const isTimingFunction = (v) => !!v.match(/ease|linear|ease-in|ease-out|ease-in-out|spring|cubic-bezier/g);
108 const isIterationCount = (v) => !!v.match(/infinite|[\d.]+$/g);
109 const isDirection = (v) => !!v.match(/normal|reverse|alternate|alternate-reverse/g);
110 const isFillMode = (v) => !!v.match(/none|forwards|backwards|both/g);
111 const isPlayState = (v) => !!v.match(/running|paused/g);
112 const values = value.split(MULTIPLE_SPLIT_RE);
113 for (const parsedValue of values) {
114 const animationInfo = new KeyframeAnimationInfo();
115 const parts = parsedValue.trim().split(VALUE_SPLIT_RE);
116 const [duration, delay] = parts.filter(isTime);
117 const [timing] = parts.filter(isTimingFunction);
118 const [iterationCount] = parts.filter(isIterationCount);
119 const [direction] = parts.filter(isDirection);
120 const [fillMode] = parts.filter(isFillMode);
121 const [playState] = parts.filter(isPlayState);
122 const [name] = parts.filter((v) => {
123 // filter out "consumed" values
124 return ![duration, delay, timing, iterationCount, direction, fillMode, playState].filter(Boolean).includes(v);
125 });
126 // console.log({
127 // duration,
128 // delay,
129 // timing,
130 // iterationCount,
131 // direction,
132 // fillMode,
133 // playState,
134 // name,
135 // });
136 if (duration) {
137 ANIMATION_PROPERTY_HANDLERS['animation-duration'](animationInfo, duration);
138 }
139 if (delay) {
140 ANIMATION_PROPERTY_HANDLERS['animation-delay'](animationInfo, delay);
141 }
142 if (timing) {
143 ANIMATION_PROPERTY_HANDLERS['animation-timing-function'](animationInfo, timing);
144 }
145 if (iterationCount) {
146 ANIMATION_PROPERTY_HANDLERS['animation-iteration-count'](animationInfo, iterationCount);
147 }
148 if (direction) {
149 ANIMATION_PROPERTY_HANDLERS['animation-direction'](animationInfo, direction);
150 }
151 if (fillMode) {
152 ANIMATION_PROPERTY_HANDLERS['animation-fill-mode'](animationInfo, fillMode);
153 }
154 if (playState) {
155 // TODO: implement play state? Currently not supported...
156 }
157 if (name) {
158 ANIMATION_PROPERTY_HANDLERS['animation-name'](animationInfo, name);
159 }
160 else {
161 // based on the SPEC we should set the name to 'none' if no name is provided
162 // however we just don't set the name at all.
163 // perhaps we should set it to 'none' and handle it accordingly.
164 // animationInfo.name = 'none'
165 }
166 animations.push(animationInfo);
167 }
168}
169export function parseKeyframeDeclarations(unparsedKeyframeDeclarations) {
170 const declarations = unparsedKeyframeDeclarations.reduce((declarations, { property: unparsedProperty, value: unparsedValue }) => {
171 const property = CssAnimationProperty._getByCssName(unparsedProperty);
172 unparsedValue = cleanupImportantFlags(unparsedValue, property?.cssLocalName);
173 if (typeof unparsedProperty === 'string' && property?._valueConverter) {
174 declarations[property.name] = property._valueConverter(unparsedValue);
175 }
176 else if (unparsedProperty === 'transform') {
177 const transformations = transformConverter(unparsedValue);
178 Object.assign(declarations, transformations);
179 }
180 return declarations;
181 }, {});
182 return Object.keys(declarations).map((property) => ({
183 property,
184 value: declarations[property],
185 }));
186}
187//# sourceMappingURL=css-animation-parser.js.map
\No newline at end of file