Skip to content

Commit 4ee0f25

Browse files
committed
fix: cancel in-flight animation on mouseleave in DecryptedText
1 parent bc52be2 commit 4ee0f25

8 files changed

Lines changed: 40 additions & 44 deletions

File tree

public/r/DecryptedText-JS-CSS.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/r/DecryptedText-JS-TW.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/r/DecryptedText-TS-CSS.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/r/DecryptedText-TS-TW.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/content/TextAnimations/DecryptedText/DecryptedText.jsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export default function DecryptedText({
4343
const containerRef = useRef(null);
4444
const orderRef = useRef([]);
4545
const pointerRef = useRef(0);
46+
const intervalRef = useRef(null);
4647

4748
const availableChars = useMemo(() => {
4849
return useOriginalCharsOnly
@@ -147,7 +148,6 @@ export default function DecryptedText({
147148
useEffect(() => {
148149
if (!isAnimating) return;
149150

150-
let interval;
151151
let currentIteration = 0;
152152

153153
const getNextIndex = revealedSet => {
@@ -176,7 +176,7 @@ export default function DecryptedText({
176176
}
177177
};
178178

179-
interval = setInterval(() => {
179+
intervalRef.current = setInterval(() => {
180180
setRevealedIndices(prevRevealed => {
181181
if (sequential) {
182182
// Forward
@@ -188,7 +188,7 @@ export default function DecryptedText({
188188
setDisplayText(shuffleText(text, newRevealed));
189189
return newRevealed;
190190
} else {
191-
clearInterval(interval);
191+
clearInterval(intervalRef.current);
192192
setIsAnimating(false);
193193
setIsDecrypted(true);
194194
return prevRevealed;
@@ -202,13 +202,13 @@ export default function DecryptedText({
202202
newRevealed.delete(idxToRemove);
203203
setDisplayText(shuffleText(text, newRevealed));
204204
if (newRevealed.size === 0) {
205-
clearInterval(interval);
205+
clearInterval(intervalRef.current);
206206
setIsAnimating(false);
207207
setIsDecrypted(false);
208208
}
209209
return newRevealed;
210210
} else {
211-
clearInterval(interval);
211+
clearInterval(intervalRef.current);
212212
setIsAnimating(false);
213213
setIsDecrypted(false);
214214
return prevRevealed;
@@ -220,7 +220,7 @@ export default function DecryptedText({
220220
setDisplayText(shuffleText(text, prevRevealed));
221221
currentIteration++;
222222
if (currentIteration >= maxIterations) {
223-
clearInterval(interval);
223+
clearInterval(intervalRef.current);
224224
setIsAnimating(false);
225225
setDisplayText(text);
226226
setIsDecrypted(true);
@@ -239,7 +239,7 @@ export default function DecryptedText({
239239
setDisplayText(shuffleText(text, nextSet));
240240
currentIteration++;
241241
if (nextSet.size === 0 || currentIteration >= maxIterations) {
242-
clearInterval(interval);
242+
clearInterval(intervalRef.current);
243243
setIsAnimating(false);
244244
setIsDecrypted(false);
245245
// ensure final scrambled state
@@ -253,7 +253,7 @@ export default function DecryptedText({
253253
});
254254
}, speed);
255255

256-
return () => clearInterval(interval);
256+
return () => clearInterval(intervalRef.current);
257257
}, [
258258
isAnimating,
259259
text,
@@ -293,16 +293,15 @@ export default function DecryptedText({
293293
const triggerHoverDecrypt = useCallback(() => {
294294
if (isAnimating) return;
295295

296-
// Reset animation state cleanly
297296
setRevealedIndices(new Set());
298297
setIsDecrypted(false);
299298
setDisplayText(text);
300-
301299
setDirection('forward');
302300
setIsAnimating(true);
303301
}, [isAnimating, text]);
304302

305303
const resetToPlainText = useCallback(() => {
304+
clearInterval(intervalRef.current);
306305
setIsAnimating(false);
307306
setRevealedIndices(new Set());
308307
setDisplayText(text);

src/tailwind/TextAnimations/DecryptedText/DecryptedText.jsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default function DecryptedText({
2626
const containerRef = useRef(null);
2727
const orderRef = useRef([]);
2828
const pointerRef = useRef(0);
29+
const intervalRef = useRef(null);
2930

3031
const availableChars = useMemo(() => {
3132
return useOriginalCharsOnly
@@ -130,7 +131,6 @@ export default function DecryptedText({
130131
useEffect(() => {
131132
if (!isAnimating) return;
132133

133-
let interval;
134134
let currentIteration = 0;
135135

136136
const getNextIndex = revealedSet => {
@@ -158,7 +158,7 @@ export default function DecryptedText({
158158
}
159159
};
160160

161-
interval = setInterval(() => {
161+
intervalRef.current = setInterval(() => {
162162
setRevealedIndices(prevRevealed => {
163163
if (sequential) {
164164
// Forward
@@ -170,7 +170,7 @@ export default function DecryptedText({
170170
setDisplayText(shuffleText(text, newRevealed));
171171
return newRevealed;
172172
} else {
173-
clearInterval(interval);
173+
clearInterval(intervalRef.current);
174174
setIsAnimating(false);
175175
setIsDecrypted(true);
176176
return prevRevealed;
@@ -185,13 +185,13 @@ export default function DecryptedText({
185185
newRevealed.delete(idxToRemove);
186186
setDisplayText(shuffleText(text, newRevealed));
187187
if (newRevealed.size === 0) {
188-
clearInterval(interval);
188+
clearInterval(intervalRef.current);
189189
setIsAnimating(false);
190190
setIsDecrypted(false);
191191
}
192192
return newRevealed;
193193
} else {
194-
clearInterval(interval);
194+
clearInterval(intervalRef.current);
195195
setIsAnimating(false);
196196
setIsDecrypted(false);
197197
return prevRevealed;
@@ -203,7 +203,7 @@ export default function DecryptedText({
203203
setDisplayText(shuffleText(text, prevRevealed));
204204
currentIteration++;
205205
if (currentIteration >= maxIterations) {
206-
clearInterval(interval);
206+
clearInterval(intervalRef.current);
207207
setIsAnimating(false);
208208
setDisplayText(text);
209209
setIsDecrypted(true);
@@ -222,7 +222,7 @@ export default function DecryptedText({
222222
setDisplayText(shuffleText(text, nextSet));
223223
currentIteration++;
224224
if (nextSet.size === 0 || currentIteration >= maxIterations) {
225-
clearInterval(interval);
225+
clearInterval(intervalRef.current);
226226
setIsAnimating(false);
227227
setIsDecrypted(false);
228228
// ensure final scrambled state
@@ -236,7 +236,7 @@ export default function DecryptedText({
236236
});
237237
}, speed);
238238

239-
return () => clearInterval(interval);
239+
return () => clearInterval(intervalRef.current);
240240
}, [
241241
isAnimating,
242242
text,
@@ -276,16 +276,15 @@ export default function DecryptedText({
276276
const triggerHoverDecrypt = useCallback(() => {
277277
if (isAnimating) return;
278278

279-
// Reset animation state cleanly
280279
setRevealedIndices(new Set());
281280
setIsDecrypted(false);
282281
setDisplayText(text);
283-
284282
setDirection('forward');
285283
setIsAnimating(true);
286284
}, [isAnimating, text]);
287285

288286
const resetToPlainText = useCallback(() => {
287+
clearInterval(intervalRef.current);
289288
setIsAnimating(false);
290289
setRevealedIndices(new Set());
291290
setDisplayText(text);

src/ts-default/TextAnimations/DecryptedText/DecryptedText.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export default function DecryptedText({
6161
const containerRef = useRef<HTMLSpanElement>(null);
6262
const orderRef = useRef<number[]>([]);
6363
const pointerRef = useRef<number>(0);
64+
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
6465

6566
const availableChars = useMemo<string[]>(() => {
6667
return useOriginalCharsOnly
@@ -165,7 +166,6 @@ export default function DecryptedText({
165166
useEffect(() => {
166167
if (!isAnimating) return;
167168

168-
let interval: ReturnType<typeof setInterval>;
169169
let currentIteration = 0;
170170

171171
const getNextIndex = (revealedSet: Set<number>): number => {
@@ -194,7 +194,7 @@ export default function DecryptedText({
194194
}
195195
};
196196

197-
interval = setInterval(() => {
197+
intervalRef.current = setInterval(() => {
198198
setRevealedIndices(prevRevealed => {
199199
if (sequential) {
200200
// Forward
@@ -206,7 +206,7 @@ export default function DecryptedText({
206206
setDisplayText(shuffleText(text, newRevealed));
207207
return newRevealed;
208208
} else {
209-
clearInterval(interval);
209+
clearInterval(intervalRef.current ?? undefined);
210210
setIsAnimating(false);
211211
setIsDecrypted(true);
212212
return prevRevealed;
@@ -220,13 +220,13 @@ export default function DecryptedText({
220220
newRevealed.delete(idxToRemove);
221221
setDisplayText(shuffleText(text, newRevealed));
222222
if (newRevealed.size === 0) {
223-
clearInterval(interval);
223+
clearInterval(intervalRef.current ?? undefined);
224224
setIsAnimating(false);
225225
setIsDecrypted(false);
226226
}
227227
return newRevealed;
228228
} else {
229-
clearInterval(interval);
229+
clearInterval(intervalRef.current ?? undefined);
230230
setIsAnimating(false);
231231
setIsDecrypted(false);
232232
return prevRevealed;
@@ -238,7 +238,7 @@ export default function DecryptedText({
238238
setDisplayText(shuffleText(text, prevRevealed));
239239
currentIteration++;
240240
if (currentIteration >= maxIterations) {
241-
clearInterval(interval);
241+
clearInterval(intervalRef.current ?? undefined);
242242
setIsAnimating(false);
243243
setDisplayText(text);
244244
setIsDecrypted(true);
@@ -257,7 +257,7 @@ export default function DecryptedText({
257257
setDisplayText(shuffleText(text, nextSet));
258258
currentIteration++;
259259
if (nextSet.size === 0 || currentIteration >= maxIterations) {
260-
clearInterval(interval);
260+
clearInterval(intervalRef.current ?? undefined);
261261
setIsAnimating(false);
262262
setIsDecrypted(false);
263263
// ensure final scrambled state
@@ -271,7 +271,7 @@ export default function DecryptedText({
271271
});
272272
}, speed);
273273

274-
return () => clearInterval(interval);
274+
return () => clearInterval(intervalRef.current ?? undefined);
275275
}, [
276276
isAnimating,
277277
text,
@@ -311,16 +311,15 @@ export default function DecryptedText({
311311
const triggerHoverDecrypt = useCallback(() => {
312312
if (isAnimating) return;
313313

314-
// Reset animation state cleanly
315314
setRevealedIndices(new Set());
316315
setIsDecrypted(false);
317316
setDisplayText(text);
318-
319317
setDirection('forward');
320318
setIsAnimating(true);
321319
}, [isAnimating, text]);
322320

323321
const resetToPlainText = useCallback(() => {
322+
clearInterval(intervalRef.current ?? undefined);
324323
setIsAnimating(false);
325324
setRevealedIndices(new Set());
326325
setDisplayText(text);

src/ts-tailwind/TextAnimations/DecryptedText/DecryptedText.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default function DecryptedText({
4444
const containerRef = useRef<HTMLSpanElement>(null);
4545
const orderRef = useRef<number[]>([]);
4646
const pointerRef = useRef<number>(0);
47+
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
4748

4849
const availableChars = useMemo<string[]>(() => {
4950
return useOriginalCharsOnly
@@ -148,7 +149,6 @@ export default function DecryptedText({
148149
useEffect(() => {
149150
if (!isAnimating) return;
150151

151-
let interval: ReturnType<typeof setInterval>;
152152
let currentIteration = 0;
153153

154154
const getNextIndex = (revealedSet: Set<number>): number => {
@@ -176,7 +176,7 @@ export default function DecryptedText({
176176
}
177177
};
178178

179-
interval = setInterval(() => {
179+
intervalRef.current = setInterval(() => {
180180
setRevealedIndices(prevRevealed => {
181181
if (sequential) {
182182
// Forward
@@ -188,7 +188,7 @@ export default function DecryptedText({
188188
setDisplayText(shuffleText(text, newRevealed));
189189
return newRevealed;
190190
} else {
191-
clearInterval(interval);
191+
clearInterval(intervalRef.current ?? undefined);
192192
setIsAnimating(false);
193193
setIsDecrypted(true);
194194
return prevRevealed;
@@ -202,13 +202,13 @@ export default function DecryptedText({
202202
newRevealed.delete(idxToRemove);
203203
setDisplayText(shuffleText(text, newRevealed));
204204
if (newRevealed.size === 0) {
205-
clearInterval(interval);
205+
clearInterval(intervalRef.current ?? undefined);
206206
setIsAnimating(false);
207207
setIsDecrypted(false);
208208
}
209209
return newRevealed;
210210
} else {
211-
clearInterval(interval);
211+
clearInterval(intervalRef.current ?? undefined);
212212
setIsAnimating(false);
213213
setIsDecrypted(false);
214214
return prevRevealed;
@@ -220,7 +220,7 @@ export default function DecryptedText({
220220
setDisplayText(shuffleText(text, prevRevealed));
221221
currentIteration++;
222222
if (currentIteration >= maxIterations) {
223-
clearInterval(interval);
223+
clearInterval(intervalRef.current ?? undefined);
224224
setIsAnimating(false);
225225
setDisplayText(text);
226226
setIsDecrypted(true);
@@ -239,7 +239,7 @@ export default function DecryptedText({
239239
setDisplayText(shuffleText(text, nextSet));
240240
currentIteration++;
241241
if (nextSet.size === 0 || currentIteration >= maxIterations) {
242-
clearInterval(interval);
242+
clearInterval(intervalRef.current ?? undefined);
243243
setIsAnimating(false);
244244
setIsDecrypted(false);
245245
// ensure final scrambled state
@@ -252,7 +252,7 @@ export default function DecryptedText({
252252
return prevRevealed;
253253
});
254254
}, speed);
255-
return () => clearInterval(interval);
255+
return () => clearInterval(intervalRef.current ?? undefined);
256256
}, [
257257
isAnimating,
258258
text,
@@ -292,16 +292,15 @@ export default function DecryptedText({
292292
const triggerHoverDecrypt = useCallback(() => {
293293
if (isAnimating) return;
294294

295-
// Reset animation state cleanly
296295
setRevealedIndices(new Set());
297296
setIsDecrypted(false);
298297
setDisplayText(text);
299-
300298
setDirection('forward');
301299
setIsAnimating(true);
302300
}, [isAnimating, text]);
303301

304302
const resetToPlainText = useCallback(() => {
303+
clearInterval(intervalRef.current ?? undefined);
305304
setIsAnimating(false);
306305
setRevealedIndices(new Set());
307306
setDisplayText(text);

0 commit comments

Comments
 (0)