From 0f6d4a5f0732226d0e0ff74d11dae6e952ff9ad3 Mon Sep 17 00:00:00 2001 From: Frudrax Cheng Date: Wed, 27 May 2026 09:17:56 +0800 Subject: [PATCH] Use real device rotation for signature overlay CSS transform: rotate(90deg) only rotates the visual; pointer events still fire in the unrotated viewport coordinate system, and canvas.getBoundingClientRect returns the rotated bounding box with swapped width/height. signature_pad computed stroke positions from clientX/clientY minus that swapped rect, so most strokes landed outside the canvas drawing buffer. The brief flash of marks visible when the phone was rotated back to portrait was the small fraction of points that happened to land inside the canvas, revealed once the CSS rotation was undone. Drop the CSS fake-landscape and gate the signature pad behind real device orientation: portrait shows a rotate-your-phone prompt, and the pad only renders in landscape where the coordinate system is clean. Attempt screen.orientation.lock('landscape') where supported; iOS users with portrait lock see the prompt with a hint. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/SignatureOverlay.css | 52 +++++++++++++++++++++-------- src/components/SignatureOverlay.tsx | 43 +++++++++++++++++++----- 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/src/components/SignatureOverlay.css b/src/components/SignatureOverlay.css index 44ca178..a62ed8d 100644 --- a/src/components/SignatureOverlay.css +++ b/src/components/SignatureOverlay.css @@ -17,19 +17,6 @@ box-sizing: border-box; } -/* Phones in portrait: rotate the whole stage to use landscape space */ -@media (orientation: portrait) and (max-width: 820px) { - .signature-overlay-stage { - inset: auto; - top: 50%; - left: 50%; - width: 100dvh; - height: 100dvw; - transform: translate(-50%, -50%) rotate(90deg); - transform-origin: center center; - } -} - .signature-overlay-bar { display: flex; align-items: center; @@ -50,7 +37,6 @@ border-radius: 12px; background: #ffffff; overflow: hidden; - /* keep room from screen edges so iOS back-swipe doesn't intercept */ margin: 0 4px; } @@ -73,3 +59,41 @@ .signature-overlay-actions { flex-shrink: 0; } + +.signature-overlay-rotate { + position: absolute; + inset: 0; + z-index: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 24px; + gap: 14px; + background: #f8fafc; + text-align: center; +} + +.signature-overlay-rotate-icon { + font-size: 64px; + color: #1677ff; + animation: signature-rotate-hint 1.6s ease-in-out infinite; +} + +@keyframes signature-rotate-hint { + 0%, 100% { transform: rotate(0deg); } + 50% { transform: rotate(90deg); } +} + +.signature-overlay-rotate-text { + font-size: 17px; + font-weight: 500; + color: #1f2937; + margin: 0; +} + +.signature-overlay-rotate-hint { + font-size: 13px; + color: #6b7280; + margin: 0 0 8px; +} diff --git a/src/components/SignatureOverlay.tsx b/src/components/SignatureOverlay.tsx index 73cfcd3..f285c18 100644 --- a/src/components/SignatureOverlay.tsx +++ b/src/components/SignatureOverlay.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import { Button } from 'antd'; +import { RotateRightOutlined } from '@ant-design/icons'; import SignaturePad, { type SignaturePadHandle } from './SignaturePad'; import './SignatureOverlay.css'; @@ -9,26 +10,38 @@ interface SignatureOverlayProps { onConfirm: (dataUrl: string) => void; } +const isLandscapeNow = () => + typeof window !== 'undefined' && window.matchMedia('(orientation: landscape)').matches; + function SignatureOverlay({ open, onCancel, onConfirm }: SignatureOverlayProps) { const padRef = useRef(null); - const [data, setData] = useState(''); + const [, setData] = useState(''); + const [landscape, setLandscape] = useState(isLandscapeNow); useEffect(() => { if (!open) return; + const mq = window.matchMedia('(orientation: landscape)'); + const update = () => setLandscape(mq.matches); + update(); + mq.addEventListener('change', update); + const orientation = (screen as Screen & { - orientation?: { lock?: (o: string) => Promise }; + orientation?: { lock?: (o: string) => Promise; unlock?: () => void }; }).orientation; if (orientation?.lock) { orientation.lock('landscape').catch(() => { - // ignore; iOS Safari etc. don't permit lock outside fullscreen + // ignore; iOS Safari and most browsers refuse outside fullscreen }); } const prevOverflow = document.body.style.overflow; document.body.style.overflow = 'hidden'; + return () => { + mq.removeEventListener('change', update); document.body.style.overflow = prevOverflow; + orientation?.unlock?.(); }; }, [open]); @@ -40,15 +53,29 @@ function SignatureOverlay({ open, onCancel, onConfirm }: SignatureOverlayProps) }; const handleConfirm = () => { - if (padRef.current?.isEmpty()) return; - const url = padRef.current?.getDataURL() || data; - if (!url) return; - onConfirm(url); + const pad = padRef.current; + if (!pad || pad.isEmpty()) return; + onConfirm(pad.getDataURL()); }; return (
-
+ {!landscape && ( +
+ +

请将手机横置以开始签名

+

+ 如果无法旋转,请检查手机是否锁定了竖屏方向 +

+ +
+ )} + {/* SignaturePad stays mounted so its strokes survive accidental rotation flips */} +