evalstate HF Staff commited on
Commit
b77c05a
·
verified ·
1 Parent(s): e2176ee

Fix animation double-start on hosted pageshow/load

Browse files
animations/shared/diagram-helpers.js ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function setMessagePosition(message, x, y) {
2
+ message.style.left = `${x}px`;
3
+ message.style.top = `${y}px`;
4
+ }
5
+
6
+ function animateMessageBetween(message, fromX, fromY, toX, toY, duration, callback) {
7
+ const startTime = performance.now();
8
+
9
+ function step(currentTime) {
10
+ const elapsed = currentTime - startTime;
11
+ const progress = Math.min(elapsed / duration, 1);
12
+ const eased = 1 - Math.pow(1 - progress, 3);
13
+ setMessagePosition(
14
+ message,
15
+ fromX + (toX - fromX) * eased,
16
+ fromY + (toY - fromY) * eased
17
+ );
18
+
19
+ if (progress < 1) {
20
+ requestAnimationFrame(step);
21
+ } else if (callback) {
22
+ callback();
23
+ }
24
+ }
25
+
26
+ requestAnimationFrame(step);
27
+ }
28
+
29
+ function animatePathBetween(message, waypoints, totalDuration, callback) {
30
+ if (waypoints.length < 2) {
31
+ if (callback) {
32
+ callback();
33
+ }
34
+ return;
35
+ }
36
+
37
+ let totalDist = 0;
38
+ for (let i = 0; i < waypoints.length - 1; i++) {
39
+ const dx = waypoints[i + 1].x - waypoints[i].x;
40
+ const dy = waypoints[i + 1].y - waypoints[i].y;
41
+ totalDist += Math.sqrt(dx * dx + dy * dy);
42
+ }
43
+
44
+ const durations = [];
45
+ for (let i = 0; i < waypoints.length - 1; i++) {
46
+ const dx = waypoints[i + 1].x - waypoints[i].x;
47
+ const dy = waypoints[i + 1].y - waypoints[i].y;
48
+ const legDist = Math.sqrt(dx * dx + dy * dy);
49
+ durations.push((legDist / totalDist) * totalDuration);
50
+ }
51
+
52
+ let i = 0;
53
+ function nextLeg() {
54
+ if (i < waypoints.length - 1) {
55
+ animateMessageBetween(
56
+ message,
57
+ waypoints[i].x, waypoints[i].y,
58
+ waypoints[i + 1].x, waypoints[i + 1].y,
59
+ durations[i],
60
+ () => {
61
+ i++;
62
+ nextLeg();
63
+ }
64
+ );
65
+ } else if (callback) {
66
+ callback();
67
+ }
68
+ }
69
+
70
+ nextLeg();
71
+ }
72
+
73
+ function configureMessage(elements, {
74
+ type,
75
+ arrow,
76
+ method,
77
+ detail,
78
+ sessionText,
79
+ sessionError = false,
80
+ direction = 'right',
81
+ } = {}) {
82
+ const container = elements.container;
83
+ const header = elements.header || container.querySelector('.message-header');
84
+
85
+ container.className = `message ${type} visible`;
86
+ elements.type.textContent = method;
87
+ elements.arrow.textContent = arrow;
88
+ elements.detail.textContent = detail;
89
+
90
+ if (elements.session) {
91
+ if (sessionText) {
92
+ elements.session.innerHTML = sessionText;
93
+ elements.session.style.display = 'block';
94
+ elements.session.className = 'message-session' + (sessionError ? ' error' : '');
95
+ } else {
96
+ elements.session.style.display = 'none';
97
+ }
98
+ }
99
+
100
+ if (header) {
101
+ header.style.flexDirection = direction === 'left' ? 'row-reverse' : 'row';
102
+ }
103
+ }
104
+
105
+ function runStepSequence(steps, { initialDelay = 100, stepPause, startOnLoad = true } = {}) {
106
+ if (!steps || steps.length === 0) {
107
+ return;
108
+ }
109
+
110
+ const pause = stepPause ?? (typeof ANIMATION !== 'undefined' ? ANIMATION.STEP_PAUSE : 1500);
111
+ let currentStep = 0;
112
+ let animationTimer = null;
113
+ let runId = 0;
114
+
115
+ function runStep(activeRunId) {
116
+ if (activeRunId !== runId) {
117
+ return;
118
+ }
119
+
120
+ if (currentStep >= steps.length) {
121
+ currentStep = 0;
122
+ }
123
+
124
+ const step = steps[currentStep];
125
+ step.setup();
126
+
127
+ animationTimer = setTimeout(() => {
128
+ if (activeRunId !== runId) {
129
+ return;
130
+ }
131
+
132
+ step.animate(() => {
133
+ if (activeRunId !== runId) {
134
+ return;
135
+ }
136
+
137
+ if (step.after) {
138
+ step.after();
139
+ }
140
+ currentStep++;
141
+ animationTimer = setTimeout(() => runStep(activeRunId), pause);
142
+ });
143
+ }, initialDelay);
144
+ }
145
+
146
+ const start = () => {
147
+ runId++;
148
+
149
+ if (animationTimer) {
150
+ clearTimeout(animationTimer);
151
+ animationTimer = null;
152
+ }
153
+
154
+ currentStep = 0;
155
+ runStep(runId);
156
+ };
157
+
158
+ if (startOnLoad) {
159
+ if (document.readyState === 'complete') {
160
+ start();
161
+ } else {
162
+ window.addEventListener('load', start, { once: true });
163
+ }
164
+
165
+ // Restart only for bfcache restores to avoid duplicate starts.
166
+ window.addEventListener('pageshow', (event) => {
167
+ if (event.persisted) {
168
+ start();
169
+ }
170
+ });
171
+ } else {
172
+ start();
173
+ }
174
+ }
animations/shared/sequence-helpers.js ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function runSequenceAnimation({
2
+ selectors,
3
+ cyclePause = 2000,
4
+ fadeOutOffset = 4000,
5
+ initialDelay = 500,
6
+ lastDelayOverride,
7
+ onShow,
8
+ scrollContainerSelector,
9
+ } = {}) {
10
+ const groups = (selectors || []).flatMap((selector) =>
11
+ Array.from(document.querySelectorAll(selector))
12
+ );
13
+
14
+ if (groups.length === 0) {
15
+ return;
16
+ }
17
+
18
+ const delays = groups.map((el) => parseInt(el.dataset.delay, 10) || 0);
19
+ const maxDelay = Math.max(...delays, 0);
20
+ const finalDelay = Math.max(maxDelay, lastDelayOverride || 0);
21
+ const scrollContainer = scrollContainerSelector
22
+ ? document.querySelector(scrollContainerSelector)
23
+ : null;
24
+
25
+ let timers = [];
26
+ let runId = 0;
27
+
28
+ function clearTimers() {
29
+ timers.forEach((id) => clearTimeout(id));
30
+ timers = [];
31
+ }
32
+
33
+ function schedule(fn, delay) {
34
+ const id = setTimeout(fn, delay);
35
+ timers.push(id);
36
+ return id;
37
+ }
38
+
39
+ function runAnimation(activeRunId) {
40
+ if (activeRunId !== runId) {
41
+ return;
42
+ }
43
+
44
+ clearTimers();
45
+ groups.forEach((el) => el.classList.remove('show', 'fade-out'));
46
+ if (scrollContainer) {
47
+ scrollContainer.scrollTop = 0;
48
+ }
49
+
50
+ // Force reflow to ensure class removal is processed before re-adding
51
+ void document.body.offsetHeight;
52
+
53
+ groups.forEach((el) => {
54
+ const delay = parseInt(el.dataset.delay, 10) || 0;
55
+ schedule(() => {
56
+ if (activeRunId !== runId) {
57
+ return;
58
+ }
59
+
60
+ el.classList.add('show');
61
+ if (onShow) {
62
+ onShow(el);
63
+ }
64
+ if (scrollContainer) {
65
+ scrollContainer.scrollTop = scrollContainer.scrollHeight;
66
+ }
67
+ }, delay);
68
+ });
69
+
70
+ const fadeOutTime = finalDelay + fadeOutOffset;
71
+ schedule(() => {
72
+ if (activeRunId !== runId) {
73
+ return;
74
+ }
75
+ groups.forEach((el) => el.classList.add('fade-out'));
76
+ }, fadeOutTime);
77
+
78
+ schedule(() => runAnimation(activeRunId), fadeOutTime + 1000 + cyclePause);
79
+ }
80
+
81
+ function start() {
82
+ runId++;
83
+ clearTimers();
84
+ schedule(() => runAnimation(runId), initialDelay);
85
+ }
86
+
87
+ if (document.readyState === 'complete') {
88
+ start();
89
+ } else {
90
+ window.addEventListener('load', start, { once: true });
91
+ }
92
+
93
+ // Restart only on bfcache restores to avoid duplicate starts.
94
+ window.addEventListener('pageshow', (event) => {
95
+ if (event.persisted) {
96
+ start();
97
+ }
98
+ });
99
+ }