Last active
May 28, 2021 01:13
-
-
Save lletfrix/11b3e07bf869f93c1fa79a9ba40bc4d1 to your computer and use it in GitHub Desktop.
ShadowView.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { View, StyleSheet, Text, Dimensions, Platform } from 'react-native'; | |
import Svg, { Circle, Rect, Defs, Use, Symbol, ForeignObject, LinearGradient, RadialGradient, Stop, G} from 'react-native-svg'; | |
import { LinearGradient as LGradient } from 'expo-linear-gradient'; | |
const Gauss = (x, sigma) => 1/(sigma * Math.sqrt(2*Math.PI)) * Math.exp( - (x**2) / (2*sigma**2 )) | |
const dot = (v1, v2) => v1.reduce( (acc, comp, idx) => (acc + comp*v2[idx]), 0) | |
const convolutions = (img, ker) => { | |
const s = ker.reduce( (a, b) => a+b, 0 ) | |
const conv = [] | |
for (let i = 0; i < img.length - ker.length + 1; i++) { | |
conv.push(dot(ker, img.slice(i, i+ker.length))/s); | |
} | |
return conv; | |
} | |
const gradientValues = (radius) => { | |
const sigma = radius/2; // Should we adjust this to 1.75?? | |
const a = -175/22; | |
const c = 75/22; | |
const final = 1/(a*radius+c)+1 | |
const initial = 1 - final; | |
let kerW = Math.ceil(3*sigma); | |
if (!( kerW % 2 )) { | |
kerW = kerW-1 | |
}; | |
const pad = Math.round((kerW-1)/2); | |
const padimg = Array.from({length: pad+radius}, (_) => initial).concat(Array.from({length: pad+radius}, (_) => final)); | |
const ker = Array.from({length: kerW}, (_, i) => Gauss(Math.abs(i - (kerW+1)/2 + 1), sigma)) | |
return convolutions(padimg, ker) | |
} | |
const linearApprox = (radius, color, maxStop, maxOpacity) => { | |
const alphas = Array.of(0, ...gradientValues(radius)); | |
alphas.push(1); | |
const colors = alphas.map( a => color+ ('00' + (a*maxOpacity * 255 | 0).toString(16)).slice(-2)) | |
const locations = Array.from({length: colors.length}, ( (_, i) => i/(colors.length-1)*maxStop ) ); | |
// Should we take care of less colors? | |
return {colors, locations, alphas} | |
} | |
export const ShadowView = ({shadowRadius, shadowOffset = {width: 0, height: 0}, shadowColor, shadowOpacity = 1, surroundColor = 'white', style, children}) => { | |
if (Platform.OS === 'ios') { // It seems shadows on iOS are double the size of the SVG implemented. | |
return <View style={{...style, shadowColor, shadowOpacity, shadowOffset, shadowRadius: shadowRadius/2}}>{children}</View> | |
} | |
const outerStyle = (({width, height}) => ( | |
width && height ? {width, height, maxWidth: width, maxHeight: height} | |
: width ? {width, maxWidth: width} | |
: height ? {height, maxHeight: height} | |
: {} | |
))(style); | |
const { width: offsetX, height: offsetY } = shadowOffset; | |
const borderRadius = style.borderRadius ? style.borderRadius : 0; | |
const blurDiam = shadowRadius + Math.max(borderRadius, shadowRadius); | |
const maxStop = 2*shadowRadius/blurDiam; | |
const computedShadowColor = shadowColor + ('00' + (shadowOpacity*255 | 0).toString(16)).slice(-2); | |
const linearStops = linearApprox(shadowRadius, shadowColor, maxStop, shadowOpacity); | |
return ( | |
<View style={[{alignItems: 'center', justifyContent: 'center', flex: 1}, outerStyle]}> | |
<View style={[{position: 'absolute', top: offsetY, bottom: -offsetY, left: offsetX, right: -offsetX}, {margin: -shadowRadius, backgroundColor: '#ffffff'}]}> | |
<Svg width="100%" height="100%" preserveAspectRatio="none"> | |
<Defs> | |
<RadialGradient id="radialGradient" cx="100%" cy="100%" r="100%"> | |
{ linearStops.alphas.map(( a, i ) => (<Stop offset={1 - linearStops.locations[i]} stopColor={shadowColor} stopOpacity={a*shadowOpacity} key={i}/>)) } | |
</RadialGradient> | |
<Symbol id="corner" width={`${blurDiam}`} height={`${blurDiam}`} viewBox={`0 0 ${blurDiam} ${blurDiam}`}> | |
<Rect x="0" y="0" width={`${blurDiam}`} height={`${blurDiam}`} fill={`${surroundColor}`} /> | |
<Rect x="0" y="0" width={`${blurDiam}`} height={`${blurDiam}`} fill="url(#radialGradient)" /> | |
</Symbol> | |
</Defs> | |
<ForeignObject x={`${blurDiam}`} y={`${blurDiam}`} width="100%" height="100%"> | |
<Svg width="100%" height="100%" preserveAspectRatio="none"> | |
<ForeignObject x="0" y="-100%" height="100%" width="100%" transform="scale(1, -1)"> | |
<View style={{paddingTop: blurDiam, marginHorizontal: blurDiam, height: Dimensions.get('window')['height'], overflow: 'hidden'}}> | |
<View style={{backgroundColor: computedShadowColor, flexGrow: 1}}/> | |
</View> | |
</ForeignObject> | |
</Svg> | |
</ForeignObject> | |
<ForeignObject width="200%" height={`${blurDiam}`} x={`${blurDiam}`}> | |
<View style={{marginHorizontal: blurDiam, height:blurDiam, backgroundColor: surroundColor}}> | |
<LGradient style={{flexGrow: 1}} colors={linearStops.colors} locations={linearStops.locations} start={[0, 0]} end={[0, 1]}/> | |
</View> | |
</ForeignObject> | |
<ForeignObject width="100%" height={`${blurDiam}`} x={`${blurDiam}`} y="-100%" transform="scale(1, -1)"> | |
<View style={{marginHorizontal: blurDiam, height:blurDiam, backgroundColor: surroundColor}}> | |
<LGradient style={{flexGrow: 1}} colors={linearStops.colors} locations={linearStops.locations} start={[0, 0]} end={[0, 1]}/> | |
</View> | |
</ForeignObject> | |
<G id="vertical"> | |
<ForeignObject x="0" y={`${blurDiam}`} height="100%"> | |
<Svg width={`${blurDiam}`} height="100%" preserveAspectRatio="none"> | |
<ForeignObject x="0" y="-100%" height="100%" transform="scale(1, -1)"> | |
<View style={{paddingTop: blurDiam, position: 'absolute', height: Dimensions.get('window')['height'], width: blurDiam, overflow: 'hidden', backgroundColor: surroundColor}}> | |
<LGradient style={{flexGrow: 1}} colors={linearStops.colors} locations={linearStops.locations} start={[0, 0]} end={[1, 0]}/> | |
</View> | |
</ForeignObject> | |
</Svg> | |
</ForeignObject> | |
</G> | |
<Use href="#vertical" transform="scale(-1, 1)" x="-100%"/> | |
<Use href="#corner" width={`${blurDiam}`} height={`${blurDiam}`}/> | |
<Use href="#corner" transform="scale(1, -1)" width={`${blurDiam}`} height={`${blurDiam}`} x="0" y="-100%"/> | |
<Use href="#corner" transform="scale(-1, 1)" width={`${blurDiam}`} height={`${blurDiam}`} x="-100%" y="0"/> | |
<Use href="#corner" transform="scale(-1, -1)" width={`${blurDiam}`} height={`${blurDiam}`} x="-100%" y="-100%"/> | |
</Svg> | |
</View> | |
<View style={[{alignSelf: 'stretch', flex: 1}, style]}> | |
{children} | |
</View> | |
</View> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment