99

I have a query regarding tag. I want an image to take entire width of parent which I do using alignSelf:stretch, but I also want the height to be according to the aspect ratio of the image. How can I achieve something like this?

So I want a way to specify the height as a ratio of the width of the Image.

Param Aggarwal
  • 2,459
  • 2
  • 17
  • 15

17 Answers17

106

Use style={{ aspectRatio: 3/2 }} for a horizontal image with width to height ratio of 3:2.

Docs: https://reactnative.dev/docs/layout-props#aspectratio

(Available in RN 0.40+)

karel
  • 5,489
  • 46
  • 45
  • 50
Param Aggarwal
  • 2,459
  • 2
  • 17
  • 15
  • 2
    Beautiful. I did this along with `flex: 1` with a container set to `flexDirection: 'row'`, and I was able to get my images to stretch 100% the width of the containing element with preserved aspect ratios. Thank you. – HartleySan Sep 17 '19 at 21:42
104
<Image
   source={require('../../assets/img/headers/image-1.jpg')}
   style={styles.responsiveImage}
 />

const styles = StyleSheet.create({

  responsiveImage: {
    width: '100%',
    // Without height undefined it won't work
    height: undefined,
    // figure out your image aspect ratio
    aspectRatio: 135 / 76,
  },

});
David Schumann
  • 13,380
  • 9
  • 75
  • 96
Stirner
  • 1,207
  • 1
  • 8
  • 6
36

I like bdv's approach and I use this kind of images almost everywhere in my app. That's why I created an own component which is using onLayout to also support device rotation.

import resolveAssetSource from "resolveAssetSource";
import React, { useCallback, useState } from "react";
import { Image, View } from "react-native";

export default function FullWidthImage(props) {
  const [width, setWidth] = useState(0);
  const [height, setHeight] = useState(0);

  const onLayout = useCallback((event) => {
    const containerWidth = event.nativeEvent.layout.width;

    if (props.ratio) {
      setWidth(containerWidth);
      setHeight(containerWidth * props.ratio);
    } else if (typeof props.source === "number") {
      const source = resolveAssetSource(props.source);

      setWidth(containerWidth);
      setHeight(containerWidth * source.height / source.width);
    } else if (typeof props.source === "object") {
      Image.getSize(props.source.uri, (w, h) => {
        setWidth(containerWidth);
        setHeight(containerWidth * h / w);
      });
    }
  }, [props.ratio, props.source]);

  return (
    <View onLayout={onLayout}>
      <Image
        source={props.source}
        style={{ width, height }} />
    </View>
  );
}

You can use it like this:

<FullWidthImage source={{ uri: "http://example.com/image.jpg" }} />
<FullWidthImage source={require("./images/image.jpg")} />

Or if you know the ratio like this:

<FullWidthImage source={{ uri: "http://example.com/image.jpg"}} ratio={0.5} />
<FullWidthImage source={require("./images/image.jpg")} ratio={0.5} />
tomatentobi
  • 3,119
  • 3
  • 23
  • 29
  • Thanks for making it easy to copy/paste the component – Petr Peller Aug 12 '17 at 10:39
  • 3
    What if source of Image need to be static image resource, using `require` like `require('../../some.png')`? – Halt Aug 25 '17 at 15:37
  • I have been using this approach since many months. This works fine, But it slows down the rendering of image. An image rendering with this approach takes more time (almost double) than an image rendering with hardcoded width-height (Test in 2G network/slow network for more noticeable difference). In need of best solution. – Kalpesh Wadekar Sep 13 '17 at 10:16
  • @KalpeshWadekar I've added a `ratio` attribute to my component. This should solve your problem. – tomatentobi Oct 13 '17 at 12:46
  • 1
    @Halt you can use Image.resolveAssetSource(this.props.source). Reference: https://stackoverflow.com/questions/41997611/is-it-possible-to-using-image-getsize-with-static-image-file – Guto Marrara Marzagao Aug 01 '18 at 22:14
  • @Halt I've updated the component to support static images resource. – tomatentobi Dec 04 '18 at 10:40
  • On larger screens (in height) this solution leaves a blank space at the bottom (in portrait mode). any idea? – myh34d Apr 18 '19 at 08:45
17

It's actually pretty simple.

The Image class has a getSize method. [1]

Let's say that you've created a component for your aspectRatioImage and you calculate the appropriate values every time componentWillMount fires.

Then your code would look something like this:

componentDidMount() {
    Image.getSize(this.props.source.uri, (srcWidth, srcHeight) => {
      const maxHeight = Dimensions.get('window').height; // or something else
      const maxWidth = Dimensions.get('window').width;

      const ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
      this.setState({ width: srcWidth * ratio, height: srcHeight * ratio });
    }, error => {
      console.log('error:', error);
    });
  }

So now that the image height and width are saved in your component's state, you can just run

 <Image
   style={{ width: this.state.width, height: this.state.height }}
   source={this.props.source}
   resizeMode="cover"
 />

[1] - https://facebook.github.io/react-native/docs/image.html#getsize

David Schumann
  • 13,380
  • 9
  • 75
  • 96
bdv
  • 1,154
  • 2
  • 19
  • 42
  • I edited your post so I could remove my downvote, which I did. I didn't think that the image dimensions were properly state (still not sure about that) but saw that the docs suggested this approach. – Matt Parrilla Oct 26 '16 at 15:19
  • @MattParrilla it depends a little on what you will be using it for. I can imagine you want to use this to 'scale' an image until it hits it's bounds somewhere on the screen. In that case state will do fine. If it's to display a static image, then it would make more sense to pass it as props I think. – bdv Oct 27 '16 at 11:18
  • 1
    This method work well if the image is still on the screen. I have encountered issue where the image getSize is still being process, the user swap to other screen, the setState now setting to a value to a component that is unMounted. I was hoping the Image component itself have a much cleaner way to scale the height according to the width of it parent. The solution above work really well for a fix image location. it causing so much warnings when we implement the image resizing dynamically in a FlatList where the component unmount any item that is not on the screen to reduce the memory footprint – samw2k00 Jun 27 '17 at 01:20
  • the only valid answer – no_fate Nov 10 '21 at 19:05
6

You can calculate the image height based on the width/height ratio.

So if the image originally is 200x100, after setting its resizeMode to stretch:

var deviceWidth: Dimensions.get('window').width;

...

myImage {
    width: deviceWidth,
    height: deviceWidth * 0.5
}

I know maybe it is not the best practice, but it helped me a lot with images of all sizes that needed to mantain a certain relation with other images, etc.

David Schumann
  • 13,380
  • 9
  • 75
  • 96
Camila
  • 89
  • 1
  • 9
6

In my case i also had to set height to 'auto' :

{
    width: 200,
    height: 'auto',
    aspectRatio: 16 / 9,
}
renas
  • 69
  • 1
  • 3
5

Typically, doing the following would give us an image rendered to max width/height depending on orientation while maintaining the aspect ratio of the image itself:

render(){
    return(<Image style={{'width': Dimensions.get('window').width, 
                         'height': Dimensions.get('window').height}}
                  resizeMode='contain'
                  source='[URL here]'
           </Image>);
}

Using 'contain' with resizeMode: Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or less than the corresponding dimension of the view (minus padding).

Update: * Unfortunately, it seems that there is a common bug with resizeMode's 'contain' specifically when using react native for Android: https://github.com/facebook/react-native/pull/5738*

David Schumann
  • 13,380
  • 9
  • 75
  • 96
antihero989
  • 486
  • 1
  • 10
  • 32
  • 1
    This gives a tonne of whitespace above & below the image? (on iOS) – Matt The Ninja Apr 17 '16 at 20:17
  • 1
    the solution propose above just trying to fit the image to the whole screen which is not what the question is all about. The question is related to how to resize the image with the same aspect ratio as the original image into the given width – samw2k00 Jun 27 '17 at 01:25
5

You can use react-native-scalable-image. The following example will do the job:

import React from 'react';
import { Dimensions } from 'react-native';
import Image from 'react-native-scalable-image';

const image = <Image width={Dimensions.get('window').width} source={{uri: '<image uri>'}} />;
David Schumann
  • 13,380
  • 9
  • 75
  • 96
Ihor Burlachenko
  • 4,689
  • 1
  • 26
  • 25
3

With resizeMode='contain' and flex=1 I get the image in full width while keeping the aspect ratio.

  <Image
     source={{ uri: 'URI' }}
     resizeMode="contain"
     style={{flex:1}} />

The image need to be in a container View with flex or height defined, so the flex of the image can work.

B. Mohammad
  • 2,152
  • 1
  • 13
  • 28
2
const RespImage = ({ offer }) => {

const [height, setHeight] = useState(0);
const [width, setWidth] = useState(0);

    let image_url = `YOUR_IMAGE_URI`;

    Image.getSize(image_url, (srcWidth, srcHeight) => {

        const maxHeight = Dimensions.get('window').height;
        const maxWidth = Dimensions.get('window').width;

        const ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
        setHeight(srcHeight * ratio);
        setWidth(srcWidth * ratio);
    });

    return (
        <View>
            <Image resizeMode={'contain'} style={{ height: height, width: width, marginBottom: 20, resizeMode: "contain" }}
                source={{ uri: image_url }}
            />
        </View>
    )

}

  • Works like a charm! And quote from the [docs](https://reactnative.dev/docs/image#getsize) "In order to retrieve the image dimensions, the image may first need to be loaded or downloaded, after which it will be cached. " – khoroshevj Dec 10 '21 at 11:00
1

In my case, I am using Styled Components in my React Native (v0.62+) project.

I needed to specify a square aspectRatio for Image components that had a defined width and undefined height.

I found that styling height:0; achieved the "square image" result that I wanted:

// Gallery container styled-component
const Gallery = styled.View`
  flexDirection:row;
  flexWrap:wrap;
`

// Square half-width image styled-component
const Photo = styled.Image`
  width:50%;
  height:0;
  aspectRatio:1;
`

This method also works for full width image styling - replacing width:50% with width:100% produces the expect result with correct aspect ratio of each image.

Dacre Denny
  • 29,664
  • 5
  • 45
  • 65
0

I tried the Image.getSize approach, but had problems, since we gather all the image links in a config file and then pass the ImageURISource into the source prop of the Image.

My solution for that was to wait for the Image onLayout callback to get it's layout properties and use that to update the dimensions. I created a component for that:

import * as React from 'react';
import { Dimensions, Image, ImageProperties, LayoutChangeEvent, StyleSheet, ViewStyle } from 'react-native';

export interface FullWidthImageState {
  width: number;
  height: number;
  stretched: boolean;
}

export default class FullWidthImage extends React.Component<ImageProperties, FullWidthImageState> {
  constructor(props: ImageProperties) {
    super(props);

    this.state = { width: 100, height: 100, stretched: false };
  }

  render() {
    return <Image {...this.props} style={this.getStyle()} onLayout={this.resizeImage} />;
  }

  private resizeImage = (event: LayoutChangeEvent) => {
    if (!this.state.stretched) {
      const width = Dimensions.get('window').width;
      const height = width * event.nativeEvent.layout.height / event.nativeEvent.layout.width;
      this.setState({ width, height, stretched: true });
    }
  };

  private getStyle = (): ViewStyle => {
    const style = [StyleSheet.flatten(this.props.style)];
    style.push({ width: this.state.width, height: this.state.height });
    return StyleSheet.flatten(style);
  };
}

This will update the dimensions of the image to match the width of the screen.

David Schumann
  • 13,380
  • 9
  • 75
  • 96
gtRfnkN
  • 489
  • 7
  • 19
0

Im my case on RN 0.68.0 on iOS 16 I had to do this in order to make the image show up properly in proper aspect ratio and fill the available width.

Setting aspect ratio directly on the <Image> element did not work.

imageSource is an object made from Image.getSize() i.e. {uri, width, height}

// ....
{imageSource && (
        <View
          style={[
            styles.imageContainer,
            {
              aspectRatio:
                (imageSource.width ?? 1) / (imageSource.height ?? 1),
            },
          ]}>
          <Image source={imageSource} style={styles.image} />
        </View>
      )}
// ...


const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingHorizontal: 2 * spacing,
  },
  imageContainer: {},
  image: {
    flex: 1,
    width: '100%',
  },
});

Esben von Buchwald
  • 2,772
  • 1
  • 29
  • 37
0

let's create a component like this:

const ImageCustom = ({uri}) => {
const [ratio, setRatio] = useState(0);
  Image.getSize(
    uri,
    (width, height) => {
      setRatio(width / height);
    },
    error => {
      console.log('error:', error);
    },
  );
  return (
    <Image
      source={{uri: uri}}
      style={{width: '100%', height: undefined, aspectRatio: ratio}}
    />
  );
};

And call it like this

<ImageCustom uri={item}/>
Tăng Du
  • 13
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Gugu72 Jul 18 '23 at 20:30
-1

Give aspectRatio and width to the parent view and add width and height 100% to the Image If you want an image with width of 100 and height of 50

<View style={{ width: 100, aspectRatio: 2 }}>
  <Image 
     source={{ uri: '' }}
     style={{
       width: '100%',
       height: '100%'
     }}/>
</View>
  • The question specifies that the width of parent is not available so answer would not be valid according to that situation. – Jagjot Jun 15 '22 at 12:31
-3

Use resizeMode='contain'

<Image style={{height:'100%', width:'100%'}} resizeMode="contain" source={{uri:this.state.imgSource}} />

This will keep the original aspect ratio, with given width and height as max-height and max-width.

Taazar
  • 1,545
  • 18
  • 27
Soni Kamal
  • 20
  • 2
-9

You may use something like this:

<View style={{height:200}} >
<Image source={require('image!MyImage') style={{ resizeMode:Image.resizeMode.ratio, flex:1 }}} />
</View>

Please not that you still have to set the height on the view container.

David Schumann
  • 13,380
  • 9
  • 75
  • 96
  • 9
    There is no `resizeMode.ratio` as per https://github.com/facebook/react-native/blob/master/Libraries/Image/ImageResizeMode.js, so didn't work for me. – Param Aggarwal Apr 17 '15 at 05:42