Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I automatically scale an SVG element within a React Native View?

I am trying to put a react-native-svg element inside of a View such that it's rendered with a certain, fixed aspect ratio, but then scaled to be as large as possible, within the confines of the containing view.

The Svg element (from react-native-svg) seems to only accept absolute width and height attributes (I've tried using percentages, but nothing renders, and debugging confirms that percent values are NSNull by the time they get to the native view). I'm not sure how to achieve the desired effect. Here's what I've tried so far:

// I have a component defined like this: export default class MySvgCircle extends React.Component {     render() {         return (             <View style={[this.props.style, {alignItems: 'center', justifyContent: 'center'}]} ref="containingView">                 <View style={{flex: 1, justifyContent: 'center', alignItems: 'center', aspectRatio: 1.0}}>                     <Svg.Svg width="100" height="100">                         <Svg.Circle cx="50" cy="50" r="40" stroke="blue" strokeWidth="1.0" fill="transparent" />                         <Svg.Circle cx="50" cy="50" r="37" stroke="red" strokeWidth="6.0" fill="transparent" />                     </Svg.Svg>                 </View>             </View>         );     } }  // And then consumed like this: <MySvgCircle style={{height: 200, width: 200, backgroundColor: "powderblue"}}/> 

And this is what I see when it renders.

this is what I see

I want the red and blue circles to be scaled up to fill the 200x200 area (staying circular if the containing view is rectangular and not square), without having foreknowledge of the size desired by the consumer/user of the component.

As mentioned, I tried using percentages, like this (the rest is the same):

<Svg.Svg width="100%" height="100%"> 

But then the SVG part doesn't draw at all. Like this:

no SVG rendering

There are no error messages, or other indications of why this doesn't work, in the console logs.

The methods for measuring UI elements after layout in RN appears to be asynchronous, which seems like a poor match to what I'm trying to do. Is there some sort of scaling or transform magic that I could use?

The desired output would look like this (obtained by hardcoding values):

desired output

And when the containing view isn't a perfect square I'd like it to work like this:

horizontal rectangle behavior vertical rectangle behavior

like image 289
ipmcc Avatar asked Feb 03 '18 21:02

ipmcc


People also ask

How do I change the size of an SVG React?

You can change the size using CSS transform: scale(2) in <ComponentName /> , which in React can be achieved using className or a global CSS file. Note: To change the color you can use . componentClass path { fill: "color" } , but if you change the scale on .

How do I make SVG Scalable?

Once you add a viewBox to your <svg> (and editors like Inkscape and Illustrator will add it by default), you can use that SVG file as an image, or as inline SVG code, and it will scale perfectly to fit within whatever size you give it. However, it still won't scale quite like any other image.

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.


2 Answers

Here is a component that behaves like your images:

import React from 'react'; import { View } from 'react-native';  import Svg, { Circle } from 'react-native-svg';  const WrappedSvg = () =>   (     <View style={{ aspectRatio: 1, backgroundColor: 'blue' }}>       <Svg height="100%" width="100%" viewBox="0 0 100 100">         <Circle r="50" cx="50" cy="50" fill="red" />       </Svg>     </View>   ); 

In context:

const WrappedSvgTest = () => (   <View>     <View style={{       width: '100%',       height: 140,       alignItems: 'center',       backgroundColor: '#eeeeee'     }}     >       <WrappedSvg />     </View>      {/* spacer */}     <View style={{ height: 100 }} />      <View style={{       width: 120,       height: 280,       justifyContent: 'space-around',       backgroundColor: '#eeeeee'     }}     >       <WrappedSvg />     </View>   </View> ); 

The trick is to wrap the SVG element in a view that preserves its aspect ratio, then set the SVG sizing to 100% width and height.

I believe there is some complex interaction between the SVG element size and the viewbox size that makes the SVG render smaller than you would expect, or in some cases not render at all. You can avoid this by keeping your <View> tags at a fixed aspect ratio and setting the <Svg> tags to 100% width and height, so the viewbox aspect ratio always matches the element ratio.

Be sure to set aspectRatio to viewbox.width / viewbox.height.

like image 76
Elliot Fiske Avatar answered Sep 20 '22 13:09

Elliot Fiske


the trick in

  preserveAspectRatio="xMinYMin slice"  

you should do that

<Svg   height="100%"   preserveAspectRatio="xMinYMin slice"   width="100%"   viewBox="0 0 100 100" >   
like image 37
Abanoub Istfanous Avatar answered Sep 17 '22 13:09

Abanoub Istfanous