Internet连接速度是可变的,尤其是在使用移动设备时。作为开发人员,我们经常会忘记许多用户在功能较弱的设备上运行我们的应用程序,并且互联网连接速度比我们慢。走出深山,尝试访问您的应用程序并查看其性能。
应用程序中最昂贵的东西之一就是您正在加载的远程图像。它们将花费一些时间来加载,特别是如果它们是大图像。
今天,我们将构建一个组件,使我们能够:
-
传递完整尺寸的图像以显示(就像普通
Image
组件一样) -
在加载全尺寸图片时传递缩略图以显示
-
在即将下载的图片的位置自动显示一个占位符,以指示有东西存在
-
在每个状态之间进行动画处理。
Getting Started
要开始创建一个新的阵营原生应用(通过react-native init
,create-react-native-app
或expo
CLI),并添加以下App.js
。
1import React from 'react';
2import { StyleSheet, View, Dimensions, Image } from 'react-native';
3
4const w = Dimensions.get('window');
5
6const styles = StyleSheet.create({
7 container: {
8 flex: 1,
9 alignItems: 'center',
10 justifyContent: 'center',
11 },
12});
13
14export default class App extends React.Component {
15 render() {
16 return (
17 <View style={styles.container}>
18 <Image
19 source={{ uri: `https://images.pexels.com/photos/671557/pexels-photo-671557.jpeg?w=${w.width * 2}&buster=${Math.random()}` }}
20 style={{ width: w.width, height: w.width }}
21 resizeMode="cover"
22 />
23 </View>
24 );
25 }
26}
27
该代码块仅显示图像。它要求Pexels提供2 倍于屏幕大小的图像(我们想要大图像,以便它们缓慢加载),而buster
查询参数将帮助我们不缓存图像,因此我们可以完全了解发生了什么。您不想在实际的应用程序中执行此操作。
Feel the Pain
就像我之前说过的-作为开发人员,您可能已经拥有相当不错的互联网连接。
让我们改变它。
如果您使用的是Mac,则可以安装一个称为Network Link Conditioner的工具(以下是安装方法)。我确定Windows和Linux的粉丝也有相似之处(如果您有工具建议,请参见下面的提示)。
它将允许您模拟几乎所需的任何网络状况。请记住在开始流式传输视频之前将其关闭。
启用它并将其设置为“ 3G”配置文件。
这是关闭和打开网络链接调节器的启动器的比较。
网络链接调节器关闭与网络链接调节器打开(3G速度)
ProgressiveImage Component
要替换该Image
组件,我们将创建一个名为的新组件ProgressiveImage
。该组件的目标是使它能够像普通Image
组件一样正常工作,只是具有一些附加功能:
-
彩色背景填充图像的位置
-
能够传递缩略图
首先,让我们建立基础:
1// ProgressiveImage.jsimport React from 'react';
2import { View, StyleSheet, Image } from 'react-native';const styles = StyleSheet.create({});class ProgressiveImage extends React.Component {
3 render() {
4 return <Image {...this.props} />
5 }
6}export default ProgressiveImage;
7
我们正在使用传播语法将所有内容传递this.props
到Image
组件,以使一切都能按预期工作,而无需手动定义每个道具。
然后替换Image
在App.js
用新的ProgressiveImage
组件。
1// App.jsimport React from 'react';
2import { StyleSheet, View, Dimensions } from 'react-native';
3import ProgressiveImage from './ProgressiveImage';// ...export default class App extends React.Component {
4 render() {
5 return (
6 <View style={styles.container}>
7 <ProgressiveImage
8 source={{ uri: `https://images.pexels.com/photos/671557/pexels-photo-671557.jpeg?w=${w.width * 2}&buster=${Math.random()}` }}
9 style={{ width: w.width, height: w.width }}
10 resizeMode="cover"
11 />
12 </View>
13 );
14 }
15}
16
一切都应该与以前完全一样。
设置背景色
加载远程图像时,需要指定图像的宽度和高度以进行渲染。我们将利用该要求使我们能够轻松设置默认背景色。
1import React from 'react';
2import { View, StyleSheet, Image } from 'react-native';const styles = StyleSheet.create({
3 imageOverlay: {
4 position: 'absolute',
5 left: 0,
6 right: 0,
7 bottom: 0,
8 top: 0,
9 },
10 container: {
11 backgroundColor: '#e1e4e8',
12 },
13});class ProgressiveImage extends React.Component {
14 render() {
15 return (
16 <View style={styles.container}>
17 <Image {...this.props} />
18 </View>
19 );
20 }
21}export default ProgressiveImage;
22
首先,我们container
使用背景颜色创建样式,然后将Image
组件包装为View
(分配了新样式)。
这为我们逐步加载图像提供了第一阶段。
显示缩略图图像
接下来,我们将显示图像的缩略图。生成此图像超出了本教程的范围,因此我们假设您已经获得了完整尺寸的图像和缩略图版本。
首先,在的实例中ProgressiveImage
,我们将添加一个thumbnailSource
道具,该道具将获取与典型Image
源道具完全相同的信息。在其中,我们将传递图像的较小版本(在这种情况下为50,请使用所需的任何值)和我们的清除缓存查询变量(仅用于演示目的)。
1// App.js// ...export default class App extends React.Component {
2 render() {
3 return (
4 <View style={styles.container}>
5 <ProgressiveImage
6 thumbnailSource={{ uri: `https://images.pexels.com/photos/671557/pexels-photo-671557.jpeg?w=50&buster=${Math.random()}` }}
7 source={{ uri: `https://images.pexels.com/photos/671557/pexels-photo-671557.jpeg?w=${w.width * 2}&buster=${Math.random()}` }}
8 style={{ width: w.width, height: w.width }}
9 resizeMode="cover"
10 />
11 </View>
12 );
13 }
14}
15
然后,我们将修改我们的ProgressiveImage
组件。首先将imageOverlay
样式添加到样式对象。
1// ProgressiveImage.js// ...const styles = StyleSheet.create({
2 imageOverlay: {
3 position: 'absolute',
4 left: 0,
5 right: 0,
6 bottom: 0,
7 top: 0,
8 },
9 container: {
10 backgroundColor: '#e1e4e8',
11 },
12});// ...
然后,我们将渲染两个Image
组件。在此之前,尽管我们将使用对象分解来提取一些道具,this.props
因为我们将覆盖/组合它们。
1// ProgressiveImage.js// ...class ProgressiveImage extends React.Component {
2 render() {
3 const {
4 thumbnailSource,
5 source,
6 style,
7 ...props
8 } = this.props; return (
9 <View style={styles.container}>
10 <Image
11 {...props}
12 source={thumbnailSource}
13 style={style}
14 />
15 <Image
16 {...props}
17 source={source}
18 style={[styles.imageOverlay, style]}
19 />
20 </View>
21 );
22 }
23}export default ProgressiveImage;
你可以看到我们拉thumbnailSource
,source
和style
道具了。然后,我们使用“ rest”语法捕获其余的道具。这使我们可以将所有通用道具转发到我们的两个图像,而仅将所需道具转发到正确的组件(如适当的来源)。
您会注意到,我们正在将传递的样式和我们styles.imageOverlay
的全尺寸图片结合在一起。这样,通过绝对定位,图像将覆盖缩略图版本。
结果如下:
注意:您会注意到缩略图图像非常像素化。您可以将blurRadius
道具传递到缩略图图像以使其模糊。我拍了一个屏幕截图,以便您可以看到不同之处(我在示例中使用的是blurRadius 2)。
您还会注意到,如果我们不将a传递thumbnailSource
给ProgressiveImage
组件,则一切正常,这意味着即使我们没有缩略图,我们也可以将其用于所有远程图像。
动画过渡
我们要做的最后一件事是使背景颜色,缩略图图像和全尺寸图像之间的过渡变得平滑。为此,我们将使用Animated
React Native中的库。
一旦导入Animated
,你会再想更换Image
的部件ProgressiveImage
用Animated.Image
。
您还需要在组件上创建两个新的动画变量,默认将它们设置为0。
1// ProgressiveImage.jsimport React from 'react';
2import { View, StyleSheet, Animated } from 'react-native';// ...class ProgressiveImage extends React.Component {
3 thumbnailAnimated = new Animated.Value(0); imageAnimated = new Animated.Value(0); render() {
4 const {
5 thumbnailSource,
6 source,
7 style,
8 ...props
9 } = this.props; return (
10 <View style={styles.container}>
11 <Animated.Image
12 {...props}
13 source={thumbnailSource}
14 style={style}
15 blurRadius={2}
16 />
17 <Animated.Image
18 {...props}
19 source={source}
20 style={[styles.imageOverlay, style]}
21 />
22 </View>
23 );
24 }
25}
这些Animated.Value
将用于驱动图像的不透明度。加载缩略图时,我们将设置thumbnailAnimated
为1。加载全尺寸图片时,我们将设置imageAnimated
为1。
1// ProgressiveImage.js// ...class ProgressiveImage extends React.Component {
2 thumbnailAnimated = new Animated.Value(0); imageAnimated = new Animated.Value(0); handleThumbnailLoad = () => {
3 Animated.timing(this.thumbnailAnimated, {
4 toValue: 1,
5 }).start();
6 } onImageLoad = () => {
7 Animated.timing(this.imageAnimated, {
8 toValue: 1,
9 }).start();
10 } // ...
11}
这些功能将通过组件的onLoad
属性调用Animated.Image
。
1// ProgressiveImage.js// ...class ProgressiveImage extends React.Component {
2 // ... render() {
3 const {
4 thumbnailSource,
5 source,
6 style,
7 ...props
8 } = this.props; return (
9 <View style={styles.container}>
10 <Animated.Image
11 {...props}
12 source={thumbnailSource}
13 style={[style, { opacity: this.thumbnailAnimated }]}
14 onLoad={this.handleThumbnailLoad}
15 blurRadius={1}
16 />
17 <Animated.Image
18 {...props}
19 source={source}
20 style={[styles.imageOverlay, { opacity: this.imageAnimated }, style]}
21 onLoad={this.onImageLoad}
22 />
23 </View>
24 );
25 }
26}export default ProgressiveImage;
这将导致最终的渐进式图像加载。
最终代码可以在Github(https://github.com/HandlebarLabs/react-native-examples-and-tutorials/tree/master/tutorials/progressive-image-loading)上找到。
欢迎关注公众号