Skip to content

Instantly share code, notes, and snippets.

@ChronSyn
Forked from lletfrix/ShadowView.js
Created May 28, 2021 01:13
Show Gist options
  • Save ChronSyn/f8a834d3fbc4e3de08ef5b402f6f4008 to your computer and use it in GitHub Desktop.
Save ChronSyn/f8a834d3fbc4e3de08ef5b402f6f4008 to your computer and use it in GitHub Desktop.
ShadowView.js
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