UNPKG

react-bottom-fixed

Version:

A React component for iOS that keeps your bottom-positioned elements fixed and safely visible, automatically adjusting their positions when viewport changes (e.g., virtual keyboard appearance).

76 lines (75 loc) 3.89 kB
/** * BottomFixedArea * ============================================================================= * PURPOSE * ------- * Keeps its children (typically a Call‑To‑Action button) visually pinned to the * **visible** bottom edge of the viewport on iOS Safari/Chrome *even while* the * on‑screen keyboard is animating in or out. * * WHY THIS COMPONENT EXISTS * ------------------------- * 1. Mobile Safari & Chrome on iOS break `position: fixed` whenever the virtual * keyboard appears. The `visualViewport` shrinks and the browser pans or * resizes the page, so `bottom: 0` no longer means "bottom". * 2. Android resolved this issue back in 2019; iOS has not, so we guard all * logic behind an `isIOS` check to avoid unnecessary work elsewhere. * 3. Product and design teams love bottom‑aligned CTAs because they convert * well. Losing them under the keyboard is **not** acceptable. * * HIGH‑LEVEL STRATEGY * ------------------- * We keep the CTA in normal document flow and translate it vertically using * `transform: translateY(...)`. The translation amount is simply the *negative* * height of the virtual keyboard, derived from the `visualViewport` API. * * ┌──────────────────────────────┐ visualViewport.height * │ (visible content) │◄───────────────┐ * │ │ │ offsetTop * ├──────────────────────────────┤ │ * │ ┌─────────────┐ │ │ * │ │ keyboard │ │◄───────────────┘ (CTA moves above this) * │ └─────────────┘ │ * └──────────────────────────────┘ * * KEY APIS & CONCEPTS * ------------------- *`window.visualViewport` — reveals the unobscured portion of the page. *`visualViewport.resize / scroll` — fire on *every frame* of the keyboard * animation, letting us sync the CTA in real‑time. *`focusin / focusout` — reliable way to know when the keyboard is entering * or leaving. *`transform` over `top/bottom` — avoids reflow; critical for low‑end iPhones. * * EDGE CASES HANDLED * ------------------ * • Page may or may not have its own scrollbar *before* the keyboard appears. * • Safari URL‑bar collapse introduces a phantom "height gap". * • Browsers fire `focusin` **before** the keyboard is fully visible; we wait * 300 ms to avoid flicker. * • Users can scroll or drag while typing; we fade the CTA so it doesn't block * what they are reading. * * DEVELOPMENT & MAINTENANCE NOTES * ------------------------------- * 1. Stick to GPU‑friendly `transform` properties; touching layout metrics * inside scroll handlers will stutter. * 2. If a future iOS version fixes this quirk, delete the `useEffect` entirely * and the component degrades to a simple wrapper. * 3. Do **not** polyfill `visualViewport`; the math below assumes native * behavior. * 4. Performance budget: keep work under 1 RAF; handlers must stay light. */ import { ReactNode } from 'react'; import './index.css'; export declare const ScrollBehavior: { readonly FADE_OUT: "fade-out"; readonly CLOSE_KEYBOARD: "close-keyboard"; }; export type ScrollBehaviorType = (typeof ScrollBehavior)[keyof typeof ScrollBehavior]; export interface BottomFixedProps { children: ReactNode; className?: string; scrollBehavior?: ScrollBehaviorType; } export declare function BottomFixed({ children, className, scrollBehavior, }: BottomFixedProps): JSX.Element;