Skip to content

Commit 8d9c19f

Browse files
authored
fix: remove dependency on gsap library (#5330)
gsap's highly limited use in this codebase can easily be achieved using the native JS Web Animations API.
1 parent 7c4e368 commit 8d9c19f

File tree

3 files changed

+69
-57
lines changed

3 files changed

+69
-57
lines changed

ui/desktop/package-lock.json

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/desktop/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@
6868
"electron-updater": "^6.6.2",
6969
"electron-window-state": "^5.0.3",
7070
"express": "^5.1.0",
71-
"gsap": "^3.13.0",
7271
"lodash": "^4.17.21",
7372
"lucide-react": "^0.546.0",
7473
"react": "^19.2.0",

ui/desktop/src/hooks/use-text-animator.tsx

Lines changed: 69 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { gsap } from 'gsap';
21
import SplitType from 'split-type';
32
import { useEffect, useRef } from 'react';
43

@@ -110,6 +109,8 @@ export class TextAnimator {
110109
textElement: HTMLElement;
111110
splitter!: TextSplitter;
112111
originalChars!: string[];
112+
activeAnimations: globalThis.Animation[] = [];
113+
activeTimeouts: ReturnType<typeof setTimeout>[] = [];
113114

114115
constructor(textElement: HTMLElement) {
115116
if (!textElement || !(textElement instanceof HTMLElement)) {
@@ -124,7 +125,7 @@ export class TextAnimator {
124125
this.splitter = new TextSplitter(this.textElement, {
125126
splitTypeTypes: ['words', 'chars'],
126127
});
127-
this.originalChars = this.splitter.getChars().map((char) => char.innerHTML);
128+
this.originalChars = this.splitter.getChars().map((char) => char.textContent || '');
128129
}
129130

130131
animate() {
@@ -133,64 +134,83 @@ export class TextAnimator {
133134
const chars = this.splitter.getChars();
134135

135136
chars.forEach((char, position) => {
136-
const initialHTML = char.innerHTML;
137-
let repeatCount = 0;
138-
139-
// Set initial state
140-
gsap.set(char, {
141-
opacity: 1,
142-
display: 'inline-block',
143-
position: 'relative',
144-
});
145-
146-
gsap.fromTo(
147-
char,
148-
{
149-
opacity: 1,
150-
},
151-
{
152-
duration: 0.1, // Increased duration
153-
ease: 'power2.out',
154-
onStart: () => {
155-
gsap.set(char, {
156-
fontFamily: 'Cash Sans Mono',
157-
fontWeight: 300,
158-
color: '#666', // Add color change
159-
});
137+
const initialText = char.textContent || '';
138+
139+
char.style.opacity = '1';
140+
char.style.display = 'inline-block';
141+
char.style.position = 'relative';
142+
143+
const animation = char.animate(
144+
[
145+
{
146+
opacity: 1,
147+
color: '#666',
148+
fontFamily: 'Cash Sans Mono',
149+
fontWeight: '300',
160150
},
161-
onComplete: () => {
162-
gsap.set(char, {
163-
innerHTML: initialHTML,
164-
color: '',
165-
fontFamily: '',
166-
opacity: 1,
167-
});
151+
{
152+
opacity: 0.5,
153+
color: '#999',
168154
},
169-
repeat: 2, // Reduced repeats
170-
onRepeat: () => {
171-
repeatCount++;
172-
if (repeatCount === 1) {
173-
gsap.set(char, {
174-
opacity: 0.5,
175-
color: '#999',
176-
});
177-
}
155+
{
156+
opacity: 1,
157+
color: 'inherit',
158+
fontFamily: 'inherit',
159+
fontWeight: 'inherit',
178160
},
179-
repeatRefresh: true,
180-
repeatDelay: 0.05, // Increased delay
181-
delay: position * 0.03, // Reduced delay between chars
182-
innerHTML: () => lettersAndSymbols[Math.floor(Math.random() * lettersAndSymbols.length)],
183-
opacity: 1,
161+
],
162+
{
163+
duration: 300, // Total duration for all iterations
164+
easing: 'ease-in-out',
165+
delay: position * 30, // Stagger the start of each animation
166+
iterations: 1,
184167
}
185168
);
169+
170+
this.activeAnimations.push(animation);
171+
172+
let iteration = 0;
173+
const maxIterations = 2;
174+
175+
const animateCharacterChange = () => {
176+
if (iteration < maxIterations) {
177+
char.textContent =
178+
lettersAndSymbols[Math.floor(Math.random() * lettersAndSymbols.length)];
179+
const timeoutId = setTimeout(animateCharacterChange, 100);
180+
this.activeTimeouts.push(timeoutId);
181+
iteration++;
182+
} else {
183+
char.textContent = initialText;
184+
}
185+
};
186+
187+
const timeoutId = setTimeout(animateCharacterChange, position * 30);
188+
this.activeTimeouts.push(timeoutId);
189+
190+
animation.onfinish = () => {
191+
char.textContent = initialText;
192+
char.style.color = '';
193+
char.style.fontFamily = '';
194+
char.style.opacity = '1';
195+
};
186196
});
187197
}
188198

189199
reset() {
200+
// Clear all timeouts
201+
this.activeTimeouts.forEach((timeoutId) => clearTimeout(timeoutId));
202+
this.activeTimeouts = [];
203+
204+
// Cancel all animations
205+
this.activeAnimations.forEach((animation) => animation.cancel());
206+
this.activeAnimations = [];
207+
208+
// Reset text content
190209
const chars = this.splitter.getChars();
191210
chars.forEach((char, index) => {
192-
gsap.killTweensOf(char);
193-
char.innerHTML = this.originalChars[index];
211+
if (this.originalChars[index]) {
212+
char.textContent = this.originalChars[index];
213+
}
194214
});
195215
}
196216
}

0 commit comments

Comments
 (0)