forked from angular/angular
/
element_animation_style_handler.ts
154 lines (135 loc) · 4.49 KB
/
element_animation_style_handler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
* @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.io/license
*/
const ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
const ANIMATION_PROP = 'animation';
const ANIMATIONEND_EVENT = 'animationend';
const ONE_SECOND = 1000;
export class ElementAnimationStyleHandler {
private readonly _eventFn: (e: any) => any;
private _finished = false;
private _destroyed = false;
private _startTime = 0;
private _position = 0;
constructor(
private readonly _element: any, private readonly _name: string,
private readonly _duration: number, private readonly _delay: number,
private readonly _easing: string, private readonly _fillMode: ''|'both'|'forwards',
private readonly _onDoneFn: () => any) {
this._eventFn = (e) => this._handleCallback(e);
}
apply() {
applyKeyframeAnimation(
this._element,
`${this._duration}ms ${this._easing} ${this._delay}ms 1 normal ${this._fillMode} ${
this._name}`);
addRemoveAnimationEvent(this._element, this._eventFn, false);
this._startTime = Date.now();
}
pause() {
playPauseAnimation(this._element, this._name, 'paused');
}
resume() {
playPauseAnimation(this._element, this._name, 'running');
}
setPosition(position: number) {
const index = findIndexForAnimation(this._element, this._name);
this._position = position * this._duration;
setAnimationStyle(this._element, 'Delay', `-${this._position}ms`, index);
}
getPosition() {
return this._position;
}
private _handleCallback(event: any) {
const timestamp = event._ngTestManualTimestamp || Date.now();
const elapsedTime =
parseFloat(event.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)) * ONE_SECOND;
if (event.animationName == this._name &&
Math.max(timestamp - this._startTime, 0) >= this._delay && elapsedTime >= this._duration) {
this.finish();
}
}
finish() {
if (this._finished) return;
this._finished = true;
this._onDoneFn();
addRemoveAnimationEvent(this._element, this._eventFn, true);
}
destroy() {
if (this._destroyed) return;
this._destroyed = true;
this.finish();
removeKeyframeAnimation(this._element, this._name);
}
}
function playPauseAnimation(element: any, name: string, status: 'running'|'paused') {
const index = findIndexForAnimation(element, name);
setAnimationStyle(element, 'PlayState', status, index);
}
function applyKeyframeAnimation(element: any, value: string): number {
const anim = getAnimationStyle(element, '').trim();
let index = 0;
if (anim.length) {
index = countChars(anim, ',') + 1;
value = `${anim}, ${value}`;
}
setAnimationStyle(element, '', value);
return index;
}
function removeKeyframeAnimation(element: any, name: string) {
const anim = getAnimationStyle(element, '');
const tokens = anim.split(',');
const index = findMatchingTokenIndex(tokens, name);
if (index >= 0) {
tokens.splice(index, 1);
const newValue = tokens.join(',');
setAnimationStyle(element, '', newValue);
}
}
function findIndexForAnimation(element: any, value: string) {
const anim = getAnimationStyle(element, '');
if (anim.indexOf(',') > 0) {
const tokens = anim.split(',');
return findMatchingTokenIndex(tokens, value);
}
return findMatchingTokenIndex([anim], value);
}
function findMatchingTokenIndex(tokens: string[], searchToken: string): number {
for (let i = 0; i < tokens.length; i++) {
if (tokens[i].indexOf(searchToken) >= 0) {
return i;
}
}
return -1;
}
function addRemoveAnimationEvent(element: any, fn: (e: any) => any, doRemove: boolean) {
doRemove ? element.removeEventListener(ANIMATIONEND_EVENT, fn) :
element.addEventListener(ANIMATIONEND_EVENT, fn);
}
function setAnimationStyle(element: any, name: string, value: string, index?: number) {
const prop = ANIMATION_PROP + name;
if (index != null) {
const oldValue = element.style[prop];
if (oldValue.length) {
const tokens = oldValue.split(',');
tokens[index] = value;
value = tokens.join(',');
}
}
element.style[prop] = value;
}
export function getAnimationStyle(element: any, name: string) {
return element.style[ANIMATION_PROP + name] || '';
}
function countChars(value: string, char: string): number {
let count = 0;
for (let i = 0; i < value.length; i++) {
const c = value.charAt(i);
if (c === char) count++;
}
return count;
}