Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find correct values for width, height and viewBox with react-native-svg

I have been trying to do something that seemed easy, but I have been trying for a few hours and I can't find the solution.

I have an SVG that needs to be on top of a screen. It came from the designer with these dimensions:

<Svg width="354px" height="190px" viewBox="0 0 354 190">...</Svg>

In React Native, that would go inside of a container, and the SVG needs to take the full width of the screen, which I am taking from:

Dimensions.get("window").width

My problem is, I haven't found a way to scale the SVG to take 100% of the screen width, finding out the correct height (or a way for it to be set automatically), and preserve the aspect ratio. I've tried like a million things, including playing around with the container's aspectRatio style and its height (or not setting the height at all). Whenever I've found some "proportions" that worked, I tried in a different device with different screen width and it didn't look good at all (cropped, smaller than the screen's width, etc).

I feel like the preserveAspectRatio property in the SVG (react-native-svg) is somehow conflicting with the aspectRatio style. And I am totally lost with the preserveAspectRatio, I haven't found a way to make it scale without being cropped.

Does anyone have any idea how to achieve this?

This is my final code, which returns a HeatMap component showing an SVG, but although it has the correct height, part of the SVG is out of the screen from the right (looks cropped because it's too wide):

const windowWidth = Dimensions.get("window").width;

const getSVGRootProps = ({ width, height }) => ({
  width: "100%",
  height: "100%",
  viewBox: `0 0 ${width} ${height}`,
  preserveAspectRatio: "xMinYMin meet",
});

const FieldShape = () => {
  const width = 354; // Original width
  const height = 190; // Original height
  const aspectRatio = width / height;
  // adjusted height = <screen width> * original height / original width
  const calculatedHeight = (windowWidth * height) / width;
  const fieldStyles = {
    width: windowWidth,
    height: calculatedHeight,
    aspectRatio,
  };

  return (
    <View style={fieldStyles}>
      <Svg {...getSVGRootProps({ windowWidth, calculatedHeight })}>
      ...
      </Svg>
    </View>
  );
};

const HeatMap = () => {
  return <FieldShape />;
};

This is the result:

Result picture (pixelation intended)

like image 614
Luis Serrano Avatar asked May 07 '20 12:05

Luis Serrano


People also ask

How check SVG width and height?

To get width and height of SVG element with JavaScript, we can use the getBoundingClientRect method. const el = document. getElementById("yourElement"); const rect = el. getBoundingClientRect(); console.

How do you find the width and height in React Native?

state. view2LayoutProps. height + 1; const newLayout = { height: newHeight , width: width, left: x, top: y, }; this. setState({ view2LayoutProps: newLayout }); } render() { return ( <View style={styles.

How do you give the height and width to an image in React Native?

So all we need to do is create a View with width: "100%" , then use onLayout to get the width of that view (i.e. the container width), then use that container width to calculate the height of our image appropriately.

What is viewBox in React Native SVG?

The viewBox attribute defines the position and dimensions, in user space, of an SVG viewport. The value of the viewBox attribute is a list of four numbers min-x, min-y, width and height, separated by whitespace, which specify a rectangle in user space. This rectangle is mapped to the bounds of the viewport.


1 Answers

I've found the solution, and I am posting it here in case anyone runs into the same problem with react native and SVG. Basically, if you're trying to get an SVG file and turn it into a component with "dynamic" parts (like programmatically set colors to path based on data, or display SVG text), you'll probably run into this issue.

What I did was to use SVGR to convert the original SVG into a react native component (with react-native-svg). Then I just replaced hardcoded data with variables (from props) as needed. It looked good, but I had a problem with the component's size. I couldn't find a consistent way to display it across different device sizes and resolutions. It seemed easy, but I tried for hours and the results were different on each screen size. After asking here and opening an issue on react-native-svg repo, I got no answers and no clues (not blaming anyone, just saying it was maybe not something a lot of people runs into). So I digged and digged and I finally found this post by Lea Verou, where she talked about absolute and relative SVG paths. That made me think that maybe I was having so many issues trying to find the perfect resizing formula because my paths weren't relative, but absolute. So I tried this jsfiddle by heyzeuss, pasting my (original) SVG code, and then copying the results. I pasted the results into this handy SVGR playground (SVG to JS) tool, and then I changed some bits to achieve my goal:

  • I want my SVG to take the full screen's width, width its height scaled accordingly.

So this is what I changed:

// SVG's original size is 519 width, 260 height
// <Svg width="519" height="260" viewBox="0 0 519 260">...</Svg>
// I also added a container, which enforces the aspect ratio
const originalWidth = 519;
const originalHeight = 260;
const aspectRatio = originalWidth / originalHeight;
const windowWidth = Dimensions.get("window").width;
return (
    <View style={{ width: windowWidth, aspectRatio }}>
        <Svg 
            width="100%" 
            height="100%" 
            viewBox={`0 0 ${originalWidth} ${originalHeight}`}>
        ...
        </Svg>
    </View>
)

I learned some things while doing this, for example, that there's a @svgr/cli included in create-react-app and also available in my react-native project without installing anything extra, so it must be bundled with the original dependencies too. You can run this command and it'll turn a single file or all files in a folder from .svg to React components:

npx @svgr/cli [-d out-dir] [--ignore-existing] [src-dir]

The script used to transform absolute paths to relatives is part of this library called Snap.svg, but you'll only need like a 1% of it (Snap.path.toRelative). I am thinking of having a small tool that would take all the paths in an svg file, and apply this transformation. To be totally honest, I have been dealing with SVG files for years but I never really had a proper look at how it works internally, the format of the paths, coordinates, and so on, so this has been highly instructive :)

I hope this helps you if you find yourself in the same situation!

like image 134
Luis Serrano Avatar answered Nov 15 '22 07:11

Luis Serrano