Custom shape for border radius image in React-Native - css

I want to reproduce that style in my component (the border bottom).
I am basically loading a rectangular image, then I want to rounded it at the bottom.
I think about :
border-bottom-right-radius and left, but it is not accurate with the custom shape of the screen.
transform scaleX, but it scale the image proportion (obviously)
create a white image with the custom shape and load it at the bottom of the screen, but I want to add a shadow at the bottom of the image... With a white image I could not do that...
Is there a way to do that in a proper style with React-Native ?
Thank you !

This is possible, but it's a little tricky. The idea is you need to create a "mask" of sorts that you can apply the shape to. After doing that you can put the image as a child of the mask element so that it will essentially mask the area you want. It's probably possible to do it with a dynamic size, but I didn't take the time to come up with that solution, i'll leave that to you ;)
Hopefully this'll get you going in the right direction though.
First lets setup the app structure
class App extends Component {
render() {
return (
<View style={styles.app}>
<Mask />
</View>
);
}
}
Pretty straightforward, just a basic app with a mask component. I made it a component so you can pass props to it in the future (like the image uri for instance).
Then the mask component
const logoUri = `http://66.media.tumblr.com/86b941b3445b80a518ea51208f48ab35/tumblr_ntpi99a6Pl1uounv1o1_500.png`;
const Mask = (props) => (
<View style={styles.maskContainer}>
<View style={styles.mask}>
<Image
source={{ uri: logoUri }}
style={styles.img}
/>
</View>
</View>
)
The maskContainer is a positioning element to help center the image.
The mask uses the oval style approach but to get the edges to not round like border radius, we have to scale it 2x
The img style needs to reverse the scaling so that the image itself is not warped :)
const styles = StyleSheet.create({
app: {
marginHorizontal: "auto",
maxWidth: 500,
backgroundColor: "#e0e0e0",
width: 700,
height: 700
},
mask: {
width: 200,
height: 470,
borderBottomLeftRadius: 100,
borderBottomRightRadius: 100,
overflow: "hidden",
transform: [{ scaleX: 2 }]
},
img: {
height: 470,
width: 299,
left: 25,
position: "absolute",
transform: [{ scaleX: 0.5 }, { translate: "-50%" }]
},
maskContainer: {
position: "absolute",
left: "50%",
transform: [{ translate: "-50%" }]
}
});
See it working on this fiddle!

#John Ruddell's answer is very helpful.
I am able to design this by creating a mask and applied through SVG path. So, you need to install 2 libraries.
react-native-svg (expo install react-native-svg)
react-native-masked-view (npm install --save #react-native-community/masked-view)
Following code respect the aspect ratio of image. So, you need the set the aspect ratio of your image and adjust the value of curveAdjustment in order to achieve your desired sharpness of curve.
import React from "react";
import {
Image,
View,
StyleSheet,
Text,
useWindowDimensions,
} from "react-native";
import MaskedView from "#react-native-community/masked-view";
import Svg, { Path } from "react-native-svg";
const logoUri = `http://66.media.tumblr.com/86b941b3445b80a518ea51208f48ab35/tumblr_ntpi99a6Pl1uounv1o1_500.png`;
function SplashScreen(props) {
const windowWidth = useWindowDimensions().width;
const imageAspectWidth = 375;
const imageAspectHeight = 332;
const curveAdjustment = 40;
const maskHeight = (imageAspectHeight / imageAspectWidth) * windowWidth;
const scaleFactor = imageAspectWidth / imageAspectHeight;
const scaledHeight = scaleFactor * maskHeight;
const controlPointX = windowWidth / 2.0;
const controlPointY = scaledHeight + curveAdjustment;
const curveCenterPointY = (controlPointY - maskHeight) / 2;
return (
<View style={styles.main}>
<MaskedView
style={[
styles.mask,
{
height: controlPointY - curveCenterPointY,
},
]}
maskElement={
<Svg height="100%" width="100%">
<Path
d={`M0 0 L${windowWidth} 0 L${windowWidth} ${maskHeight} Q${controlPointX} ${controlPointY} 0 ${maskHeight} Z`}
fill={"#fff"}
/>
</Svg>
}
>
<Image source={{ uri: logoUri }} style={styles.image} />
</MaskedView>
<Text>{"Tag line"}</Text>
</View>
);
}
const styles = StyleSheet.create({
image: {
flex: 1,
resizeMode: "stretch",
},
main: {
flex: 1,
},
mask: {
backgroundColor: "orange",
width: "100%",
},
});
export default SplashScreen;
Final result

Yep. clip-path property!
Just add:
clip-path: circle(69.3% at 50% 30%)
To your class and it will work.
if you want to create it yourself, there's a generator here:
https://bennettfeely.com/clippy/

Related

How to use #media to change width in prop styled components

I am not sure if what they called but I have a component which takes its style as an object with its props.
const PricingSection = ({
secDesc,
}) => {
return (
<Text
{...secDesc}
content={intl.formatMessage({ id: 'packages.description' })}
/>
);
};
PricingSection.propTypes = {
secDesc: PropTypes.object
};
PricingSection.defaultProps = {
secDesc: {
width: '50%',
m: 'auto',
textAlign: 'center',
pt: '20px',
color: '#6a7a8d',
lineHeight: '1.5rem',
},
}
I want to apply different witdh for mobile devices. I know how to use #media tag in css but I dont know where to write #media in this component or how achieve what I want.
Instead of passing style object, it would be better if you apply a class to the component for easy maintenance. It'll also solve your problem for width for different size devices as you can add media query for the class in its css file.
Another suggestion would be to use styled-components. They provide great support for adding media queries inside the component file.
Refer:styled-components
you can use 'react-device-detect' and pass the different width from parent component like this:
enter code here
import isMobile from 'react-device-detect'
secDesc: {
width: isMobile ? '50%' : 'your value',
m: 'auto',
textAlign: 'center',
pt: '20px',
color: '#6a7a8d',
lineHeight: '1.5rem',
}
< PricingSection
secDesc={secDesc}
/>

How to Make View Filled in at Certain Intervals?

I am currently attempting to create a bar that fills in based on arrays of numbers that are fed to it out of 1440 (for minutes in a day). For instance, if [3, 400] was inputted, the view would fill in the area corresponding to 3-400 minutes with some color.
I'm not sure where to start here, since the only idea I have had is to fill the parent view with 1440 other views which I can then fill based on the input, which certainly would not be a great idea. Any help would be much appreciated.
I think I figured it out. This was my solution:
import React from 'react'
import { StyleSheet, View, Text } from 'react-native'
export default function TimeAvailabilityIndicator() {
const testArray = [[130, 210], [1000, 1300], [500, 800]]
const minuteToPercentageConverter = (minute) => {
return (minute * 100) / 1440
}
return (
<View style={styles.parentView}>
{testArray.map(time => {
return <View
style={{
position: 'absolute',
left: minuteToPercentageConverter(time[0]) + '%',
right: (100 - minuteToPercentageConverter(time[1])) + '%',
height: 16,
backgroundColor: 'red'
}}
/>
})}
</View>
)
}
const styles = StyleSheet.create({
parentView: {
borderColor: 'black',
borderWidth: 2,
height: 20,
marginTop: 10,
position: 'relative'
}
})

How to use percentage padding top and bottom in React Native?

I am trying to achieve dynamic padding top and bottom. I am aware that paddingTop and paddingBottom with % will use the width of the container and I am fine with that.
Unfortunately when I do set the paddingTop to any value with %, it sets the paddingBottom to the same value. Basically paddingTop: '5%', paddingBottom: '0%' will give me equal 5% paddings on both sides.
If I set paddingBottom to any % value - it's just being added to the value that came form the top. So: paddingTop: '5%', paddingBottom: '10%' results in paddingBottom equal to 15%...
I checked the same solution in a web project and there it works as expected.
The snack presenting the problem:
https://snack.expo.io/BJ9-2t8LB
The problem is on both Android and iOS.
How to solve it?
Apply the value to the child container to be applied, not to the parent container.
I have modified the answer according to the conditions you want. You should send Bottom's territory to the parent's container. Because they interact with each other in their child's container, they must be independent.
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
export default class App extends React.Component {
render() {
return (
<View style={styles.wrapper}>
<View style={styles.inner}>
<View style={{ backgroundColor: 'yellow' }}>
<Text>TOP</Text>
</View>
</View>
<View style={{ backgroundColor: 'red', }}><Text>BOTTOM</Text></View>
</View>
);
}
}
const styles = StyleSheet.create({
wrapper: {
flex:1,
paddingLeft: 24,
paddingRight: 24,
backgroundColor: 'green',
paddingBottom:"5%"
},
inner: {
flex:1,
justifyContent: 'space-between',
backgroundColor: 'blue',
marginTop:"10%"
},
});
That is strange behavior, you should bring it up as an issue to react-native.
In the meantime, a workaround would be applying marginTop and marginBottom to the inner View, instead of paddingTop and paddingBottom to the wrapper View.
const styles = StyleSheet.create({
ayz:{
marginTop:"15%",
backgroundColor:'pink',
alignItems:'center',
justifyContent:'center',
},
asd:{
color:'white',
fontSize:50,
fontWeight:'bold'
}
});

Center items with aspect ratio in FlatList - React Native

This seems like an easy question at first but it's actually tricky.
Better to go directly to the example. I've created a snack with the sample code here https://snack.expo.io/BkSNtNrWV
I want to have a list of items with given aspect ratio (say 3:2) and the items should take as much space as possible with a maximum limit on size. This sample code does it:
<View>
<FlatList style={{backgroundColor:'lightgray'}}
data={[{key: 'a'},{key: 'b'}]}
renderItem={({item}) =>
<View style ={styles.pink}></View>
}/>
</View>
const styles = StyleSheet.create({
pink: {
backgroundColor: "#A37E93",
maxHeight: 150,
aspectRatio: 3/2,
borderWidth: 1,
}});
And this is the result:
However, the problem is that I would like to have the items aligned to the center. I tried to wrap the list item in a flexbox with 'row' direction but that caused the item to have 0 height => not displayed. Justify content didn't help either (it's possible that I do it incorrectly).
Does anyone know how to solve this please?
Updated the code. Add flex to inner item and wrap the view inside another with screen width.
import * as React from 'react';
import { Text, View, StyleSheet,FlatList,Dimensions } from 'react-native';
import { Constants } from 'expo';
const{width} = Dimensions.get('window')
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
export default class App extends React.Component {
render() {
return (
<View>
<FlatList style={{backgroundColor:'lightgray'}}
data={[{key: 'a'},{key: 'b'}]}
renderItem={({item}) =>
<View style={styles.item}>
<View style ={styles.pink}></View>
</View>
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
item:{
width:width,
height:150,
alignItems:'center'
},
pink: {
flex:1,
backgroundColor: "#A37E93",
maxHeight:150,
aspectRatio:3/2,
borderWidth:1,
}
});

React-Native: Margin with percentage value

I'm trying to use percentage value for margin style attribute on my React Native project but it seems to reduce the height of one of my View component. If I replace percentage value by an absolute value, there is no more issue and it works fine. Did you try to use percentage value as margin in React Native ? Here is a little sample of code to reproduce this issue:
import React, { Component } from 'react';
import { Text, View, StyleSheet } from 'react-native';
export default class App extends Component {
render() {
return (
<View style={styles.scene}>
<View style={styles.card}>
<View style={styles.container}>
<Text>Hello World</Text>
</View>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
scene: {
backgroundColor: '#F9E8D5',
flex: 1,
justifyContent: 'flex-start',
alignItems: 'center',
flexDirection: 'column'
},
card: {
backgroundColor: '#E6D5C3',
flex: 0.2,
flexDirection: 'column',
marginTop: '20%' // Replace by 20
},
container: {
backgroundColor: '#FFFFFF',
flex: 1,
flexDirection: 'column',
justifyContent: 'center'
}
});
Thank you very much !
A component's height and width determine its size on the screen.
The simplest way to set the dimensions of a component is by adding a fixed width and height to style. All dimensions in React Native are unitless, and represent density-independent pixels.
Setting dimensions this way is common for components that should always render at exactly the same size, regardless of screen dimensions.
Use flex in a component's style to have the component expand and shrink dynamically based on available space. Normally you will use flex: 1.
The following references will be use for styling
https://facebook.github.io/react-native/docs/height-and-width.html
https://medium.com/the-react-native-log/tips-for-styling-your-react-native-apps-3f61608655eb
https://facebook.github.io/react-native/docs/layout-props.html#paddingtop
Use Dimensions to calculate to get the height and width of the screen.
var {height, width} = Dimensions.get('window');
To specify percentage by getting the screen size and convert it into percentage.
Sample example:
const { height } = Dimensions.get('window');
paddingTop: height * 0.1 // 10 percentage of the screen height
This is a real bug but Facebook does not care.
https://github.com/facebook/react-native/issues/19164

Resources