Skip to content

Commit 9bcbd2f

Browse files
committed
fix(resources): iframe scaling failed on mobile screens with mobile UA
The bug is due to two things: 1. Wix delivers a mobile-optimized website that caps width to 320px when they receive a mobile UA. Our code assumes width to be 1440px across all screen sizes/UA, which fails. The fix is to auto-detect UA client-side and set the IFRAME_DEFAULTS to 320 or 1440 for mobile/desktop respectively. 2. `calculateResponsiveScale` caps the return value via `Math.min(containerWidth / sourceWidth, 1)`. So when sourceWidth is set to 320, and the code passes user screen width to containerWidth, it still does not work since mobile screens are usually 450px => 450/320 > 1 -> 1 (capped). The fix is to remove the Math.min(). These are the main fixes of the code. This commit also incorporates some usual cleanup of the code, e.g., inlining functions/code that are only called once (e.g., calculateResponsiveScale, createScaleTransformStyle). Also, it removes the unnecessary <div> that wraps the <iframe>, and instead applies styles directly on the iframe.
1 parent 32718b3 commit 9bcbd2f

File tree

1 file changed

+66
-39
lines changed

1 file changed

+66
-39
lines changed

src/components/sections/resource-iframe.tsx

Lines changed: 66 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,63 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
66
import { cn, isInternalHref, noReturnDebounce } from "@/lib/utils";
77
import { CSSProperties, RefObject, useCallback, useEffect, useRef, useState } from "react";
88

9-
const IFRAME_DEFAULTS = {
10-
WIDTH: 1440, // Magic! Experiment with this for different iframed websites
11-
HEIGHT: 600,
9+
const IFRAME_SIZE = {
10+
/**
11+
* For Wix, mobile UA serves:
12+
* <div id="SITE_CONTAINER">
13+
* body.device-mobile-optimized:not(.responsive) #SITE_CONTAINER
14+
* width: 320px;
15+
* Hence MOBILE_WIDTH = 320.
16+
*/
17+
MOBILE_WIDTH: 320,
18+
DESKTOP_WIDTH: 1440,
19+
/** DEFAULT_WIDTH = DESKTOP_WIDTH */
20+
DEFAULT_WIDTH: 1440,
21+
/** Non-fullscreen view iframe height in /resource page */
22+
HEIGHT: 400,
1223
} as const;
1324

14-
function calculateResponsiveScale(containerWidth: number, sourceWidth: number): number {
15-
if (!containerWidth || !sourceWidth) return 1;
16-
return Math.min(containerWidth / sourceWidth, 1);
17-
}
18-
19-
function createScaleTransformStyle(scale: number, hideTopPx: number = 0) {
20-
const computedTopOffset = Math.max(0, hideTopPx) * scale;
21-
const height = hideTopPx > 0 ? `calc(${100 / scale}% + ${hideTopPx}px)` : `${100 / scale}%`;
25+
/** Prefer UA detection when available; fallback to viewport width <= 640px. */
26+
function getIsMobileNow(): boolean {
27+
if (typeof window === "undefined" || typeof navigator === "undefined") return false;
28+
// Wix uses UserAgent to detect mobile, so its better for us to stay consistent.
29+
const nav = navigator as Navigator & { userAgentData?: { mobile?: boolean }; maxTouchPoints?: number };
30+
if (typeof nav.userAgentData?.mobile === "boolean") {
31+
if (nav.userAgentData.mobile) return true;
32+
}
2233

23-
return {
24-
transform: `scale(${scale})`,
25-
transformOrigin: "top left",
26-
width: `${100 / scale}%`,
27-
height,
28-
position: "relative" as const,
29-
top: hideTopPx > 0 ? `-${computedTopOffset}px` : undefined,
30-
};
34+
const ua = navigator.userAgent || "";
35+
const isIpadOs =
36+
/iPad/i.test(ua) && (navigator as Navigator & { maxTouchPoints?: number }).maxTouchPoints! > 1;
37+
const isMobileUa = /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini|Mobi/i.test(ua) && !isIpadOs;
38+
if (isMobileUa) return true;
39+
return window.innerWidth <= 640;
3140
}
3241

3342
/**
34-
* Resize the iframe zoom level to fit the container width to make it look normal
43+
* Determine whether the client is mobile and update on user rotating/resize their screen.
3544
*/
45+
function useIsMobile(): boolean {
46+
const [isMobile, setIsMobile] = useState<boolean>(() => getIsMobileNow());
47+
48+
useEffect(() => {
49+
const recompute = noReturnDebounce(() => {
50+
setIsMobile(getIsMobileNow());
51+
});
52+
window.addEventListener("resize", recompute);
53+
return () => window.removeEventListener("resize", recompute);
54+
}, []);
55+
56+
return isMobile;
57+
}
58+
59+
/** Resize the iframe zoom level to fit the container width to make it look normal */
3660
function useResponsiveScale(targetRef: RefObject<HTMLElement | null>, sourceWidth: number): number {
3761
const [scale, setScale] = useState(1);
3862

3963
const updateScale = useCallback(() => {
4064
if (!targetRef.current) return;
41-
const newScale = calculateResponsiveScale(targetRef.current.clientWidth, sourceWidth);
42-
setScale(newScale);
65+
setScale(targetRef.current.clientWidth && sourceWidth ? targetRef.current.clientWidth / sourceWidth : 1);
4366
}, [targetRef, sourceWidth]);
4467

4568
useEffect(() => {
@@ -74,8 +97,8 @@ export function ResourceIframe({
7497
title,
7598
className,
7699
scale: forcedScale,
77-
desktopWidth = IFRAME_DEFAULTS.WIDTH,
78-
containerHeight = IFRAME_DEFAULTS.HEIGHT,
100+
desktopWidth = IFRAME_SIZE.DEFAULT_WIDTH,
101+
containerHeight = IFRAME_SIZE.HEIGHT,
79102
hideTopPx = 0,
80103
fullPageHref,
81104
hideHeader = false,
@@ -84,10 +107,11 @@ export function ResourceIframe({
84107
}: ResourceIframeProps) {
85108
const [isLoading, setIsLoading] = useState(true);
86109
const containerRef = useRef<HTMLDivElement>(null);
87-
const calculatedScale = useResponsiveScale(containerRef, desktopWidth);
110+
const isMobileClient = useIsMobile();
111+
const effectiveSourceWidth = isMobileClient ? IFRAME_SIZE.MOBILE_WIDTH : desktopWidth;
112+
const calculatedScale = useResponsiveScale(containerRef, effectiveSourceWidth);
88113

89114
const finalScale = forcedScale ?? calculatedScale;
90-
const scaleWrapperStyle = createScaleTransformStyle(finalScale, hideTopPx);
91115
const linkHref = fullPageHref ?? websiteUrl;
92116
const isInternalLink = isInternalHref(linkHref);
93117
const buttonLabel = isInternalLink ? "Open Full Page →" : "Open Original Site →";
@@ -131,19 +155,22 @@ export function ResourceIframe({
131155
<p className="text-muted-foreground">Loading website...</p>
132156
</div>
133157
)}
134-
<div
135-
className="h-full w-full"
136-
style={scaleWrapperStyle}
137-
>
138-
<iframe
139-
src={websiteUrl}
140-
title={title}
141-
allowFullScreen
142-
loading="lazy"
143-
className="h-full w-full border-0"
144-
onLoad={() => setIsLoading(false)}
145-
/>
146-
</div>
158+
<iframe
159+
src={websiteUrl}
160+
title={title}
161+
allowFullScreen
162+
loading="lazy"
163+
className="border-0"
164+
style={{
165+
transform: `scale(${finalScale})`,
166+
transformOrigin: "top left",
167+
width: `${100 / finalScale}%`,
168+
height: hideTopPx > 0 ? `calc(${100 / finalScale}% + ${hideTopPx}px)` : `${100 / finalScale}%`,
169+
position: "relative" as const,
170+
top: hideTopPx > 0 ? `-${Math.max(0, hideTopPx) * finalScale}px` : undefined,
171+
}}
172+
onLoad={() => setIsLoading(false)}
173+
/>
147174
</div>
148175
</CardContent>
149176
</Card>

0 commit comments

Comments
 (0)