mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-01 16:08:30 +00:00
## Fix: mobile signup modal freeze (defer Google Sign-In)
**Problem:** On mobile Safari, tapping "Sign up to create" on the create pages froze for 5–10s before the signup modal appeared. Desktop was fine. **Cause:** The modal mounts Google's GSI sign-in widget (`<GoogleLogin>`), which boots a cross-site iframe to `accounts.google.com`. Mobile Safari's tracking prevention (ITP) throttles that cross-site init for several seconds, and because the widget mounted synchronously when the modal opened, the modal's first paint was blocked behind it. The login/signup pages didn't show this because they mount the widget during a route change, hiding the cost. **Fix:** Defer mounting the real GSI iframe until after first paint (`useEffect` + `requestAnimationFrame`). The custom "Continue with Google" button shows instantly; the actual Google button hydrates a frame later, off the critical path. The container has a fixed height, so there's no layout shift. - Centralized in `GoogleLoginButton`, so the login/signup pages benefit too. - Tradeoff: the Google button becomes clickable ~1 frame later (imperceptible). - No behavior change on desktop.
This commit is contained in:
1 parent
33116e0e5b
commit
33be9f1ca4
1 file changed
+29
-14
@@ -8,7 +8,7 @@ import {
|
||||
} from "@storyteller/common";
|
||||
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface GoogleLoginButtonProps {
|
||||
onSuccess: (isNewUser: boolean) => void;
|
||||
@@ -25,6 +25,17 @@ export function GoogleLoginButton({
|
||||
}: GoogleLoginButtonProps) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Defer mounting Google's GSI widget until after first paint. Its cross-site
|
||||
// init stalls for several seconds on mobile Safari (ITP throttles
|
||||
// accounts.google.com), so mounting it inline blocks the surrounding UI (e.g.
|
||||
// the signup modal) from appearing. The custom visual button below shows
|
||||
// instantly; the real Google button hydrates a frame later.
|
||||
const [mountGsi, setMountGsi] = useState(false);
|
||||
useEffect(() => {
|
||||
const raf = requestAnimationFrame(() => setMountGsi(true));
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}, []);
|
||||
|
||||
const handleGoogleSuccess = async (
|
||||
credentialResponse: CredentialResponse,
|
||||
) => {
|
||||
@@ -75,19 +86,23 @@ export function GoogleLoginButton({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Google's actual button - invisible overlay that receives clicks */}
|
||||
<div
|
||||
className="absolute inset-0 w-full h-full overflow-hidden cursor-pointer flex items-center justify-center"
|
||||
style={{ opacity: 0.01, zIndex: 10, transform: "scaleY(1.2)" }}
|
||||
>
|
||||
<GoogleLogin
|
||||
onSuccess={handleGoogleSuccess}
|
||||
onError={() => onError("Google login failed")}
|
||||
size="large"
|
||||
width={400}
|
||||
text="continue_with"
|
||||
/>
|
||||
</div>
|
||||
{/* Google's actual button - invisible overlay that receives clicks.
|
||||
Mounted after first paint (see mountGsi) so its slow cross-site init
|
||||
never blocks the surrounding UI from showing. */}
|
||||
{mountGsi && (
|
||||
<div
|
||||
className="absolute inset-0 w-full h-full overflow-hidden cursor-pointer flex items-center justify-center"
|
||||
style={{ opacity: 0.01, zIndex: 10, transform: "scaleY(1.2)" }}
|
||||
>
|
||||
<GoogleLogin
|
||||
onSuccess={handleGoogleSuccess}
|
||||
onError={() => onError("Google login failed")}
|
||||
size="large"
|
||||
width={400}
|
||||
text="continue_with"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user