GuillaumeRZ
GuillaumeRZ

Reputation: 2943

Custom shape for border radius image in React-Native

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.

enter image description here

I think about :

Is there a way to do that in a proper style with React-Native ?

Thank you !

Upvotes: 1

Views: 8132

Answers (3)

Muhammad Zeeshan
Muhammad Zeeshan

Reputation: 2451

@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.

  1. react-native-svg (expo install react-native-svg)
  2. 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

Screenshot

Upvotes: 0

John Ruddell
John Ruddell

Reputation: 25862

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!

Upvotes: 5

Ben Cohen
Ben Cohen

Reputation: 1410

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/

Upvotes: -2

Related Questions