My book on iOS interface design, Design Teardowns: Step-by-step iOS interface design walkthroughs is now available!
A better <img> tag
There's usually two solutions to embedding images into your web content:
1) Use the HTML <img>
tag
This means you can set its height
and width
which could result in a non-uniform scaling applied to each dimension.
2) Apply background-image
via CSS
You have more control over how the image is displayed but now you have to consider classes and how you would apply them to your HTML content.
With React, we can simplify this by abstracting the implementation details into a reusable component. Ideally we want to use our component as if it was regular HTML markup:
<Image src={url} width={500} height={300} mode='fit' />
This bit of JSX suggests we want to be able to set the image src
, and dimensions via width
and height
. We also have mode
which may be specified as fit
or fill
. This allows us to scale proportionally and crop the image appropriately.
Conveniently, CSS also exposes a similar attribute via background-size
which can be specified as contain
or cover
.
Let's start with a typical React component template:
import React, {Component} from 'react';
export default class Image extends Component {
render() {
return <div />
}
}
Next we'll extract the necessary props and setup the CSS attributes as inline styles on our <div />
:
import React, {Component} from 'react';
export default class Image extends Component {
render() {
let {mode, src, height, width, style, ...props} = this.props;
let modes = {
'fill': 'cover',
'fit': 'contain'
};
let size = modes[mode] || 'contain';
let important = {
backgroundImage: `url("${src}")`,
backgroundSize: size,
backgroundPosition: 'center center',
backgroundRepeat: 'no-repeat'
};
return <div {...props} style={{...style, ...important}} />
}
}
Here we're doing a couple of things:
1) We're using ES6's destructuring assignment feature to extract the props we're specifically interested to handle and coalesce the remaining props into the props
variable.
2) We setup the CSS styles we require (mostly background-
attributes.) ES6's template strings allow us to interpolate the url from src
easily.
3) We use the spread operator ...
to expand props
into the <div />
element and also to merge style
, which contains inline styles the parent component might have specified, with important
, which contains the CSS styles needed for displaying the image. The order here is crucial because we want keys from important
to override the same keys if they are specified in style
.
Finally to make our component a bit more robust, let's add some default styles if they are not specified by the parent component:
import React, {Component} from 'react';
export default class Image extends Component {
render() {
let {mode, src, height, width, style, ...props} = this.props;
let modes = {
'fill': 'cover',
'fit': 'contain'
};
let size = modes[mode] || 'contain';
let defaults = {
height: height || 100,
width: width || 100,
backgroundColor: 'gray'
};
let important = {
backgroundImage: `url("${src}")`,
backgroundSize: size,
backgroundPosition: 'center center',
backgroundRepeat: 'no-repeat'
};
return <div {...props} style={{...defaults, ...style, ...important}} />
}
}
This fixes the issue whereby if no height
and width
(or corresponding styles) are specified, we would end up with a zero-by-zero element. And there we have a functional and complete React element!