Auto scale image height with React Native

React Native

React Native Problem Overview


In my React Native app, I am fetching images from an API with unknown dimensions. How do I auto scale the height if I know my desired width?

Example:

I set the width to Dimensions.get('window').width. How do set the height and keep the same ratio?

export default class MyComponent extends Component {
  constructor(props) {
    super(props)
    this.state = {
      imgUrl: 'http://someimg.com/coolstuff.jpg'
    }
  }
  
  componentDidMount() {
    // sets the image url to state
    this.props.getImageFromAPi()
  }
  
  render() {
    return (
      <View>
        <Image 
          source={uri: this.state.imgUrl}
          style={styles.myImg}
        />
        <Text>Some description</Text>
      </View>
    )
  }
}

const styles = StyleSheet.create(
  myImg: {
    width: Dimensions.get('window').width,
    height: >>>???what goes here???<<<
  }
)

React Native Solutions


Solution 1 - React Native

Try this:

 import React, { Component, PropTypes } from "react";
 import { Image } from "react-native";

export default class ScaledImage extends Component {
constructor(props) {
    super(props);
    this.state = { source: { uri: this.props.uri } };
}

componentWillMount() {
    Image.getSize(this.props.uri, (width, height) => {
        if (this.props.width && !this.props.height) {
            this.setState({
                width: this.props.width,
                height: height * (this.props.width / width)
            });
        } else if (!this.props.width && this.props.height) {
            this.setState({
                width: width * (this.props.height / height),
                height: this.props.height
            });
        } else {
            this.setState({ width: width, height: height });
        }
    });
}

render() {
    return (
        <Image
            source={this.state.source}
            style={{ height: this.state.height, width: this.state.width }}
        />
    );
}
}

ScaledImage.propTypes = {
uri: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number
};

I'm passing the URL as a prop called uri. You can specify your width prop as Dimensions.get('window').width and that should cover it.

Note that this will also work if you know what you want to set the height to and you need to resize the width to maintain the ratio. In that case, you would specify the height prop instead of the width one.

Solution 2 - React Native

There is a property resizeMode set it to 'contain'

Example:

<Image
    source={require('./local_path_to/your_image.png')}
    style={{ width: 30 }}
    resizeMode="contain"
 />

Source: https://facebook.github.io/react-native/docs/image#resizemode

Edit: The above solution is working fine for me, the resizeMode property is not deprecated and I couldn't find any indications that they are planning to do so. If for some reason the the above solution doesn't work for you, you can calculate the height yourself. Here is an axample:

const Demo = () => {
    const scaleHeight = ({ source, desiredWidth }) => {
        const { width, height } = Image.resolveAssetSource(source)

        return desiredWidth / width * height
    }

    const imageSource = './local_image.png'
    const imageWidth = 150
    const imageHeigh = scaleHeight({
        source: require(imageSource),
        desiredWidth: imageWidth
    })
    
    return (
        <View style={{
            display: 'flex',
            flex: 1,
            alignItems: 'center',
            justifyContent: 'center'
        }}>
            <Image
                source={require(imageSource)}
                style={{
                    borderWidth: 1,
                    width: imageWidth,
                    height: imageHeigh
                }}
            />
        </View>
    )
}

The above solution works only for local images. Here is how to do the same for remote images:

const RemoteImage = ({uri, desiredWidth}) => {
    const [desiredHeight, setDesiredHeight] = React.useState(0)

    Image.getSize(uri, (width, height) => {
        setDesiredHeight(desiredWidth / width * height)
    })

    return (
        <Image
            source={{uri}}
            style={{
                borderWidth: 1,
                width: desiredWidth,
                height: desiredHeight
            }}
        />
    )
}

const Demo = () => {
    return (
        <View style={{
            flex: 1,
            alignItems: 'center',
            justifyContent: 'center'
        }}>
            <RemoteImage
                uri="https://via.placeholder.com/350x150"
                desiredWidth={200}
            />
        </View>
    )
}

Solution 3 - React Native

Have a look at this library react-native-scalable-image. It does exactly what you are asking for.

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

const image = (
   <Image
       width={Dimensions.get('window').width} // height will be calculated automatically
       source={{uri: '<image uri>'}}
   />
);

Solution 4 - React Native

TypeScript version of @TheJizel answer with optional style property and failure callback in Image.getSize:

import * as React from 'react'
import {Image} from 'react-native'

interface Props {
    uri: string
    width?: number
    height?: number
    style?
}

interface State {
    source: {}
    width: number
    height: number
}

export default class ScaledImage extends React.Component<Props, State> {
    constructor(props) {
        super(props)
        this.state = {
            source: {uri: this.props.uri},
            width: 0,
            height: 0,
        }
    }

    componentWillMount() {
        Image.getSize(this.props.uri, (width, height) => {
            if (this.props.width && !this.props.height) {
                this.setState({width: this.props.width, height: height * (this.props.width / width)})
            } else if (!this.props.width && this.props.height) {
                this.setState({width: width * (this.props.height / height), height: this.props.height})
            } else {
                this.setState({width: width, height: height})
            }
        }, (error) => {
            console.log("ScaledImage:componentWillMount:Image.getSize failed with error: ", error)
        })
    }

    render() {
        return <Image source={this.state.source} style={[this.props.style, {height: this.state.height, width: this.state.width}]}/>
    }
}

Example usage:

<ScaledImage style={styles.scaledImage} uri={this.props.article.coverImageUrl} width={Dimensions.get('window').width}/>

Solution 5 - React Native

Hooks version of @TheJizel answer. I knew the width but wanted the height of the image, so the below worked for me :

    const ScaledImage = props => {

    const [width, setWidth] = useState()
    const [height, setHeight] = useState()
    const [imageLoading, setImageLoading] = useState(true)

    useEffect(() => {
        Image.getSize(props.uri, (width1, height1) => {
            if (props.width && !props.height) {
                setWidth(props.width)
                setHeight(height1 * (props.width / width1))
            } else if (!props.width && props.height) {
                setWidth(width1 * (props.height / height1))
                setHeight(props.height)
            } else {
                setWidth(width1)
                setHeight(height1)
            }
            setImageLoading(false)
        }, (error) => {
            console.log("ScaledImage,Image.getSize failed with error: ", error)
        })
    }, [])


    return (
        height ?
            <View style={{ height: height, width: width, borderRadius: 5, backgroundColor: "lightgray" }}>
                <Image
                    source={{ uri: props.uri }}
                    style={{ height: height, width: width, borderRadius: 5, }}
                />
            </View>
            : imageLoading ?
                <ActivityIndicator size="large" />
                : null
    );
}

Usage :

<ScaledImage width={Dimensions.get('window').width * 0.8} uri={imageurl} />

Solution 6 - React Native

I created a hook that calculates an image's aspect ratio:

function useImageAspectRatio(imageUrl) {
  const [aspectRatio, setAspectRatio] = useState(1);

  useEffect(() => {
    if (!imageUrl) {
      return;
    }

    let isValid = true;
    Image.getSize(imageUrl, (width, height) => {
      if (isValid) {
        setAspectRatio(width / height);
      }
    });

    return () => {
      isValid = false;
    };
  }, [imageUrl]);

  return aspectRatio;
}

With that you can set only one value of width or height, and calculate the other automatically:

function App() {
  const aspectRatio = useImageAspectRatio(imageUrl);

  return (
    <Image 
      src={{ uri: imageUrl }}
      style={{ width: 200, aspectRatio }}
    />
  )
}

Solution 7 - React Native

First try this and see if it works for you: https://github.com/facebook/react-native/commit/5850165795c54b8d5de7bef9f69f6fe6b1b4763d

If it doesn't, then you can implement your own image component. But instead of taking width as prop, you override onLayout method which gives you desired width so that you can calculate the height. This works better if you don't know the width and want RN to do the layout for you. The drawback is onLayout is called after one pass of layout and rendering. So you might notice your components moving around a bit.

Solution 8 - React Native

Based on the answers above, I made, with TypeScript, a functional component that downloads the image only once (because the second time it will be cached: https://reactnative.dev/docs/image#getsize), if only one value is passed; and that calculates both height and width, depending on the property that was passed

    import { useFocusEffect } from '@react-navigation/native';
    import React from 'react';
    import { ImageProps, ImageURISource } from 'react-native';
    import { useIsMounted } from '../../hooks/is-mounted';
    import { DrImageStyl } from './styled';
    import { getImageSizes } from '../../utils/util';
    
    interface DrSource extends ImageURISource {
      uri: string;
    }
    
    interface DrImageProps extends ImageProps {
      source: DrSource;
      width?: number;
      height?: number;
    }
    
    const DrImage: React.FC<DrImageProps> = ({
      width: widthProp,
      height: heightProp,
      source,
      ...rest
    }: DrImageProps) => {
      const isMountedRef = useIsMounted();
    
      const [sizes, setSizes] = React.useState({
        width: widthProp,
        height: heightProp,
      });
    
      useFocusEffect(
        React.useCallback(() => {
          const getImageSizesState = async () => {
            try {
              const { width, height } = await getImageSizes({
                uri: source.uri,
                width: widthProp,
                height: heightProp,
              });
    
              if (isMountedRef.current) {
                setSizes({ width, height });
              }
            } catch (error) {
              console.log('Erro em dr-image getImageSizesState:', error);
            }
          };
    
          getImageSizesState();
        }, [widthProp, heightProp, source.uri])
      );
    
      return (
        <>
          {!!sizes.height && !!sizes.width && (
            <DrImageStyl sizes={sizes} source={source} {...rest} />
          )}
        </>
      );
    };

export default DrImage;

I used a hook to determine if, after the asynchronous function, the component is still mounted (useIsMounted):

import React from 'react';

export const useIsMounted = (): React.MutableRefObject<boolean> => {
  const isMountedRef = React.useRef(false);
  React.useEffect(() => {
    isMountedRef.current = true;
    return () => {
      isMountedRef.current = false;
    };
  }, []);
  return isMountedRef;
};

I used the styled-components module to make the component's css (DrImageStyl ):

import React from 'react';
import styled, { css } from 'styled-components/native';

interface Sizes {
  width?: number;
  height?: number;
}

interface DrImageStylProps {
  sizes: Sizes;
}

export const DrImageStyl = styled.Image<DrImageStylProps>`
  ${({ sizes }) => {
    const { width, height } = sizes;

    return css`
      ${width ? `width: ${width}px;` : ''}
      ${height ? `height: ${height}px;` : ''}
    `;
  }}
`;

I separated the code that calculates the other image size (getImageSizes):

import { Image } from 'react-native';

interface GetImageSizesParams {
  uri: string;
  height?: number;
  width?: number;
}

export function getImageSizes({
  height: heightParam,
  width: widthParam,
  uri,
}: GetImageSizesParams): Promise<{
  width: number;
  height: number;
}> {
  return new Promise((resolve, reject) => {
    function onSuccess(width: number, height: number) {
      let widthResolve: number | undefined;
      let heightResolve: number | undefined;

      if (widthParam && !heightParam) {
        widthResolve = widthParam;
        heightResolve = height * (widthParam / width);
      } else if (!widthParam && heightParam) {
        widthResolve = width * (heightParam / height);
        heightResolve = heightParam;
      } else {
        widthResolve = widthParam;
        heightResolve = heightParam;
      }

      resolve({
        width: widthResolve as number,
        height: heightResolve as number,
      });
    }

    function onError(error: any) {
      reject(error);
    }
    try {
      Image.getSize(uri, onSuccess, onError);
    } catch (error) {
      console.log('error', error);
    }
  });
}

Solution 9 - React Native

Here's a gist for a pretty simple solution that leverages @Haitao Li's proposal to use aspectRatio:

https://gist.github.com/tpraxl/02dc4bfcfa301340d26a0bf2140cd8b9

No magic and no calculations necessary. Pure "CSS" if you know the original image's dimensions.

Solution 10 - React Native

The proposed solution works, but you have to download image twice, once to determine the size and another to actually show the image, this is a different approach, image is loaded squared initially and resized.

import React, { Component, } from "react";
import { Image } from "react-native";
import PropTypes from 'prop-types'

    export default class ScaledImage extends Component {
        state = {}
    
        componentWillMount() {
            const { uri, width, height } = this.props;
            this.setState({ source: { uri }, width: width || height, height: height || width });
        }
    
        render() {
            return (
                <Image
                    source={this.state.source}
                    onLoad={(value) => {
                        const { height, width } = value.nativeEvent.source;
                        if (this.props.width && !this.props.height) {
                            this.setState({
                                width: this.props.width,
                                height: height * (this.props.width / width)
                            });
                        } else if (!this.props.width && this.props.height) {
                            this.setState({
                                width: width * (this.props.height / height),
                                height: this.props.height
                            });
                        } else {
                            this.setState({ width: width, height: height });
                        }
    
                    }}
                    style={{ height: this.state.height, width: this.state.width }}
                />
            );
        }
    }
    
    ScaledImage.propTypes = {
        uri: PropTypes.string.isRequired,
        width: PropTypes.number,
        height: PropTypes.number
    };

Solution 11 - React Native

This one worked for me in expo

<Image style={{flex:1,width:null,height:null }} resizeMode={'contain'}  source={{uri: 'http://134.209.40.60/meApunto/1567655610795_1944474896.png'}}></Image>

https://forums.expo.io/t/how-to-fit-a-big-image-into-a-fixed-container-without-resizemode-help/27639

Solution 12 - React Native

Based on @TheJizel's idea, I cooked up something using the aspectRatio style property. The following class works when the width is set, but height is omitted. This also works with percentages as width.

import React from "react";
import { Image } from "react-native";

export default class ScaledImage extends React.Component {

  state = {
    aspectRatio: 0
  }

  setAspectRatio(ratio) {
    this.setState({
      aspectRatio: ratio
    });
  }

  componentWillMount() {
    if (Array.isArray(this.props.source)) {
      console.warn("ScaledImage received an array as source instead of local file resource or ImageURISource.")
    } else if(typeof this.props.source === "number") {
      // Resolve local file resource
      const resolved = Image.resolveAssetSource(this.props.source);

      // We assume 100% width, so we set the aspect ratio we want for it's height
      this.setAspectRatio(resolved.width / resolved.height);

    } else if (this.props.source.uri) {
      // Resolve remote resource
      Image.getSize(this.props.source.uri, (width, height) => {
         this.setAspectRatio( width / height);
      }, (err) => {
        console.error(err);
      });

    } else {
      console.warn("ScaledImage did not receive a valid source uri.");
    }
  }

  render() {
    if(!this.state.aspectRatio) return null;

    const props = {
      ...this.props,
      style: [this.props.style, {
        aspectRatio: this.state.aspectRatio
      }]
    };

    return (
      <Image {...props} />
    )
  }
}

Usage:

<ScaledImage source={{ uri: "<URI HERE>" }} style={{ width: "100%" }} />

Solution 13 - React Native

You have 3 numbers :

  1. width of Image
  2. height of Image
  3. width of Screen

and you should put "width of Screen" in width style and also calculate height for setup in style ??!!

componentWillMount() {

    Image.getSize(this.props.product.image, (width, height) => {

        const screenWidth = Math.round(Dimensions.get('window').width);  
        this.setState({screenWidth:screenWidth});
        Calculatedheight = screenWidth * height / width ;
        this.setState({Calculatedheight : Calculatedheight });

    });

}

and

<Image
  source={{uri: product.image,cache: 'only-if-cached'}}
  style={{ height: this.state.screenHeight , width: this.state.Calculatedheight }}

/>

Solution 14 - React Native

Here's some code I'm using in production. The backend user could make a logo image of any size and aspect ratio, but I needed the logo to fit an exact height with a max width. My self-scaling component is what resulted:

import React, { useState, useLayoutEffect, SFC } from "react";
import { Image } from "react-native";
import { Spinner } from "native-base";


interface INetworkImage {
    targetHeight: number,
    uri: string,
    maxWidth: number
}

const NetworkImage: SFC<INetworkImage> = ({ uri, targetHeight, maxWidth }) => {

    useLayoutEffect(() => setNaturalDimensions(uri), []);

    const [imageWidth, setWidth] = useState(0);
    const [imageHeight, setHeight] = useState(0);
    const [scaleFactor, setScale] = useState(1);

    function setNaturalDimensions(uri: string) {
        Image.getSize(uri, (width: number, height: number) => {
            if (width > maxWidth) {
                // too wide case
                setScale(maxWidth / width);
            } else {
                // scale to height case
                setScale(targetHeight / height);
            }
            setWidth(width);
            setHeight(height);
        }, (error: any) => {
            console.log("error", error);
        });
    }
    function adjustView(e) {
        if (e.nativeEvent.layout.width > maxWidth) {
            setScale(scaleFactor * (maxWidth/e.nativeEvent.layout.width));
        }
    }
    return (
        imageHeight ?
        <Image
            onLayout={(e) => adjustView(e)}
            source={{ uri: uri }}
            style={{
                width: imageWidth * scaleFactor,
                height: imageHeight * scaleFactor,
                resizeMode: "contain",
            }}
        />:
        <Spinner color='#454c7a' />
        );
}
export default NetworkImage;

Then I use it by passing the uri, targetHeight, and maxwidth in as props:

export const deviceWidth = Dimensions.get("window").width;

<NetworkImage
    uri={"https://purdyPic.com/image1"}
    targetHeight={300}
    maxWidth={deviceWidth * 0.85}
                          />

Solution 15 - React Native

one solution out of many

<Image source={...} style={{ transform: [{ scale: 0.5 }] }} />

Solution 16 - React Native

So this all helped me a bunch 

My particular scenario involved getting images from a server that could be either portrait or landscape, and I needed to fit them into a <View>.

This means the "known" dimensions are of that view, which I obtained via onLayout (simplified code to just show an example setting a "height"):

<View onLayout={(event) => setCellHeight(event.nativeEvent.layout.height)}>

Now with my known displayAreaHeight and displayAreaWidth values I need to size my image:

  // Set image size for portrait/landscape scenarios, reducing the total image size when
  // an overflow of the display area would occur.

  if (image.height > image.width) { // Portrait Image
    const ratio = displayAreaHeight / image.height;
    imageHeight = displayAreaHeight;
    imageWidth = image.width * ratio;
    if (imageWidth > displayAreaWidth) {
      const heightReductionRatio = displayAreaWidth / imageWidth;
      imageHeight *= heightReductionRatio;
      imageWidth = displayAreaWidth;
    }
  } else {
    const ratio = displayAreaWidth / image.width;
    imageHeight = image.height * ratio;
    imageWidth = displayAreaWidth;
    if (imageHeight > displayAreaHeight) {
      const widthReductionRatio = displayAreaHeight / imageHeight;
      imageWidth *= widthReductionRatio;
      imageHeight = displayAreaHeight;
    }
  }

Hopefully this, along with all the other great responses here, helps someone out 

Solution 17 - React Native

Solution 18 - React Native

No need to use any lib to achieve this instead use the below solution:

import React from  'react';
import { ImageProps } from 'react-native';
import FastImage from "react-native-fast-image";

const AutoHeightImage = React.memo(function AutoHeightImage ({ width,imageStyle, ...props }: ImageProps) {
  const [state, setstate] = React.useState(0)
  return (
    <FastImage 
      {...props}
      style={[{ width: width, height: state }, imageStyle]}
      resizeMode={FastImage.resizeMode.contain}
      onLoad={(evt) => {
        setstate((evt.nativeEvent.height / evt.nativeEvent.width) * width)
      }}
    />
  )
})

export default AutoHeightImage;

How to use the above custom component:

 <AutoHeightImage
     width={(Dimensions.get('window').width)}
     source={{ uri: 'image url' }}/>

React native fast image used from https://github.com/DylanVann/react-native-fast-image

Solution 19 - React Native

here is functional component sollution for using local files:

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

const ScaledImage = props => {
  const [source, setSource] = useState(props.uri);
  const [width, setWidth] = useState(props.width);
  const [height, setHeight] = useState(props.height);

  useEffect(() => {
    let dimensions = resolveAssetSource(source);
    if (props.width && !props.height) {
      setWidth(props.width);
      setHeight(dimensions.height * (props.width / dimensions.width));
    } else if (!props.width && props.height) {
      setWidth(dimensions.width * (props.height / dimensions.height));
      setHeight(props.height);
    } else {
      setWidth(dimensions.width);
      setHeight(dimensions.height);
    }
  }, []);

  return (
    <Image
      source={source}
      style={[{height: height, width: width}, props.style]}
    />
  );
};

export default ScaledImage;

usage example:

<ScaledImage
  width={Dimensions.get('window').width * 0.8}
  uri={require('../../../images/Logo_Poziom.png')}
  style={[
    {
      position: 'absolute',
      top: 100,
      zIndex: 1,
    },
  ]}
/>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPhil MokView Question on Stackoverflow
Solution 1 - React NativeTheJizelView Answer on Stackoverflow
Solution 2 - React NativeNedko DimitrovView Answer on Stackoverflow
Solution 3 - React NativeIhor BurlachenkoView Answer on Stackoverflow
Solution 4 - React NativeKlimczakMView Answer on Stackoverflow
Solution 5 - React NativeAvi RView Answer on Stackoverflow
Solution 6 - React NativePaduadoView Answer on Stackoverflow
Solution 7 - React NativeHaitao LiView Answer on Stackoverflow
Solution 8 - React NativeAlexandre OliveiraView Answer on Stackoverflow
Solution 9 - React NativeThomas PraxlView Answer on Stackoverflow
Solution 10 - React NativejosesueroView Answer on Stackoverflow
Solution 11 - React NativeArun Prasad E SView Answer on Stackoverflow
Solution 12 - React NativeHlessView Answer on Stackoverflow
Solution 13 - React NativeMohsen MolaeiView Answer on Stackoverflow
Solution 14 - React NativeRyan DinesView Answer on Stackoverflow
Solution 15 - React Nativex-magixView Answer on Stackoverflow
Solution 16 - React NativeRyan CrewsView Answer on Stackoverflow
Solution 17 - React NativeKavidu Aloka KodikaraView Answer on Stackoverflow
Solution 18 - React NativeLokesh DesaiView Answer on Stackoverflow
Solution 19 - React NativeSIlvesterView Answer on Stackoverflow