* Update the frame/animation by a given time. * * @param time Time at which the animation should be rendered.
(time: number)
| 287 | * @param time Time at which the animation should be rendered. |
| 288 | */ |
| 289 | private updateFrame(time: number) { |
| 290 | const completedRules = this.rules.filter((r) => time >= getEndTime(r)); |
| 291 | const inProgressDynamicRules = this.rules.filter((r) => { |
| 292 | const start = getStartTime(r); |
| 293 | const end = getEndTime(r); |
| 294 | // We exclude the static animation rules by `start < end` since `start == end`. |
| 295 | return start < end && start <= time && time <= end; |
| 296 | }) as DynamicAnimationRule<ParsedStyles>[]; |
| 297 | |
| 298 | // All styles/styles state at `time`. |
| 299 | const stylesState = new Map<string, ParsedStyles>(); |
| 300 | |
| 301 | // Extract the completed rules (their styles) directly ... |
| 302 | for (const rule of completedRules) { |
| 303 | let objectStyles = stylesState.get(rule.selector) || {}; |
| 304 | objectStyles = {...objectStyles, ...getEndStyles(rule)}; |
| 305 | stylesState.set(rule.selector, objectStyles); |
| 306 | } |
| 307 | |
| 308 | const deltaTime = time - this.currentTime; |
| 309 | |
| 310 | // ... and then calculate the change of the dynamic rules in progress. |
| 311 | for (const rule of inProgressDynamicRules) { |
| 312 | let timespan: number; |
| 313 | let targetStyles: ParsedStyles; // Direction styles |
| 314 | let sourceStyles: ParsedStyles; // Opposite direction styles |
| 315 | let relativeDeltaT: number; |
| 316 | |
| 317 | // Determine the change direction. Negative Dt means going back in time; positive – forward. |
| 318 | // |
| 319 | // It's important to calculate the relative time since the global current time might go out of |
| 320 | // rule boundaries which will scew the final change rate calculations. |
| 321 | // |
| 322 | // For example: |
| 323 | // If the currentTime = 0; time = 2; for a rule active between [1, 5]; |
| 324 | // the Dt = 2, but only one second has passed from the rule's timespan, |
| 325 | // i.e. we have to use a relative time which in this case is equal to timespan[0]. |
| 326 | // relativeDt = 1 (not 2); timespan = 4 (not 5); changeRate = 0.25 (not 0.4) |
| 327 | if (deltaTime > 0) { |
| 328 | const relativeTime = rule.timeframe[0]; |
| 329 | relativeDeltaT = time - relativeTime; |
| 330 | timespan = getEndTime(rule) - relativeTime; |
| 331 | targetStyles = rule.to; |
| 332 | sourceStyles = rule.from; |
| 333 | } else { |
| 334 | const relativeTime = rule.timeframe[1]; |
| 335 | relativeDeltaT = time - relativeTime; |
| 336 | timespan = relativeTime - getStartTime(rule); |
| 337 | targetStyles = rule.from; |
| 338 | sourceStyles = rule.to; |
| 339 | } |
| 340 | |
| 341 | const changeRate = Math.abs(relativeDeltaT / timespan); |
| 342 | const styles = stylesState.get(rule.selector) || {}; |
| 343 | |
| 344 | for (const [prop, target] of Object.entries(targetStyles)) { |
| 345 | const source = sourceStyles[prop]; |
| 346 | styles[prop] = calculateNextCssValue(source, target, changeRate); |
no test coverage detected