FlipOverlay
3D flip overlay that expands from a trigger
Usage
Loading demo...
Best Practices
- Pass the real trigger element or its
DOMRectassource;nullfalls back to the centered card without origin continuity. - Keep
durationnear the default for stacked overlays so shared mask and card motion stay synchronized. - Use
cardStylefor size constraints such aswidthandmaxHeight; usecardClassfor reusable visual variants. - Prefer
surface="mask"for normal cards,glass/refractiononly when the backdrop remains readable, andpurefor fully custom card styling. - Use
#header-display,#header-actions, or#header-closebefore replacing the whole header; full#headeropts out of built-in close layout.
API Reference
Props
| Name | Type | Default | Description |
|---|---|---|---|
modelValue | boolean | false | Visible state (v-model) |
source | HTMLElement | DOMRect | null | null | Animation origin |
sourceRadius | string | null | null | Origin border radius |
duration | number | 480 | Animation duration (ms) |
perspective | number | 1200 | 3D perspective |
rotateX | number | 6 | X-axis rotation |
rotateY | number | 8 | Y-axis rotation |
randomTilt | boolean | true | Random tilt per open |
tiltRange | number | 2 | Random tilt range |
easeOut | string | 'back.out(1.25)' | Open easing |
easeIn | string | 'back.in(1)' | Close easing |
maskClosable | boolean | true | Click mask to close |
preventAccidentalClose | boolean | false | Accidental-close protection (block mask close + intercept page exit + red warning glow) |
globalMask | boolean | true | Whether to render the global visual mask layer |
surface | 'pure' | 'mask' | 'blur' | 'glass' | 'refraction' | 'mask' | Built-in card surface mode |
surfaceColor | string | '' | Surface base color (theme overlay color by default) |
surfaceOpacity | number | 0.96 | Surface opacity (mask mode) |
speedBoost | number | 1.12 | Time-scale boost applied after speedBoostAt progress. |
speedBoostAt | number | 0.7 | Open/close animation progress threshold that enables speedBoost. |
transitionName | string | 'TxFlipOverlay-Mask' | Vue transition name used for the mask layer. |
header | boolean | true | Enable built-in header when no #header slot is provided |
headerTitle | string | '' | Built-in header title |
headerDesc | string | '' | Built-in header description |
closable | boolean | true | Show built-in round close button |
closeAriaLabel | string | 'Close' | Built-in close button aria-label |
maskClass | string | '' | Mask class |
cardClass | string | '' | Card class |
cardStyle | CSSProperties | - | Inline style object forwarded to the overlay card. |
border | 'solid' | 'dashed' | 'dash' | 'none' | 'solid' | Card border style (dash aliases dashed) |
scrollable | boolean | true | Whether body area scrolls internally |
expanded | boolean | - | Optional controlled expanded animation state for consumers that need sync telemetry. |
animating | boolean | - | Optional controlled animation state for consumers that need sync telemetry. |
Events
| Event | Params | Description |
|---|---|---|
update:modelValue | (value: boolean) | Emitted with false when the overlay closes itself. |
open | - | Open animation starts |
opened | - | Open animation ends |
close | - | Close animation starts |
closed | - | Close animation ends |
update:expanded | (value: boolean) | Sync expanded |
update:animating | (value: boolean) | Sync animating |
Slots
| Slot | Params | Description |
|---|---|---|
default | { close, expanded, animating, closable, headerTitle, headerDesc } | Overlay body content |
header | { close, expanded, animating, closable, headerTitle, headerDesc } | Full custom header (overrides built-in header system) |
header-display | { close, expanded, animating, closable, headerTitle, headerDesc } | Custom built-in title/description area |
header-actions | { close, expanded, animating, closable, headerTitle, headerDesc } | Custom area left of close button |
header-close | { close, expanded, animating, closable, headerTitle, headerDesc } | Custom close area (hidden when closable=false) |
Expose
| Method | Type | Description |
|---|---|---|
close() | () => void | Runs the full close animation and emits update:modelValue(false); the parent must own open state via v-model. |
Overview
modelValue=truemounts the overlay, resolves thesourcerectangle, emitsopen, then emitsopenedafter the card animation completes.- The overlay teleports to
<body>. Its mask and card areposition: fixed, and rendered in place they would be laid out against the nearest ancestor with atransform,filter,containorcontent-visibilityinstead of the viewport. Non-prop attributes still land on the mask. - Built-in close button, slot
close(), mask click, and Escape all start the close path. Mask click and Escape sharehandleMaskClick, somaskClosable=falseblocks both andpreventAccidentalCloseflashes the warning instead; the close button andclose()bypass those gates. - Internal close emits
close, thenupdate:modelValue(false), thenclosed. Parent code must updatev-modelto fully close controlled overlays. - Header render priority:
#headerfully overrides the built-in header; otherwiseheader=falsehides it andheader=truerenders the built-in header (customizable via#header-display/#header-actions/#header-close).closable=falsehides the entire close area, including#header-close. expandedandanimatingare sync telemetry values; bind them only when the surrounding UI needs to observe overlay motion state.globalMask=trueuses a shared body-level mask for stacked overlays; underlay masks are non-interactive and only the top overlay receives mask clicks. Stack displacement is enabled only when adjacent overlays have similar width/height (|delta| <= max(8px, previousSize * 5%)), is capped at depth 3 (-18/-36/-54pxwith0.95/0.90/0.85scale), and deeper layers fade1.00 → 0.92 → 0.78 → 0.62 → 0.38 → 0.16 → 0.preventAccidentalClose=trueblocks mask close and page exit attempts, then flashes the warning state instead of silently closing.- Accessibility: the card is
role="dialog"witharia-modal="true"andtabindex="-1". When the built-in header renders,headerTitlewiresaria-labelledbyandheaderDescwiresaria-describedby. Focus moves into the card on open and is restored to the previously focused element on close.
Technologies
- State contract:
TxFlipOverlayowns the mount/animation lifecycle but only emitsupdate:modelValue(false)when it closes itself; consumers must still syncv-modelafter built-in close, slotclose(), or mask close. - Mount contract: the overlay used to render in place. A docs page's article body is
content-visibility: auto, so the "fixed" card centred itself on the whole article, focusing it scrolled the page about 870px, and the trigger it flips from left the screen. It now teleports the wayTxModalandTxCommandPalettedo; theFlipDialogwrappers that already put it inside<Teleport to="body">are unaffected. - Stacking contract:
globalMask=trueshares the visual backdrop across open overlays while only the top overlay keeps an interactive mask; size-similar overlays receive capped displacement and deeper overlays fade out. - Safety contract:
preventAccidentalCloseblocks mask close and page-exit attempts, then flashes the warning state; it is a guardrail, not a persistence or autosave feature. - Verified coverage:
flip-overlay.test.tscovers defaults, header slot priority, close ordering, dialog semantics (role/aria-modal/aria-labelledby) with Escape close, card style forwarding, stacked masks, layered displacement, and blocked-close behavior. - Component source:
packages/tuffex/packages/components/src/flip-overlay/src/TxFlipOverlay.vue. - Motion implementation:
packages/tuffex/packages/components/src/flip-overlay/src/flip-overlay-motion.ts. - Types:
packages/tuffex/packages/components/src/flip-overlay/src/types.ts. - Coverage:
packages/tuffex/packages/components/src/flip-overlay/__tests__/flip-overlay.test.tsverifies surface defaults, header slot priority, close event order, card style forwarding, stacked masks, layered displacement, and safety-close guards.
查看源码
packages/tuffex/packages/components/src/flip-overlay/index.ts