Param Aggarwal
Param Aggarwal

Reputation: 2729

Maintain aspect ratio of image with full width in React Native

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.

Upvotes: 106

Views: 176845

Answers (18)

Nuno Monteiro
Nuno Monteiro

Reputation: 9

I created a custom component to adjust image's size based on a restriction, height or width, while maintaining the original aspect ratio, for images that can vary in its size and aspect ratio.

The issue i was encountering was I needed the image to take up to 60px in height and wanted to keep AR but the component (a card) can take images without standardised AR.

Not sure if that was the issue you were encountering. Using Image component from react-native I could achieve that by adding height: 60, width:140 in Image's style but that way the image would be centered which wasn't what I needed.

So I came up with this component and it worked perfectly:

import React, { useState, useEffect } from 'react';
import { Image } from 'react-native';

const DynamicImage = ({
  picture_url,
  sizeRestriction,
  restrictionType,
  setIsLoading,
}) => {
  const [imgWidth, setImgWidth] = useState(sizeRestriction);
  const [imgHeight, setImgHeight] = useState(sizeRestriction);
  const [isLoadingImage, setIsLoadingImage] = useState(true);

  useEffect(() => {
    if (picture_url) {
      Image.getSize(
        picture_url,
        (width, height) => {
          restrictionType === 'height' &&
            setImgWidth(Math.floor(sizeRestriction * (width / height)));

          restrictionType === 'width' &&
            setImgHeight(Math.floor(sizeRestriction * (height / width)));
          setIsLoadingImage(false);
        },
        () => {
          console.error(
            'Unable to fetch image dimensions. Using default values.',
          );
          setIsLoadingImage(false);
        },
      );
    } else {
      setIsLoadingImage(false);
    }
  }, [picture_url]);

  if (isLoadingImage) {
    return null; // or render a loading indicator
  }

  return (
    <Image
      resizeMode={'contain'}
      source={{ uri: picture_url }}
      style={{
        width: imgWidth,
        height: imgHeight,
      }}
      onLoadEnd={() => setIsLoading(false)}
    />
  );
};

export default DynamicImage;

Usage for fixed height of 60px:

<DynamicImage
   picture_url={item.picture_url}
   sizeRestriction={60}
   restrictionType={'height'}
   setIsLoading={setIsLoading}
/>

Hope this can help someone looking for the same issue I was having.

Upvotes: 0

Tăng Du
Tăng Du

Reputation: 13

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}/>

Upvotes: 0

Esben von Buchwald
Esben von Buchwald

Reputation: 3049

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%',
  },
});

Upvotes: 0

Dheeraj Gour
Dheeraj Gour

Reputation: 36

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>

Upvotes: -1

tomatentobi
tomatentobi

Reputation: 3157

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} />

Upvotes: 37

B. Mohammad
B. Mohammad

Reputation: 2464

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.

Upvotes: 5

Param Aggarwal
Param Aggarwal

Reputation: 2729

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+)

Upvotes: 115

Akshay Khandarkar
Akshay Khandarkar

Reputation: 81

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>
    )

}

Upvotes: 2

Dacre Denny
Dacre Denny

Reputation: 30360

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.

Upvotes: 2

Soni Kamal
Soni Kamal

Reputation: 20

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.

Upvotes: -3

renas
renas

Reputation: 79

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

{
    width: 200,
    height: 'auto',
    aspectRatio: 16 / 9,
}

Upvotes: 7

bdv
bdv

Reputation: 1204

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

Upvotes: 18

Camila
Camila

Reputation: 89

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.

Upvotes: 6

Ihor Burlachenko
Ihor Burlachenko

Reputation: 4905

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>'}} />;

Upvotes: 5

Stirner
Stirner

Reputation: 1285

<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,
  },

});

Upvotes: 111

antihero989
antihero989

Reputation: 494

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*

Upvotes: 5

gtRfnkN
gtRfnkN

Reputation: 489

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.

Upvotes: 0

Lukas Reichart
Lukas Reichart

Reputation: 111

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.

Upvotes: -9

Related Questions