[fix] Image rendering in Safari

The use of 'max-height:100%' on the inner image can cause extremely poor
render performance in Safari. Remove the inner image and simplify
`Image` to use a single view. This fixes the following additional bugs:

Fix #202
Fix #226
This commit is contained in:
Nicolas Gallagher
2016-11-20 13:44:04 -08:00
parent fa14995a35
commit 4f71833aec
3 changed files with 26 additions and 339 deletions

View File

@@ -652,6 +652,6 @@ var styles = StyleSheet.create({
examples.forEach((example) => {
storiesOf('component: Image', module)
.addDecorator((renderStory) => <View>{renderStory()}</View>)
.addDecorator((renderStory) => <View style={{ width: '100%' }}>{renderStory()}</View>)
.add(example.title, () => example.render())
})

View File

@@ -52,23 +52,7 @@ exports[`components/Image prop "accessibilityLabel" 1`] = `
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<img
className=""
src={null}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "accessible" 1`] = `
@@ -125,23 +109,7 @@ exports[`components/Image prop "accessible" 1`] = `
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<img
className=""
src={null}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "children" 1`] = `
@@ -198,78 +166,8 @@ exports[`components/Image prop "children" 1`] = `
"textDecoration": "none",
}
}>
<img
className=""
src={null}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
<div
className=" __style_df __style_pebn"
style={
Object {
"MozBoxSizing": "border-box",
"WebkitAlignItems": "stretch",
"WebkitBoxAlign": "stretch",
"WebkitBoxDirection": "normal",
"WebkitBoxOrient": "vertical",
"WebkitFlexBasis": "auto",
"WebkitFlexDirection": "column",
"WebkitFlexShrink": 0,
"alignItems": "stretch",
"backgroundColor": "transparent",
"borderBottomStyle": "solid",
"borderBottomWidth": "0px",
"borderLeftStyle": "solid",
"borderLeftWidth": "0px",
"borderRightStyle": "solid",
"borderRightWidth": "0px",
"borderTopStyle": "solid",
"borderTopWidth": "0px",
"bottom": "0px",
"boxSizing": "border-box",
"color": "inherit",
"display": null,
"flexBasis": "auto",
"flexDirection": "column",
"flexShrink": 0,
"font": "inherit",
"left": "0px",
"listStyle": "none",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"minHeight": "0px",
"minWidth": "0px",
"msFlexAlign": "stretch",
"msFlexDirection": "column",
"msFlexNegative": 0,
"msPreferredSize": "auto",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"pointerEvents": null,
"position": "absolute",
"right": "0px",
"textAlign": "inherit",
"textDecoration": "none",
"top": "0px",
}
}>
<div
className="unique" />
</div>
className="unique" />
</div>
`;
@@ -329,23 +227,7 @@ exports[`components/Image prop "defaultSource" does not override "height" and "w
"textDecoration": "none",
"width": "40px",
}
}>
<img
className=""
src="https://google.com/favicon.ico"
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "defaultSource" sets "height" and "width" styles if missing 1`] = `
@@ -404,23 +286,7 @@ exports[`components/Image prop "defaultSource" sets "height" and "width" styles
"textDecoration": "none",
"width": "20px",
}
}>
<img
className=""
src="https://google.com/favicon.ico"
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "defaultSource" sets background image when value is a string 1`] = `
@@ -477,23 +343,7 @@ exports[`components/Image prop "defaultSource" sets background image when value
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<img
className=""
src="https://google.com/favicon.ico"
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "defaultSource" sets background image when value is an object 1`] = `
@@ -552,23 +402,7 @@ exports[`components/Image prop "defaultSource" sets background image when value
"textDecoration": "none",
"width": undefined,
}
}>
<img
className=""
src="https://google.com/favicon.ico"
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "resizeMode" value "contain" 1`] = `
@@ -624,23 +458,7 @@ exports[`components/Image prop "resizeMode" value "contain" 1`] = `
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<img
className=""
src={null}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "resizeMode" value "cover" 1`] = `
@@ -696,23 +514,7 @@ exports[`components/Image prop "resizeMode" value "cover" 1`] = `
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<img
className=""
src={null}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "resizeMode" value "none" 1`] = `
@@ -768,23 +570,7 @@ exports[`components/Image prop "resizeMode" value "none" 1`] = `
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<img
className=""
src={null}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "resizeMode" value "stretch" 1`] = `
@@ -840,23 +626,7 @@ exports[`components/Image prop "resizeMode" value "stretch" 1`] = `
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<img
className=""
src={null}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "resizeMode" value "undefined" 1`] = `
@@ -912,23 +682,7 @@ exports[`components/Image prop "resizeMode" value "undefined" 1`] = `
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<img
className=""
src={null}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "style" correctly supports "resizeMode" property 1`] = `
@@ -984,23 +738,7 @@ exports[`components/Image prop "style" correctly supports "resizeMode" property
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<img
className=""
src={null}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image prop "testID" 1`] = `
@@ -1057,23 +795,7 @@ exports[`components/Image prop "testID" 1`] = `
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<img
className=""
src={null}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;
exports[`components/Image sets correct accessibility role" 1`] = `
@@ -1129,21 +851,5 @@ exports[`components/Image sets correct accessibility role" 1`] = `
"textAlign": "inherit",
"textDecoration": "none",
}
}>
<img
className=""
src={null}
style={
Object {
"borderBottomWidth": "0px",
"borderLeftWidth": "0px",
"borderRightWidth": "0px",
"borderTopWidth": "0px",
"height": "auto",
"maxHeight": "100%",
"maxWidth": "100%",
"opacity": 0,
}
} />
</div>
} />
`;

View File

@@ -1,7 +1,6 @@
/* global window */
import applyNativeMethods from '../../modules/applyNativeMethods';
import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes';
import createDOMElement from '../../modules/createDOMElement';
import ImageResizeMode from './ImageResizeMode';
import ImageStylePropTypes from './ImageStylePropTypes';
import StyleSheet from '../../apis/StyleSheet';
@@ -53,7 +52,6 @@ class Image extends Component {
};
static defaultProps = {
accessible: true,
style: {}
};
@@ -64,12 +62,14 @@ class Image extends Component {
this.state = { isLoaded: false };
const uri = resolveAssetSource(props.source);
this._imageState = uri ? STATUS_PENDING : STATUS_IDLE;
this._isMounted = false;
}
componentDidMount() {
if (this._imageState === STATUS_PENDING) {
this._createImageLoader();
}
this._isMounted = true;
}
componentDidUpdate() {
@@ -87,6 +87,7 @@ class Image extends Component {
componentWillUnmount() {
this._destroyImageLoader();
this._isMounted = false;
}
render() {
@@ -117,26 +118,16 @@ class Image extends Component {
// View doesn't support 'resizeMode' as a style
delete style.resizeMode;
/**
* The image is displayed as a background image to support `resizeMode`.
* The HTML image is hidden but used to provide the correct responsive
* image dimensions, and to support the image context menu. Child content
* is rendered into an element absolutely positioned over the image.
*/
return (
<View
accessibilityLabel={accessibilityLabel}
accessibilityRole='img'
accessible={accessible}
children={children}
onLayout={onLayout}
style={style}
testID={testID}
>
{createDOMElement('img', { src: displayImage, style: styles.img })}
{children ? (
<View children={children} pointerEvents='box-none' style={styles.children} />
) : null}
</View>
/>
);
}
@@ -198,7 +189,11 @@ class Image extends Component {
this._imageState = status;
const isLoaded = this._imageState === STATUS_LOADED;
if (isLoaded !== this.state.isLoaded) {
this.setState({ isLoaded });
window.requestAnimationFrame(() => {
if (this._isMounted) {
this.setState({ isLoaded });
}
});
}
}
}
@@ -209,20 +204,6 @@ const styles = StyleSheet.create({
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover'
},
img: {
borderWidth: 0,
height: 'auto',
maxHeight: '100%',
maxWidth: '100%',
opacity: 0
},
children: {
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0
}
});