UNPKG

@sbaiahmed1/react-native-blur

Version:
159 lines (146 loc) 6.94 kB
"use strict"; /** * ViewStyle extended with the web-only CSS properties the web implementations * rely on. react-native-web passes unknown style properties through to the DOM, * but RN's ViewStyle type does not declare them. Dynamic (inline) styles are * NOT auto-prefixed by react-native-web, so the Webkit twins must always be set * alongside the unprefixed properties. */ /** * Scale from the 0-100 `blurAmount` prop to a CSS blur radius in px. * 100 → 40px, calibrated side-by-side against the iOS simulator so common * amounts (40-70) diffuse the backdrop like UIVisualEffectView does. */ export const WEB_BLUR_RADIUS_SCALE = 0.4; /** Number of stacked backdrop-filter layers used for the progressive ramp. */ export const PROGRESSIVE_LAYER_COUNT = 5; /** * Stacked sibling backdrop-filters compound roughly as the root-sum-of-squares * of their radii (each later layer re-blurs the earlier layers' output). With * radii halving per layer the compound peak lands at ~1.15x the largest radius, * so radii are scaled down by ~0.87 to make the peak match `blurAmount`. */ export const PROGRESSIVE_RADIUS_COMPENSATION = 0.87; /** * Maps native blur types to semi-transparent CSS background colours that * approximate the tint each type produces on iOS: light variants use * white-based overlays, dark variants black-based ones, and the system * materials follow the iOS ultra-thin → chrome opacity hierarchy. */ export const BLUR_TYPE_TO_BACKGROUND = { // Base types xlight: 'rgba(255, 255, 255, 0.6)', light: 'rgba(255, 255, 255, 0.4)', dark: 'rgba(0, 0, 0, 0.5)', extraDark: 'rgba(0, 0, 0, 0.75)', regular: 'rgba(255, 255, 255, 0.2)', prominent: 'rgba(255, 255, 255, 0.35)', // System materials (default / adaptive) systemUltraThinMaterial: 'rgba(255, 255, 255, 0.1)', systemThinMaterial: 'rgba(255, 255, 255, 0.2)', systemMaterial: 'rgba(255, 255, 255, 0.3)', systemThickMaterial: 'rgba(255, 255, 255, 0.45)', systemChromeMaterial: 'rgba(255, 255, 255, 0.5)', // System materials (light) systemUltraThinMaterialLight: 'rgba(255, 255, 255, 0.15)', systemThinMaterialLight: 'rgba(255, 255, 255, 0.25)', systemMaterialLight: 'rgba(255, 255, 255, 0.4)', systemThickMaterialLight: 'rgba(255, 255, 255, 0.55)', systemChromeMaterialLight: 'rgba(255, 255, 255, 0.6)', // System materials (dark) systemUltraThinMaterialDark: 'rgba(0, 0, 0, 0.2)', systemThinMaterialDark: 'rgba(0, 0, 0, 0.3)', systemMaterialDark: 'rgba(0, 0, 0, 0.45)', systemThickMaterialDark: 'rgba(0, 0, 0, 0.6)', systemChromeMaterialDark: 'rgba(0, 0, 0, 0.7)' }; const clamp = (value, min, max) => Math.min(Math.max(value, min), max); /** Formats a number for CSS without float noise (2 decimals max). */ const cssNumber = value => Number(value.toFixed(2)); let backdropFilterSupport; /** * Feature-detects CSS backdrop-filter. Safe to call during server-side / * static rendering (Expo `web.output: "static"` renders in Node, where the * CSS API is absent) — it simply reports false there, leaving the tint-only * fallback styles. Memoized after the first call. */ export function supportsBackdropFilter() { if (backdropFilterSupport === undefined) { const css = globalThis.CSS; backdropFilterSupport = typeof css?.supports === 'function' && (css.supports('backdrop-filter', 'blur(1px)') || css.supports('-webkit-backdrop-filter', 'blur(1px)')); } return backdropFilterSupport; } /** * Builds the style for a uniform blur layer: the blurType tint as background * plus, when supported, a backdrop blur scaled from the 0-100 `blurAmount`. * Without backdrop-filter support the tint alone remains (graceful * degradation, same strategy as expo-blur). */ export function getBlurLayerStyle(blurType, blurAmount) { const style = { backgroundColor: BLUR_TYPE_TO_BACKGROUND[blurType] ?? BLUR_TYPE_TO_BACKGROUND.regular }; if (supportsBackdropFilter()) { const radius = cssNumber(clamp(blurAmount, 0, 100) * WEB_BLUR_RADIUS_SCALE); const filter = `blur(${radius}px) saturate(180%)`; style.backdropFilter = filter; style.WebkitBackdropFilter = filter; } return style; } /** One layer of the stacked progressive blur. */ const pct = value => `${cssNumber(value)}%`; const maskForLayer = (direction, startOffset, layer) => { const n = PROGRESSIVE_LAYER_COUNT; if (direction === 'blurredCenterClearTopAndBottom') { // Band edges as distances from the 50% center line. const q = j => startOffset * 50 + (1 - startOffset) * 50 * j / n; const inner = q(n - layer); const outer = q(n - layer + 1); return `linear-gradient(to bottom, transparent ${pct(50 - outer)}, ` + `black ${pct(50 - inner)}, black ${pct(50 + inner)}, ` + `transparent ${pct(50 + outer)})`; } // Band edges measured from the blurred edge (0%). const boundary = j => startOffset * 100 + (1 - startOffset) * 100 * j / n; const axis = direction === 'blurredBottomClearTop' ? 'to top' : 'to bottom'; const solidEnd = boundary(n - layer); const fadeEnd = boundary(n - layer + 1); return `linear-gradient(${axis}, black 0%, black ${pct(solidEnd)}, transparent ${pct(fadeEnd)})`; }; /** * Computes the stacked layers for a progressive blur: every layer covers the * fully-blurred plateau and fades out one gradient band further than the next * stronger one, halving the radius per band. Stacked, they produce a smooth * blur ramp from `blurAmount` px at the blurred edge down to 0 at the clear * edge — a true radius ramp rather than a single blur fading in opacity. * * `blurAmount` is the maximum radius in px (matching the native components, * where it is a pixel radius, unlike BlurView's 0-100 scale). */ export function getProgressiveLayers(direction, startOffset, blurAmount) { const s = clamp(startOffset, 0, 1); const maxRadius = Math.max(blurAmount, 0) * PROGRESSIVE_RADIUS_COMPENSATION; const layers = []; for (let layer = 1; layer <= PROGRESSIVE_LAYER_COUNT; layer++) { layers.push({ radius: cssNumber(maxRadius / 2 ** (PROGRESSIVE_LAYER_COUNT - layer)), maskImage: maskForLayer(direction, s, layer) }); } return layers; } /** * Gradient for the tint layer of a progressive blur: the blurType tint at full * strength over the blurred plateau, fading to transparent at the clear edge, * following the same direction as the blur ramp. */ export function getProgressiveTintGradient(direction, startOffset, tint) { const s = clamp(startOffset, 0, 1); if (direction === 'blurredCenterClearTopAndBottom') { const inner = s * 50; return `linear-gradient(to bottom, transparent 0%, ${tint} ${pct(50 - inner)}, ` + `${tint} ${pct(50 + inner)}, transparent 100%)`; } const axis = direction === 'blurredBottomClearTop' ? 'to top' : 'to bottom'; return `linear-gradient(${axis}, ${tint} 0%, ${tint} ${pct(s * 100)}, transparent 100%)`; } //# sourceMappingURL=webBlurUtils.js.map