Thibault Mocellin

Thibault Mocellin

Développeur Full-Stack freelance basé à Annecy 🇫🇷

Progressive Image avec React Native

Posted on June 16, 2017

Dans le cas des applications mobiles les images sont souvent problématiques à cause de leurs temps de chargement lorsque la connexion internet est faible. Durant ce temps on se retrouve souvent avec une zone vide ne renvoyant aucun feedback visuel à l’utilisateur.

Pour palier à ce problème une des bonnes pratiques consiste à afficher en premier un version avec une très basse résolution et généralement flouté de l’image que l’on souhaite afficher. Ensuite une fois l’image avec la résolution normale chargée on fait disparaitre la première au profit de la seconde.

Nous allons donc mettre en place ce système dans cet article.

Commençons par créer le projet :

react-native init ProgressiveImageSample

Dans cet exemple notre image sera en plein écran nous utiliserons donc Dimensions pour définir la largeur et la hauteur. Ensuite pour ce qui est des images nous utiliserons Unsplash.

Dans le fichier index.ios.js rajoutez le code suivant au dessus de la déclaration de la classe :

const { width, height } = Dimensions.get('window')
const picture = `https://unsplash.it/${width}/${height}?image=214`
const preview = 'https://unsplash.it/80/120?image=214&blur'

Ici on récupère donc la hauteur et la largeur puis on définit les urls de l’image et l’image “preview”.

Ensuite nous allons ajouter l’image de preview :

export default class ProgressiveImageSample extends Component {
  constructor(props) {
    super(props)
    this.state = {
      isLoading: true,
    }
  }

  onPreviewLoad() {
    this.setState({ isLoading: false })
  }

  render() {
    return (
      <View style={styles.container}>
        <Image
          style={{
            opacity: 1,
            width: width,
            height: height,
            position: 'absolute',
          }}
          source={{ uri: preview }}
          onLoad={() => this.onPreviewLoad()}
        />
        {this.renderLoader()}
      </View>
    )
  }

  renderLoader() {
    if (this.state.isLoading) {
      return <ActivityIndicator size="small" color="white" animating={true} />
    }
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'black',
  },
})

On définit au chargement du composant que l’image est entrain de se charger grace à :

this.state = {
  isLoading: true,
}

Cela va nous permettre d’afficher un ActivityIndicator grace à la méthode renderLoader() le temps que la preview se charge. Ensuite une fois l’image chargée on met à jour le state grace à la méthode onPreviewLoad().

Maintenant nous allons rajouter l’image principale que l’on souhaite afficher :

render() {
    return (
      <View style={styles.container}>
        <Image
          resizeMode={'cover'}
          style={{ position: 'absolute', width: width, height: height }}
          source={{ uri: picture }}
          onLoad={() => this.onLoad()} />
        <Image
          style={{
            opacity: 1,
            width: width,
            height: height,
            position: 'absolute'
          }}
          source={{ uri: preview }}
          onLoad={() => this.onPreviewLoad()} />
        {this.renderLoader()}
      </View>
    );
  }

Pour l’instant une fois l’image principale chargée elle reste cachée derrière la preview. Ce que nous allons faire c’est passer progressivement l’opacité de la preview à 0 une fois l’image principale chargée. Pour ce faire nous allons utilisé les animations.

Rajoutez Animated dans la liste des imports depuis react-native, ensuite déclarez une nouvelle Animated.Value dans le constructeur :

this.previewOpacity = new Animated.Value(0)

Puis remplacer les deux images par des Animated.Image ainsi que la valeur de l’opacité de preview par l’AnimatedValue :

render() {
    return (
      <View style={styles.container}>
        <Animated.Image
          resizeMode={'cover'}
          style={{ position: 'absolute', width: width, height: height }}
          source={{ uri: picture }}
          onLoad={() => this.onLoad()} />
        <Animated.Image
          style={{
            opacity: this.previewOpacity,
            width: width,
            height: height,
            position: 'absolute'
          }}
          source={{ uri: preview }}
          onLoad={() => this.onPreviewLoad()} />
        {this.renderLoader()}
      </View>
    );
  }

Afin de pouvoir afficher progressivement la preview une fois chargée nous allons changer la valeur previewOpacity dans la méthode onPreviewLoad :

onPreviewLoad() {
    Animated.timing(this.previewOpacity, {
      toValue: 1,
      duration: 250
    }).start();
    this.setState({ isLoading: false });
}

Enfin une fois l’image chargée nous allons repasser la valeur previewOpacity à 0 dans la méthode onLoad :

onLoad() {
    Animated.timing(this.previewOpacity, {
      toValue: 0,
      duration: 350
    }).start();
  }

Voici le résultat final :

Le code source est disponible ici.