React Native Academy · Leçon

Zoom par pincement et rotation

Combinez PinchGestureHandler et RotationGestureHandler avec la reconnaissance simultanée des gestes pour créer un composant d'image zoomable et rotatif.

Leçon 4 sur 413 étapes

Zoom par pincement et rotation est une leçon React Native Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage React Native Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours React Native Academy comprend 4 leçons au total.

Pourquoi le pincement et la rotation sont importants

Le zoom par pincement et la rotation sont des interactions emblématiques sur mobile, que l’on retrouve dans les visionneuses de photos, les cartes et les applications de documents. Leur bonne implémentation nécessite une reconnaissance des gestes multipoints exécutée sur le thread natif, ainsi que des mises à jour fluides et en temps réel des transformations via les valeurs partagées de Reanimated.

React Native Gesture Handler fournit les détecteurs Gesture.Pinch() et Gesture.Rotation() qui, combinés à useAnimatedStyle et useSharedValue de Reanimated, produisent des interactions extrêmement fluides, impossibles à distinguer d’implémentations natives.

Gesture.Pinch : détecter le facteur d’échelle

Gesture.Pinch() reconnaît un geste de pincement ou d’écartement à deux doigts. La valeur la plus importante de l’objet d’événement est event.scale : elle commence à 1.0 au début du geste et évolue proportionnellement à la distance entre les doigts. Une valeur de 2.0 signifie que les doigts sont deux fois plus éloignés qu’au départ.

Vous devez combiner le facteur d’échelle progressif du geste avec un facteur d’échelle de base enregistré afin d’accumuler le zoom sur plusieurs gestes de pincement ; sinon, chaque nouveau pincement réinitialise la valeur à 1.0.

import { Gesture } from 'react-native-gesture-handler';
import { useSharedValue, withSpring } from 'react-native-reanimated';

const scale = useSharedValue(1);
const savedScale = useSharedValue(1);

const pinch = Gesture.Pinch()
  .onUpdate((e) => {
    scale.value = savedScale.value * e.scale;
  })
  .onEnd(() => {
    savedScale.value = scale.value;
  });

Limiter la plage de zoom

Sans limitation, les utilisateurs peuvent effectuer un zoom jusqu’à des facteurs d’échelle énormes ou dézoomer jusqu’à faire disparaître l’image. Définissez un niveau de zoom minimal et maximal, puis limitez la valeur d’échelle dans la fonction de rappel onUpdate à l’aide d’un worklet utilitaire.

La fonction de limitation s’exécute sur le thread de l’interface utilisateur sous forme de worklet, ce qui maintient une réponse rapide aux gestes. Choisissez des valeurs adaptées à votre contenu : les visionneuses de photos autorisent généralement un zoom de 1x à 5x, tandis que les cartes peuvent accepter des plages bien plus larges.

const MIN_SCALE = 1;
const MAX_SCALE = 5;

function clamp(value, min, max) {
  'worklet';
  return Math.min(Math.max(value, min), max);
}

const pinch = Gesture.Pinch()
  .onUpdate((e) => {
    scale.value = clamp(savedScale.value * e.scale, MIN_SCALE, MAX_SCALE);
  })
  .onEnd(() => {
    savedScale.value = scale.value;
    // Snap back if over-zoomed out:
    if (scale.value < MIN_SCALE) {
      scale.value = withSpring(MIN_SCALE);
      savedScale.value = MIN_SCALE;
    }
  });

Gesture.Rotation : suivre l’angle

Gesture.Rotation() suit la variation d’angle d’un geste de torsion à deux doigts. La valeur event.rotation est exprimée en radians : elle représente l’angle de rotation des doigts depuis la position de départ du geste. Une rotation complète dans le sens horaire correspond à environ 6,28 radians (2π).

Comme pour le facteur d’échelle, vous devez accumuler la rotation sur plusieurs gestes en enregistrant la rotation actuelle à la fin du geste, puis en ajoutant la rotation du nouveau geste au décalage enregistré lors des gestes suivants.

const rotation = useSharedValue(0);
const savedRotation = useSharedValue(0);

const rotationGesture = Gesture.Rotation()
  .onUpdate((e) => {
    rotation.value = savedRotation.value + e.rotation;
  })
  .onEnd(() => {
    savedRotation.value = rotation.value;
  });

Appliquer le facteur d’échelle et la rotation dans useAnimatedStyle

Le tableau transform de useAnimatedStyle applique à la fois le facteur d’échelle et la rotation. L’ordre des transformations est important : appliquez d’abord le facteur d’échelle, puis la rotation (ou l’inverse selon le comportement souhaité). Le tableau de transformations de React Native accepte directement les valeurs numériques d’échelle et les valeurs de rotation en radians provenant des valeurs partagées.

La transformation rotate nécessite une chaîne se terminant par le suffixe 'rad', tandis que scale accepte directement un nombre. Utilisez la concaténation de chaînes (et non des littéraux de modèle avec des accents graves) pour construire la chaîne de rotation dans un worklet.

const animatedStyle = useAnimatedStyle(() => ({
  transform: [
    { scale: scale.value },
    { rotate: rotation.value + 'rad' }, // concatenate string, no backticks
  ],
}));

return (
  <GestureDetector gesture={composedGesture}>
    <Animated.Image
      source={{ uri: imageUrl }}
      style={[styles.image, animatedStyle]}
    />
  </GestureDetector>
);

Composer simultanément le pincement et la rotation

Une visionneuse de photos doit permettre d’utiliser le pincement et la rotation en même temps. Regroupez les deux gestes dans Gesture.Simultaneous et transmettez le geste composé à un seul GestureDetector. L’utilisateur peut ainsi zoomer et faire pivoter l’image avec un geste à deux doigts, de manière fluide.

L’ajout simultané du balayage crée une expérience complète de visionneuse de photos : zoomer, faire pivoter et repositionner l’image avec deux doigts à la fois.

const composedGesture = Gesture.Simultaneous(
  pinch,
  rotationGesture,
  pan
);

return (
  <GestureDetector gesture={composedGesture}>
    <Animated.Image
      source={{ uri: imageUrl }}
      style={[styles.image, animatedStyle]}
      resizeMode='contain'
    />
  </GestureDetector>
);

Réinitialiser le zoom par double appui

Une fonctionnalité courante des visionneuses de photos consiste à appuyer deux fois pour basculer entre la taille naturelle et une vue agrandie, puis à appuyer deux fois à nouveau pour réinitialiser. Utilisez Gesture.Tap().numberOfTaps(2) pour le détecteur de double appui, puis, dans son gestionnaire, animez le facteur d’échelle et la rotation afin de les ramener à leurs valeurs initiales avec un ressort.

Utilisez Gesture.Exclusive, en donnant la priorité au geste de double appui par rapport à celui d’appui simple, afin d’empêcher l’appui simple de se déclencher pendant une séquence de double appui.

const doubleTap = Gesture.Tap()
  .numberOfTaps(2)
  .onEnd(() => {
    if (scale.value > 1) {
      // Reset to natural size:
      scale.value = withSpring(1);
      savedScale.value = 1;
      rotation.value = withSpring(0);
      savedRotation.value = 0;
      offsetX.value = withSpring(0);
      offsetY.value = withSpring(0);
    } else {
      // Zoom to 2x:
      scale.value = withSpring(2);
      savedScale.value = 2;
    }
  });

Point de pivot du zoom par pincement

Par défaut, la transformation d’échelle agrandit ou réduit l’image autour de son centre. Pour un comportement plus naturel, l’image doit zoomer autour du point focal, c’est-à-dire le milieu entre les deux doigts. L’événement du geste fournit event.focalX et event.focalY à cette fin.

Pour implémenter un zoom autour du point focal, vous devez ajuster le décalage de translation en même temps que le facteur d’échelle afin que le point situé sous les doigts reste fixe pendant le zoom. Ce calcul consiste à déterminer la variation du décalage causée par la variation d’échelle au niveau d’un point focal donné.

const pinch = Gesture.Pinch()
  .onUpdate((e) => {
    const newScale = clamp(savedScale.value * e.scale, MIN_SCALE, MAX_SCALE);
    const scaleDelta = newScale / scale.value;
    // Adjust offset to keep focal point fixed:
    offsetX.value = e.focalX - scaleDelta * (e.focalX - offsetX.value);
    offsetY.value = e.focalY - scaleDelta * (e.focalY - offsetY.value);
    scale.value = newScale;
  });

Contraintes aux limites après un balayage et un zoom

Après un zoom avant, l’utilisateur peut balayer l’image pour en voir différentes parties. Vous devez toutefois limiter ce balayage afin qu’il soit impossible de déplacer l’image au point de révéler une zone vide au-delà de ses bords. Calculez le décalage maximal du balayage en fonction du facteur d’échelle actuel et des dimensions de l’image, puis limitez le décalage dans le onUpdate du balayage.

C’est ce niveau de finition qui distingue une visionneuse de photos professionnelle d’un prototype rudimentaire. La logique de contrainte se résume à quelques lignes de calcul exécutées sous forme de worklets sur le thread de l’interface utilisateur.

const IMAGE_WIDTH = 300;
const CONTAINER_WIDTH = 375;

const pan = Gesture.Pan()
  .onUpdate((e) => {
    const maxOffsetX = Math.max(0, (scale.value * IMAGE_WIDTH - CONTAINER_WIDTH) / 2);
    offsetX.value = clamp(
      savedOffsetX.value + e.translationX,
      -maxOffsetX,
      maxOffsetX
    );
  });

Animer le retour après un zoom excessif

Lorsque l’utilisateur relève ses doigts après avoir dépassé les limites de zoom (en dessous du minimum ou au-dessus du maximum), ramenez le facteur d’échelle à la limite avec un ressort au lieu de laisser l’image à une taille inhabituelle. Faites-le dans la fonction de rappel onEnd du geste de pincement.

De même, lorsque l’utilisateur dézoome jusqu’à 1x, réinitialisez le décalage du balayage à zéro afin que l’image revienne au centre. Cette combinaison de vérifications des limites dans onEnd rend le composant soigné et prévisible.

const pinch = Gesture.Pinch()
  .onEnd(() => {
    if (scale.value < MIN_SCALE) {
      scale.value = withSpring(MIN_SCALE);
      savedScale.value = MIN_SCALE;
      // Reset pan to center:
      offsetX.value = withSpring(0);
      offsetY.value = withSpring(0);
    } else if (scale.value > MAX_SCALE) {
      scale.value = withSpring(MAX_SCALE);
      savedScale.value = MAX_SCALE;
    } else {
      savedScale.value = scale.value;
    }
  });

Tout assembler

Une visionneuse de photos complète, zoomable, rotative et déplaçable combine : des valeurs partagées pour le facteur d’échelle, la rotation et le décalage ; useAnimatedStyle qui applique toutes les transformations ; Gesture.Simultaneous qui compose le pincement, la rotation, le balayage et le double appui ; ainsi que la limitation aux limites dans onUpdate et la correction avec un ressort dans onEnd.

Il s’agit de l’une des interactions les plus impressionnantes que vous puissiez créer avec React Native. Elle s’exécute entièrement sur le thread de l’interface utilisateur, sans intervention du thread JavaScript pendant le geste, ce qui la rend impossible à distinguer d’une implémentation native.

const composed = Gesture.Simultaneous(
  Gesture.Exclusive(
    doubleTap,
    singleTap
  ),
  pan,
  pinch,
  rotationGesture
);

const style = useAnimatedStyle(() => ({
  transform: [
    { translateX: offsetX.value },
    { translateY: offsetY.value },
    { scale: scale.value },
    { rotate: rotation.value + 'rad' },
  ],
}));

Vérification rapide

Vérifiez votre compréhension des concepts du développement mobile avec React Native abordés dans cette leçon.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris que Gesture.Pinch() fournit event.scale pour suivre le zoom à deux doigts par rapport au début du geste, que Gesture.Rotation() fournit event.rotation en radians pour suivre la torsion à deux doigts, et que Gesture.Simultaneous compose le pincement, la rotation et le balayage afin que les trois fonctionnent simultanément pour une expérience complète de visionneuse de photos. Ensuite, nous configurerons des schémas d’URL personnalisés pour établir des liens profonds vers des écrans précis de l’application.

Gratuit pour commencer

Apprends JavaScript avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
30
Leçons
120

Questions Fréquemment Posées

La leçon « Zoom par pincement et rotation » est-elle gratuite ?

Oui — le texte complet de « Zoom par pincement et rotation » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours React Native Academy, passe à CoddyKit PRO. Le cours React Native Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Zoom par pincement et rotation » ?

Combinez PinchGestureHandler et RotationGestureHandler avec la reconnaissance simultanée des gestes pour créer un composant d'image zoomable et rotatif. Tu pratiques React Native Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer React Native Academy ?

Aucune expérience préalable n'est requise. React Native Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.

Combien de temps prend la leçon « Zoom par pincement et rotation » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon React Native Academy ?

Oui. Chaque leçon React Native Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Installer Reanimated et les valeurs partagées
  2. Worklets et exécution du code sur le thread d'interface
  3. Gestes de glissement et balayage avec Gesture Handler
  4. Zoom par pincement et rotation
← Retour à React Native Academy