I am trying to create a shadow over the bottom part of an image like in the example I have provided. I was thinking about just overlaying a linear gradient and changing the opacity maybe, I am not even sure how accurately this would portray it but I am assuming there is a better way.
To be clear, I do NOT want to create a shadow on the outside of the image, I want it to overlay the image.
Thank you guys for any insight at all!
Linear Gradient to achieve using expo linear gradient
<View style={{borderBottomLeftRadius: 30, borderBottomRightRadius: 30, overflow: 'hidden', top: 125}}>
<LinearGradient
colors={['transparent', 'rgba(0,0,0,0.4)', ]}
style={{width: 300, height: 100,}}>
</LinearGradient>
</View>
I found a good library here to use, the usage in their example provided exactly what I am looking for with easy to use props.
https://www.npmjs.com/package/react-native-inset-shadow
import React from "react";
import { View, Image, } from "react-native";
const ShadowExample = (props) => {
const imageURL = "https://designpress-10674.kxcdn.com/wp-content/uploads/2012/10/funny-horse-pictures/happy-to-see-you.jpg"
const localStyles = {
withShadowStyle: {
justifyContent: "center",
alignItems: "center",
backgroundColor: "white",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.50,
shadowRadius: 4,
elevation: 3,
},
};
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<View style={{ ...localStyles.withShadowStyle, padding: 10, }}>
<Image source={{ uri: imageURL, }} style={{ width: 200, height: 200, }} />
</View>
</View>
)
}
P.S. Check out the React Native shadow generator:
https://ethercreative.github.io/react-native-shadow-generator/
Related
I'm new using React Native and I'm trying to map the following component (made in web) but for React Native with no success:
Elevation and shadow properties does not do the trick because they add some blur to the resulting shadow. Which would be the proper way to handle this?
Regards
Use react-native-cardview
import React, { Component } from 'react';
import {
View,
ScrollView,
TextInput,
} from 'react-native';
import CardView from 'react-native-cardview';
import styles from './styles';
export default class Signup extends Component {
render() {
return (
<View style={{ flex: 1, backgroundColor: colors.whiteColor }}>
<ScrollView contentContainerStyle={styles.signupContainer}>
<View style={styles.signupInputs}>
<CardView
style={styles.cardStyle}
cardElevation={2}
cardMaxElevation={2}
cornerRadius={5}
>
<TextInput
underlineColorAndroid="transparent"
style={[styles.signupInput, styles.commonsignupStyle]}
placeholder="Nom *"
placeholderTextColor={colors.primaryColor}
/>
</CardView>
<CardView
style={styles.cardStyle}
cardElevation={2}
cardMaxElevation={2}
cornerRadius={5}
>
<TextInput
underlineColorAndroid="transparent"
style={[styles.signupInput, styles.commonsignupStyle]}
placeholder="Prénom *"
placeholderTextColor={colors.primaryColor}
/>
</CardView>
</View>
</ScrollView>
</View>
);
}
}
Edit:
For dynamic height, two lines or more of text, as asked for in the comments, I had to use another workaround.
https://snack.expo.io/7bVXvbmE0
const Label = () => {
return <View style={{width: 100, height: 50}}>
<View style={styles.topView}>
<Text>Hello world</Text>
<Text>Hi world</Text>
</View>
<View style={styles.shadowView} >
<Text style={{color: 'transparent'}}>Hello world</Text>
<Text style={{color: 'transparent'}}>Hi world</Text>
</View>
</View>;
}
Whatever dynamic text you have on the label, duplicate for the shadow label, but make it transparent. That way you are guaranteed that the shadow follows the top view.
Also, get rid of the hardcoded heights in the styles. For both top view and shadow view, their heights are informed by the text input, and the wrapper container's height is informed by the two views.
Lastly, change shadow view style's top to be just a few points above 0 to make sure you it peeks from under topview. You can adjust borderRadius of the shadow view to fit your preferences.
const styles = StyleSheet.create({
topView: {
width: '100%',
position: 'absolute',
top: 0, backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 25,
},
shadowView: {
position: 'absolute',
top: 3,
width: '100%',
zIndex: -10,
borderRadius: 17,
backgroundColor: '#ddd'}
});
Previous Post
A little bit hacky, but you can do this if you absolutely don't want any blur.
https://snack.expo.io/pWyPplcm3
const Label = () => {
return <View style={{width: 100, height: 30}}>
<View style={styles.topView}>
<Text>Hello world</Text>
</View>
<View style={styles.shadowView} />
</View>;
}
styles:
const styles = StyleSheet.create({
topView: {
height: 25,
width: '100%',
position: 'absolute',
top: 0, backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 15,
},
shadowView: {
position: 'absolute',
top: 0,
height: 28,
width: '100%',
zIndex: -10,
borderRadius: 13,
backgroundColor: '#ddd'}
});
I would like to make part of a Touchable traslucid, i.e, to be able to see the background behind it.
A possible hack is to use MaskedView and draw my background again, inside the Touchable, as the children prop to MaskedView. However, this only works for a limited number of scenarios. Here it is working:
However, as soon as I add some margin, for example, things get out of phase:
A couple clarifications, just in case:
My real intention is to use a gradient which goes between opposite corners of the screen. In that case, things don't work even in the simple scenario I presented.
I know why this hack doesn't work, but I haven't been able to come up with anything better
Here is a MWE, using a view instead of an image, so that I don't need to bundle the png file:
import React from 'react';
import {
View,
TouchableOpacity,
} from 'react-native';
import MaskedView from '#react-native-community/masked-view';
import LinearGradient from 'react-native-linear-gradient';
export default function () {
return (
<LinearGradient start={{x: 0, y: 0}} end={{x: 1, y: 0}}
colors={['red', 'blue', 'green']}
style={
{flex: 1,
alignItems: 'stretch',
justifyContent: 'center'}
}>
<TouchableOpacity>
<View style={
{height: 100,
alignItems: 'stretch',
justifyContent: 'center',
backgroundColor: 'white',
borderRadius: 30,
//marginLeft: 50, // -> if you uncomment this line, the translucid effect is ruined
}
}>
<MaskedView
style={{height: '100%', backgroundColor: 'yellow',
alignItems: 'stretch', justifyContent: 'center',
}}
maskElement={
<View style={{flex: 1, backgroundColor: 'transparent',
alignItems: 'center', justifyContent: 'center',
}}>
<View style={{width: 300, height: '100%', backgroundColor: 'black'}}/>
</View>
}
>
<LinearGradient start={{x: 0, y: 0}} end={{x: 1, y: 0}}
colors={['red', 'blue', 'green']}
style={{height: '100%'}}
/>
</MaskedView>
</View>
</TouchableOpacity>
</LinearGradient>
);
}
Here's an expo snack to illustrate my comment: https://snack.expo.io/SkCNR7Iqr
The idea is, rather than rendering and then hiding content, just don't render anything there in the first place. This will render the white ends within the bounds of the button. The wrapper uses overflow: 'hidden' to ensure that the Touchable effect will only appear within the bounded borderRadius (more noticeable with TouchableHighlight), and it will ensure that the white ends and any other content in it will stay within the bounded borderRadius.
import * as React from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native'
import { LinearGradient } from 'expo-linear-gradient'
export default class App extends React.Component {
render() {
return (
<LinearGradient
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
colors={['red', 'blue', 'green']}
style={styles.gradient}>
<View style={styles.wrapper}>
<TouchableOpacity style={styles.touch}>
<View style={styles.end} />
<View style={styles.content} />
<View style={styles.end} />
</TouchableOpacity>
</View>
</LinearGradient>
);
}
}
const styles = StyleSheet.create({
gradient: {
flex: 1,
justifyContent: 'center'
},
wrapper: {
height: 100,
borderRadius: 30,
overflow: 'hidden',
marginLeft: 50,
flexDirection: 'row',
},
touch: {
flexDirection: 'row',
flex: 1,
},
end: {
width: 50,
backgroundColor: 'white',
height: '100%',
},
content: {
flex: 1,
}
});
I would like to know how to apply shadow only to the main outer view. Here on applying shadow, it's getting applied to all the inner elements
The trick to make the shadow props of parent don't inherit to children element, is to set a background color to the component on which you set the shadow. For example that would be:
<View
style={{ backgroundColor: '#fff' }}
shadowOffset={{height: 10}}
shadowColor='black'
shadowOpacity={0.5}
>
<Text>{title}</Text>
</View>
Unfortunately this only works with colored backgrounds – when setting a transparent background with RGBA or 'transparent' is doesn't help.
I cannot really answer based on a simple image, but from my previous experience, setting shadow offset to the required height and width should do the trick for the iOS.
Read more about it here: Shadow Offset
Here's a picture of what my card looks like with the following style used:
marginLeft: 10,
backgroundColor: 'white',
shadowColor: 'blue',
alignItems: 'center',
shadowOffset: {width: 3, height: 3 },
shadowOpacity: 1.0,
borderRadius: 10,
My card View
Hope it works out well for you.
Display custom shadow color >= 28 or >= P for above Sdk level 28
Code
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<View
style={{
shadowColor: 'purple',
height: 150,
width: '90%',
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center',
//android specific
elevation: 10,
//ios specific
shadowOffset: { width: 1, height: 1 },
shadowRadius: 3,
shadowOpacity: 0.5,
}}>
<Text style={{ color: 'rgba(128,0,128,0.5)' }}>
Welcome to React Native
</Text>
</View>
</View>
In android we can adjust shadow by elevation property
In iOS we can adjust shadow by shadowOffset, shadowRadius,shadowOpacity property
Output android
Output iOS
Available library for further usage
react-native-shadow-2
react-native-drop-shadow
Create a Shadow.js
export const Shadow = (elevation) => { // Receive elevation as a prop
return {
elevation,
shadowColor: 'black',
shadowOffset: { width: 0, height: 0.5 * elevation },
shadowOpacity: 0.3,
shadowRadius: 0.8 * elevation,
};
};
import Shadow.js to the page where you want to apply
import Shadow from './Shadow' //path to Shadow.js
<View style={{Shadow(5)}}> // pass elevation as a prop to Shadow.js
</View>
if you want to use in styles
import Shadow from './Shadow' //path to Shadow.js
<View style={styles.shadow}>
</View>
const styles = StyleSheet.create({
shadow:{
...Shadow(5) //use spread operator
}
});
I use React Native 0.55, and I want to display a kind of icon on top of a , like this : http://b3.ms/qpknagGre9BR
For now, I use this :
<View style={styles.cardContainer}>
<Image source={iconPlayers} style={styles.iconTop} />
<View style={styles.cardBox}>
<View style={styles.container}>
<Text style={styles.txtTitle}>
My title
</Text>
</View>
</View>
</View>
And my styles :
cardBox: {
borderRadius: 20,
backgroundColor: "#FFF",
padding: 5,
marginBottom: 10,
borderColor: "#DDD",
elevation: 4,
paddingTop: 40,
},
cardContainer: {
marginTop: 40,
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
txtTitle: {
color: "#000",
textAlign: "center",
fontWeight: "bold",
},
iconTop: {
width: 60,
height: 60,
zIndex: 999,
position: "absolute",
top: -30,
elevation: 4,
alignSelf: "center",
},
It's crazy because now it works, but I have a problem, I can't put elevation: 4 on styles for an <Image /> element, I have a Warning.
If I remove the elevation: 4, of my styles, the image is shown behind the cardBox.
How can I achieve what I want without any warning ... ? Thanks.
** EDIT **
I wrapper the <Image /> in a <View />, and I put elevation property to the wrapper, and it works.
I thought elevation was for boxShadow for android, but it impacts the zIndex.
I am trying your code in snack expo, which uses the latest react-native version (55.4). There is no warning in applying elevation to Image property, it just works fine and the image is also above the card. If your react-native version gives warning just wrap it in a View and apply elevation to that View. Also, remember zIndex gets affected by your Views elevation (only Android). Since you have applied elevation: 4 to card box, you must give elevation >= 4 for your Image component else it will draw beneath the card box.
snack example: https://snack.expo.io/B14UPA9Bm
You can also avoid Zindex by changing the order of components,
// Render the image component after your card box this way you can avoid setting the zIndex
<View style={styles.cardContainer}>
<View style={styles.cardBox}>
<View style={styles.container}>
<Text style={styles.txtTitle}>
My title
</Text>
</View>
</View>
<Image source={iconPlayers} style={styles.iconTop} />
</View>
cardBox: {
borderRadius: 20,
backgroundColor: "#FFF",
padding: 5,
marginBottom: 10,
borderColor: "#DDD",
elevation: 4,
paddingTop: 40,
},
cardContainer: {
marginTop: 40,
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
txtTitle: {
color: "#000",
textAlign: "center",
fontWeight: "bold",
},
iconTop: {
width: 60,
height: 60,
//zIndex: 999, not required now
position: "absolute",
top: -30,
elevation: 4,
alignSelf: "center",
},
Check and tell if it works in Android and iOS. thanks
I'd like to use a background image in my react native app,
the image is smaller than the screen, so I have to stretch it.
but it doesn't work if the image is loaded from asset bundle
var styles = StyleSheet.create({
bgImage: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'stretch',
resizeMode: 'stretch',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
}
});
<Image source={require('image!background')} style={styles.bgImage}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
</Image>
it looks like this:
however, it works fine for a remote image, through source={{uri: 'background-image-uri'}}:
From Github issue: Image {require} is broken in React Native for Resizing, you could try <Image style={{width: null, height: null}}, hope that facebook would fix it soon.
The Image tag should generally not be treated as a container view.
Having an absolutely positioned wrapper containing your (stretched/contained) image appears to work well:
var styles = StyleSheet.create({
bgImageWrapper: {
position: 'absolute',
top: 0, bottom: 0, left: 0, right: 0
},
bgImage: {
flex: 1,
resizeMode: "stretch"
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10
}
});
<View style={{ flex: 1 }}>
<View style={styles.bgImageWrapper}>
<Image source={require('image!background')} style={styles.bgImage} />
</View>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
</View>
You could always use the Dimensions module to get the width of the screen and set your image's width style to that value:
var Dimensions = require('Dimensions');
var {width, height} = Dimensions.get('window');
It also seems strange that a remote image works fine...you can try loading up a local static image with the uri syntax by using source={{uri: 'local_image_file_name' isStatic: true}}.
for me use undefined instead of null value. on typescript environment, it will be prompted not assignable
bgImage: {
flex: 1,
width: undefined,
height: undefined,
resizeMode: 'stretch',
},