[fix] Image layout in Firefox

Layout is more reliable in Firefox if the root element's flexBasis is
'auto'. This patch also moves the background image to an internal tile.
This commit is contained in:
Nicolas Gallagher
2018-05-23 21:19:23 -07:00
parent 0eae7bed2e
commit a82cfbe504
2 changed files with 63 additions and 48 deletions

View File

@@ -5,10 +5,13 @@ import Image from '../';
import ImageLoader from '../../../modules/ImageLoader';
import ImageUriCache from '../ImageUriCache';
import React from 'react';
import StyleSheet from '../../StyleSheet';
import { mount, shallow } from 'enzyme';
const originalImage = window.Image;
const findImageSurfaceStyle = wrapper => StyleSheet.flatten(wrapper.childAt(0).prop('style'));
describe('components/Image', () => {
beforeEach(() => {
window.Image = jest.fn(() => ({}));
@@ -37,14 +40,14 @@ describe('components/Image', () => {
test('sets background image when value is an object', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico' };
const component = shallow(<Image defaultSource={defaultSource} />);
expect(component.prop('style').backgroundImage).toMatchSnapshot();
expect(findImageSurfaceStyle(component).backgroundImage).toMatchSnapshot();
});
test('sets background image when value is a string', () => {
// emulate require-ed asset
const defaultSource = 'https://google.com/favicon.ico';
const component = shallow(<Image defaultSource={defaultSource} />);
expect(component.prop('style').backgroundImage).toMatchSnapshot();
expect(findImageSurfaceStyle(component).backgroundImage).toMatchSnapshot();
});
test('sets "height" and "width" styles if missing', () => {
@@ -54,7 +57,7 @@ describe('components/Image', () => {
width: 20
};
const component = shallow(<Image defaultSource={defaultSource} />);
const { height, width } = component.prop('style');
const { height, width } = StyleSheet.flatten(component.prop('style'));
expect(height).toBe(10);
expect(width).toBe(20);
});
@@ -68,7 +71,7 @@ describe('components/Image', () => {
const component = shallow(
<Image defaultSource={defaultSource} style={{ height: 20, width: 40 }} />
);
const { height, width } = component.prop('style');
const { height, width } = StyleSheet.flatten(component.prop('style'));
expect(height).toBe(20);
expect(width).toBe(40);
});
@@ -141,7 +144,7 @@ describe('components/Image', () => {
].forEach(resizeMode => {
test(`value "${resizeMode}"`, () => {
const component = shallow(<Image resizeMode={resizeMode} />);
expect(component.prop('style').backgroundSize).toMatchSnapshot();
expect(findImageSurfaceStyle(component).backgroundSize).toMatchSnapshot();
});
});
});
@@ -206,7 +209,7 @@ describe('components/Image', () => {
describe('prop "style"', () => {
test('correctly supports "resizeMode" property', () => {
const component = shallow(<Image style={{ resizeMode: Image.resizeMode.contain }} />);
expect(component.prop('style').backgroundSize).toMatchSnapshot();
expect(findImageSurfaceStyle(component).backgroundSize).toMatchSnapshot();
});
test('removes other unsupported View styles', () => {

View File

@@ -188,37 +188,6 @@ class Image extends Component<*, State> {
...other
} = this.props;
const displayImage = resolveAssetUri(shouldDisplaySource ? source : defaultSource);
const imageSizeStyle = resolveAssetDimensions(shouldDisplaySource ? source : defaultSource);
const backgroundImage = displayImage ? `url("${displayImage}")` : null;
const originalStyle = StyleSheet.flatten(this.props.style);
const finalResizeMode = resizeMode || originalStyle.resizeMode || ImageResizeMode.cover;
const style = StyleSheet.flatten([
styles.initial,
imageSizeStyle,
originalStyle,
resizeModeStyles[finalResizeMode],
this.context.isInAParentText && styles.inline,
backgroundImage && { backgroundImage }
]);
// View doesn't support these styles
delete style.overlayColor;
delete style.resizeMode;
delete style.tintColor;
// Allows users to trigger the browser's image context menu
const hiddenImage = displayImage
? createElement('img', {
alt: accessibilityLabel || '',
decode: 'async',
draggable: draggable || false,
ref: this._setImageRef,
src: displayImage,
style: styles.img
})
: null;
if (process.env.NODE_ENV !== 'production') {
if (this.props.src) {
console.warn('The <Image> component requires a `source` property rather than `src`.');
@@ -231,15 +200,49 @@ class Image extends Component<*, State> {
}
}
const selectedSource = shouldDisplaySource ? source : defaultSource;
const displayImageUri = resolveAssetUri(selectedSource);
const imageSizeStyle = resolveAssetDimensions(selectedSource);
const backgroundImage = displayImageUri ? `url("${displayImageUri}")` : null;
const flatStyle = { ...StyleSheet.flatten(this.props.style) };
const finalResizeMode = resizeMode || flatStyle.resizeMode || ImageResizeMode.cover;
// View doesn't support these styles
delete flatStyle.overlayColor;
delete flatStyle.resizeMode;
delete flatStyle.tintColor;
// Accessibility image allows users to trigger the browser's image context menu
const hiddenImage = displayImageUri
? createElement('img', {
alt: accessibilityLabel || '',
draggable: draggable || false,
ref: this._setImageRef,
src: displayImageUri,
style: styles.accessibilityImage
})
: null;
return (
<View
{...other}
accessibilityLabel={accessibilityLabel}
accessible={accessible}
onLayout={onLayout}
style={style}
style={[
styles.root,
this.context.isInAParentText && styles.inline,
imageSizeStyle,
flatStyle
]}
testID={testID}
>
<View
style={[
styles.image,
resizeModeStyles[finalResizeMode],
backgroundImage && { backgroundImage }
]}
/>
{hiddenImage}
</View>
);
@@ -317,17 +320,25 @@ class Image extends Component<*, State> {
}
const styles = StyleSheet.create({
initial: {
backgroundColor: 'transparent',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
root: {
flexBasis: 'auto',
overflow: 'hidden',
zIndex: 0
},
inline: {
display: 'inline-flex'
},
img: {
image: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'transparent',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
height: '100%',
width: '100%',
zIndex: -1
},
accessibilityImage: {
...StyleSheet.absoluteFillObject,
height: '100%',
opacity: 0,
@@ -338,8 +349,7 @@ const styles = StyleSheet.create({
const resizeModeStyles = StyleSheet.create({
center: {
backgroundSize: 'auto',
backgroundPosition: 'center'
backgroundSize: 'auto'
},
contain: {
backgroundSize: 'contain'
@@ -348,11 +358,13 @@ const resizeModeStyles = StyleSheet.create({
backgroundSize: 'cover'
},
none: {
backgroundPosition: '0 0',
backgroundSize: 'auto'
},
repeat: {
backgroundSize: 'auto',
backgroundRepeat: 'repeat'
backgroundPosition: '0 0',
backgroundRepeat: 'repeat',
backgroundSize: 'auto'
},
stretch: {
backgroundSize: '100% 100%'