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
TypeScript
/**
* 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;