Skip to content

Commit 81c6e0d

Browse files
authored
Merge pull request #2763 from appwrite/fix-bento-animations
Fix: bento grid animations
2 parents 233a171 + d1b92c9 commit 81c6e0d

File tree

4 files changed

+110
-56
lines changed

4 files changed

+110
-56
lines changed

src/lib/animations/index.ts

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,59 @@
1-
export function write(text: string, cb: (v: string) => void, duration = 500) {
2-
if (text.length === 0) {
3-
cb('');
4-
return Promise.resolve();
1+
export type WriteAnimation = Promise<void> & { cancel: () => void };
2+
3+
export function write(
4+
text: string,
5+
cb: (v: string) => void,
6+
duration = 500,
7+
from = 0
8+
): WriteAnimation {
9+
const remaining = text.length - from;
10+
if (remaining <= 0) {
11+
cb(text);
12+
const p = Promise.resolve() as WriteAnimation;
13+
p.cancel = () => {};
14+
return p;
515
}
616
const step = duration / text.length;
7-
let i = 0;
8-
return new Promise<void>((resolve) => {
9-
const interval = setInterval(() => {
17+
let i = from;
18+
let intervalId: ReturnType<typeof setInterval>;
19+
const promise = new Promise<void>((resolve) => {
20+
intervalId = setInterval(() => {
1021
cb(text.slice(0, ++i));
1122
if (i === text.length) {
12-
clearInterval(interval);
23+
clearInterval(intervalId);
1324
resolve();
1425
}
1526
}, step);
16-
});
27+
}) as WriteAnimation;
28+
promise.cancel = () => {
29+
clearInterval(intervalId);
30+
};
31+
return promise;
1732
}
1833

19-
export function unwrite(text: string, cb: (v: string) => void, duration = 500) {
34+
export function unwrite(text: string, cb: (v: string) => void, duration = 500): WriteAnimation {
2035
if (text.length === 0) {
2136
cb('');
22-
return Promise.resolve();
37+
const p = Promise.resolve() as WriteAnimation;
38+
p.cancel = () => {};
39+
return p;
2340
}
2441
const step = duration / text.length;
2542
let i = text.length;
26-
return new Promise<void>((resolve) => {
27-
const interval = setInterval(() => {
43+
let intervalId: ReturnType<typeof setInterval>;
44+
const promise = new Promise<void>((resolve) => {
45+
intervalId = setInterval(() => {
2846
cb(text.slice(0, --i));
2947
if (i === 0) {
30-
clearInterval(interval);
48+
clearInterval(intervalId);
3149
resolve();
3250
}
3351
}, step);
34-
});
52+
}) as WriteAnimation;
53+
promise.cancel = () => {
54+
clearInterval(intervalId);
55+
};
56+
return promise;
3557
}
3658

3759
export function sleep(duration: number) {

src/routes/(marketing)/(components)/bento/(animations)/auth.svelte

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,38 @@
66
import { isMobile } from '$lib/utils/is-mobile';
77
import { cn } from '$lib/utils/cn';
88
import GridPaper from '../../grid-paper.svelte';
9-
import { unwrite, write } from '$lib/animations';
9+
import { unwrite, write, type WriteAnimation } from '$lib/animations';
1010
import { trackEvent } from '$lib/actions/analytics';
1111
1212
let container: HTMLElement;
1313
1414
let password = $state('');
1515
let button: HTMLButtonElement;
16+
let currentAnimation: WriteAnimation | null = null;
1617
1718
$effect(() => {
1819
inView(
1920
container,
2021
() => {
2122
if (!isMobile()) return;
2223
23-
write('•••••••••••••', (v) => (password = v), 1000).then(() => {
24+
currentAnimation?.cancel();
25+
currentAnimation = write(
26+
'•••••••••••••',
27+
(v) => (password = v),
28+
1000,
29+
password.length
30+
);
31+
currentAnimation.then(() => {
2432
animate(button, { scale: [1, 0.95, 1] }, { duration: 0.25 });
2533
});
2634
return () => {
27-
unwrite('•••••••••••••', (v) => (password = v));
35+
currentAnimation?.cancel();
36+
currentAnimation = unwrite(
37+
password,
38+
(v) => (password = v),
39+
(password.length / 13) * 1000
40+
);
2841
};
2942
},
3043
{ amount: 'all' }
@@ -33,11 +46,18 @@
3346
hover(container, () => {
3447
if (isMobile()) return;
3548
36-
write('•••••••••••••', (v) => (password = v), 1000).then(() => {
49+
currentAnimation?.cancel();
50+
currentAnimation = write('•••••••••••••', (v) => (password = v), 1000, password.length);
51+
currentAnimation.then(() => {
3752
animate(button, { scale: [1, 0.95, 1] }, { duration: 0.25 });
3853
});
3954
return () => {
40-
unwrite('•••••••••••••', (v) => (password = v));
55+
currentAnimation?.cancel();
56+
currentAnimation = unwrite(
57+
password,
58+
(v) => (password = v),
59+
(password.length / 13) * 1000
60+
);
4161
};
4262
});
4363
});

src/routes/(marketing)/(components)/bento/(animations)/functions.svelte

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { animate, hover, inView } from 'motion';
3+
import { fade } from 'svelte/transition';
34
45
import GridPaper from '../../grid-paper.svelte';
56
import { isMobile } from '$lib/utils/is-mobile';
@@ -18,16 +19,19 @@
1819
let container: HTMLElement;
1920
let activeCommand: HTMLElement;
2021
let complete = $state<boolean>(false);
22+
let baseWidth: number;
23+
let widthAnim: ReturnType<typeof animate> | null = null;
2124
2225
$effect(() => {
26+
baseWidth = activeCommand.offsetWidth;
27+
2328
hover(container, () => {
2429
if (isMobile()) return;
2530
26-
animate(
31+
widthAnim?.stop();
32+
widthAnim = animate(
2733
activeCommand,
28-
{
29-
width: [activeCommand.offsetWidth, activeCommand.offsetWidth + 24]
30-
},
34+
{ width: baseWidth + 24 },
3135
{
3236
onComplete: () => {
3337
complete = true;
@@ -36,17 +40,9 @@
3640
);
3741
3842
return () => {
39-
animate(
40-
activeCommand,
41-
{
42-
width: [activeCommand.offsetWidth, activeCommand.offsetWidth - 24]
43-
},
44-
{
45-
onComplete: () => {
46-
complete = false;
47-
}
48-
}
49-
);
43+
widthAnim?.stop();
44+
complete = false;
45+
widthAnim = animate(activeCommand, { width: baseWidth });
5046
};
5147
});
5248
@@ -55,11 +51,10 @@
5551
() => {
5652
if (!isMobile()) return;
5753
58-
animate(
54+
widthAnim?.stop();
55+
widthAnim = animate(
5956
activeCommand,
60-
{
61-
width: [activeCommand.offsetWidth, activeCommand.offsetWidth + 24]
62-
},
57+
{ width: baseWidth + 24 },
6358
{
6459
onComplete: () => {
6560
complete = true;
@@ -68,17 +63,9 @@
6863
);
6964
7065
return () => {
71-
animate(
72-
activeCommand,
73-
{
74-
width: [activeCommand.offsetWidth, activeCommand.offsetWidth - 24]
75-
},
76-
{
77-
onComplete: () => {
78-
complete = false;
79-
}
80-
}
81-
);
66+
widthAnim?.stop();
67+
complete = false;
68+
widthAnim = animate(activeCommand, { width: baseWidth });
8269
};
8370
},
8471
{ amount: 'all' }
@@ -133,7 +120,9 @@
133120
UpdateProfile
134121

135122
{#if complete}
136-
<Checkmark play class="animate-fade-in size-5 text-[#B4F8E2]" />
123+
<span in:fade={{ duration: 150 }} out:fade={{ duration: 100 }}>
124+
<Checkmark play class="size-5 text-[#B4F8E2]" />
125+
</span>
137126
{/if}
138127
</div>
139128
<div

src/routes/(marketing)/(components)/bento/(animations)/sites.svelte

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
import Site from '../../../(assets)/images/site.png';
66
import { cn } from '$lib/utils/cn';
77
import Spinner from '../../spinner.svelte';
8-
import { unwrite, write } from '$lib/animations';
8+
import { unwrite, write, type WriteAnimation } from '$lib/animations';
99
import { trackEvent } from '$lib/actions/analytics';
1010
1111
let container: HTMLElement;
1212
let seconds = $state<number>(32);
1313
let lastLine = $state<string>('');
14+
let currentAnimation: WriteAnimation | null = null;
1415
1516
const text = [
1617
{
@@ -57,7 +58,13 @@
5758
duration: 44
5859
});
5960
60-
write('Installing dependencies...', (v) => (lastLine = v), 500);
61+
currentAnimation?.cancel();
62+
currentAnimation = write(
63+
'Installing dependencies...',
64+
(v) => (lastLine = v),
65+
500,
66+
lastLine.length
67+
);
6168
6269
return () => {
6370
shouldAnimate = false;
@@ -66,7 +73,12 @@
6673
onUpdate: (latest) => (seconds = +latest.toFixed()),
6774
duration: 0.25
6875
});
69-
unwrite('Installing dependencies...', (v) => (lastLine = v), 500);
76+
currentAnimation?.cancel();
77+
currentAnimation = unwrite(
78+
lastLine,
79+
(v) => (lastLine = v),
80+
(lastLine.length / 27) * 500
81+
);
7082
};
7183
});
7284
@@ -81,7 +93,13 @@
8193
duration: 44
8294
});
8395
84-
write('Installing dependencies...', (v) => (lastLine = v), 500);
96+
currentAnimation?.cancel();
97+
currentAnimation = write(
98+
'Installing dependencies...',
99+
(v) => (lastLine = v),
100+
500,
101+
lastLine.length
102+
);
85103
86104
return () => {
87105
shouldAnimate = false;
@@ -90,7 +108,12 @@
90108
onUpdate: (latest) => (seconds = +latest.toFixed()),
91109
duration: 0.25
92110
});
93-
unwrite('Installing dependencies...', (v) => (lastLine = v), 500);
111+
currentAnimation?.cancel();
112+
currentAnimation = unwrite(
113+
lastLine,
114+
(v) => (lastLine = v),
115+
(lastLine.length / 27) * 500
116+
);
94117
};
95118
},
96119
{ amount: 'all' }

0 commit comments

Comments
 (0)