Tout le monde veut pouvoir accéder au web depuis son téléphone mobile de nos jours. Il est donc important de considérer l'accessibilité de votre application web sur Android et iPhones.

La partie la plus difficile est de construire la navigation – et le glissement en particulier peut causer beaucoup de maux de tête. Il existe des bibliothèques que vous pouvez utiliser pour vous aider avec cela, mais pourquoi ne pas le construire vous-même ?

Dans ce tutoriel, je vais vous apprendre à créer votre propre composant de glissement mobile dans React. Vous pouvez le faire en 50 lignes de JavaScript.

Initialement, j'ai créé ce composant pour mon projet personnel, le jeu 2048 en React, et j'ai maintenant décidé de partager comment je l'ai fait.

Si vous voulez l'essayer avant de lire l'article complet, vous pouvez jouer au jeu ici (utilisez votre appareil mobile). Dans ce tutoriel, nous nous concentrerons uniquement sur le glissement mobile.

Vous pouvez trouver les ressources suivantes sur GitHub :

Voici un aperçu (la qualité n'est pas la meilleure car j'ai essayé de garder la taille petite) :

Image Le résultat final dans mon projet personnel

Table des matières :

  1. Prérequis
  2. Comment simuler un appareil mobile dans votre navigateur
  3. Le composant MobileSwiper
  4. Rendons-le glissable
  5. Résumé

🛠fe0f Prérequis

Avant de commencer, assurez-vous de connaître un peu React et JavaScript. Vous n'avez pas besoin d'outils sophistiqués, mais si vous voulez exécuter le projet d'exemple sur votre ordinateur, vous devrez d'abord installer Node.js.

De plus, j'utilise Google Chrome pour simuler un appareil mobile sur mon ordinateur. Gardez à l'esprit que si vous utilisez un autre navigateur, les étapes peuvent être légèrement différentes.

🔍 Comment simuler un appareil mobile dans votre navigateur

Avant de commencer à coder, je dois vous montrer comment simuler un appareil mobile dans Google Chrome.

Ouvrez votre navigateur, faites un clic droit sur votre page et choisissez "Inspecter" dans le menu déroulant.

Image Comment ouvrir les outils de développement dans Google Chrome

La vue de votre navigateur devrait maintenant être divisée en deux parties. La deuxième partie contient les outils de développement de Chrome.

Maintenant, cliquez sur l'icône "ordinateur portable et mobile" (1 dans l'image ci-dessous) dans le coin supérieur gauche des outils de développement. Ensuite, sélectionnez les "Dimensions" (2) de l'appareil que vous souhaitez simuler. J'ai choisi l'iPhone SE car cet appareil a la plus petite résolution prise en charge par mon jeu.

Image Choisir les dimensions de l'appareil dans les outils de développement (Google Chrome)

Maintenant, nous sommes prêts à simuler le glissement en utilisant notre souris ou notre pavé tactile. Faites simplement attention au GIF ci-dessous – mon curseur s'est transformé en un cercle qui est censé visualiser la zone couverte par le doigt d'une personne.

Lorsque j'essaie de glisser, mon navigateur pense que je fais défiler le site web. Dans mon cas, ce n'est pas un comportement attendu. Je voudrais plutôt utiliser les glissements pour jouer au jeu.

Image Par défaut, les événements de défilement sont associés aux glissements

🤓 Le composant MobileSwiper

Tout d'abord, nous devons créer un fichier mobile-swiper.jsx. Dans ce fichier, nous déclarons un composant React standard. Notre composant doit accepter deux propriétés – children et onSwipe.

export default function MobileSwiper({ children, onSwipe }) {
    return null   
}

Permettez-moi de les expliquer brièvement :

  • children permet à MobileSwiper d'accepter et de rendre tout contenu placé entre ses balises d'ouverture et de fermeture. Cela vous permet d'injecter le tableau entier à l'intérieur de ce composant – mais nous y viendrons plus tard.
  • onSwipe est un rappel que notre composant déclenchera chaque fois que l'utilisateur glisse dans la zone "glissable".

Définissons maintenant la zone glissable. Tout d'abord, vous devez ajouter une référence à l'élément DOM dans lequel vous souhaitez permettre le glissement. Vous pouvez le faire en utilisant le hook useRef. Déclarez donc une constante appelée wrapperRef :

import { useRef } from "react"

export default function MobileSwiper({ children, onSwipe }) {
    const wrapperRef = useRef(null)

    return null

}

Note de côté : useRef est un hook React qui vous permet de référencer une valeur qui n'est pas nécessaire pour le rendu. Il est particulièrement courant de l'utiliser pour manipuler le DOM. React a un support intégré pour cela.

Maintenant, vous devez simplement retourner le <div /> qui référence la constante que vous avez créée en utilisant le hook useRef.

import { useRef } from "react"

export default function MobileSwiper({ children, onSwipe }) {
    const wrapperRef = useRef(null)

    return <div ref={wrapperRef}>{children}</div>
}

Réfléchissons un instant à la manière dont nous pouvons détecter le glissement. Je crois que la manière la plus simple est de comparer les positions de départ et de fin du doigt de l'utilisateur.

Cela signifie que nous devons stocker la position initiale du doigt de l'utilisateur dans l'état. Basiquement, nous stockerons les coordonnées x et y en utilisant le hook useState.

import { useState, useRef } from "react"

export default function MobileSwiper({ children, onSwipe }) {
    const wrapperRef = useRef(null)
    const [startX, setStartX] = useState(0)
    const [startY, setStartY] = useState(0)    

    return <div ref={wrapperRef}>{children}</div>
}

Maintenant, nous devons créer deux rappels d'événements :

  • handleTouchStart définira la position de départ du doigt de l'utilisateur.
  • handleTouchEnd définira la position finale du doigt de l'utilisateur et calculera le décalage (delta) basé sur le point de départ.

Commençons par le gestionnaire d'événements handleTouchStart :

import { useCallback, useState, useRef } from "react"

export default function MobileSwiper({ children, onSwipe }) {
    const wrapperRef = useRef(null)
    const [startX, setStartX] = useState(0)
    const [startY, setStartY] = useState(0)    

    const handleTouchStart = useCallback((e) => {
        if (!wrapperRef.current.contains(e.target)) {
              return
        }

        e.preventDefault()

        setStartX(e.touches[0].clientX)
        setStartY(e.touches[0].clientY)
      }, [])

    return <div ref={wrapperRef}>{children}</div>
}

Permettez-moi de l'expliquer brièvement :

  1. J'ai enveloppé cet assistant dans le hook useCallback pour le mettre en cache et améliorer ses performances. Si vous ne connaissez pas ce hook, vous pouvez le lire dans la documentation officielle de React.
  2. L'instruction if vérifie si l'utilisateur glisse dans la zone glissable. S'il glisse en dehors de cette zone, nous procéderons au défilement.
  3. e.preventDefault() désactive l'événement de défilement par défaut.
  4. Les deux dernières lignes stockent les coordonnées x et y dans l'état.

Maintenant, implémentons le gestionnaire d'événements handleTouchEnd :

import { useCallback, useState, useRef } from "react"

export default function MobileSwiper({ children, onSwipe }) {
    const wrapperRef = useRef(null)
    const [startX, setStartX] = useState(0)
    const [startY, setStartY] = useState(0)    

    const handleTouchStart = useCallback((e) => {
        if (!wrapperRef.current.contains(e.target)) {
              return
        }

        e.preventDefault()

        setStartX(e.touches[0].clientX)
        setStartY(e.touches[0].clientY)
    }, [])

      const handleTouchEnd = useCallback((e) => {
        if (!wrapperRef.current.contains(e.target)) {
            return
        }

        e.preventDefault()

        const endX = e.changedTouches[0].clientX
        const endY = e.changedTouches[0].clientY
        const deltaX = endX - startX
        const deltaY = endY - startY

        onSwipe({ deltaX, deltaY })
    }, [startX, startY, onSwipe])

    return <div ref={wrapperRef}>{children}</div>
}

Comme vous pouvez le voir, les étapes 1-3 sont exactement les mêmes que dans le rappel handleTouchStart. Maintenant, nous allons prendre les coordonnées finales x et y du doigt de l'utilisateur et soustraire les coordonnées initiales x et y de celles-ci. Grâce à cela, nous pouvons calculer le décalage horizontal et vertical du doigt de l'utilisateur (deltas).

Ensuite, nous passons ces deltas au rappel onSwipe. Si vous vous souvenez, nous l'avons déclaré dans les props du composant au début.

Maintenant, nous devons connecter ces rappels à l'écouteur d'événements. Pour ce faire, nous pouvons utiliser le hook useEffect de React.

import { useCallback, useEffect, useState, useRef } from "react"

export default function MobileSwiper({ children, onSwipe }) {
    const wrapperRef = useRef(null)
    const [startX, setStartX] = useState(0)
    const [startY, setStartY] = useState(0)    

    const handleTouchStart = useCallback((e) => {
        if (!wrapperRef.current.contains(e.target)) {
              return
        }

        e.preventDefault()

        setStartX(e.touches[0].clientX)
        setStartY(e.touches[0].clientY)
      }, [])

      const handleTouchEnd = useCallback(
    (e) => {
        if (!wrapperRef.current.contains(e.target)) {
            return
        }

        e.preventDefault()

        const endX = e.changedTouches[0].clientX
        const endY = e.changedTouches[0].clientY
        const deltaX = endX - startX
        const deltaY = endY - startY

        onSwipe({ deltaX, deltaY })
    }, [startX, startY, onSwipe])   

     useEffect(() => {
        window.addEventListener("touchstart", handleTouchStart)
        window.addEventListener("touchend", handleTouchEnd)

        return () => {
            window.removeEventListener("touchstart", handleTouchStart)
            window.removeEventListener("touchend", handleTouchEnd)
        }
      }, [handleTouchStart, handleTouchEnd])

    return <div ref={wrapperRef}>{children}</div>
}

Vous pouvez en lire plus sur le hook useEffect dans la documentation officielle de React.

Le composant MobileSwiper est maintenant prêt.

🔌 Rendons-le glissable

La dernière chose que nous devons faire est de connecter notre composant à l'application. Comme je l'ai mentionné, j'utiliserai ce composant dans mon jeu 2048 (Code source). Si vous voulez l'utiliser ailleurs, le helper handleSwipe et le composant MobileSwiper resteront les mêmes.

Intégrons-le dans le composant Board :

import { useCallback, useContext, useEffect, useRef } from "react"
import { Tile as TileModel } from "@/models/tile"
import styles from "@/styles/board.module.css"
import Tile from "./tile"
import { GameContext } from "@/context/game-context"
import MobileSwiper from "./mobile-swiper"

export default function Board() {
    const { getTiles, moveTiles, startGame } = useContext(GameContext);

    // ... parties non pertinentes supprimées ...

    const handleSwipe = useCallback(({ deltaX, deltaY }) => {
        if (Math.abs(deltaX) > Math.abs(deltaY)) {
            if (deltaX > 0) {
                moveTiles("move_right")
            } else {
                moveTiles("move_left")
            }
        } else {
            if (deltaY > 0) {
                moveTiles("move_down")
            } else {
                moveTiles("move_up")
            }
        }
    }, [moveTiles])

     // ... parties non pertinentes supprimées ...

    return (
        <MobileSwiper onSwipe={handleSwipe}>
              <div className={styles.board}>
                <div className={styles.tiles}>{renderTiles()}</div>
                <div className={styles.grid}>{renderGrid()}</div>
              </div>
        </MobileSwiper>
      )
}

Commençons par le gestionnaire handleSwipe. Comme vous pouvez le voir, nous comparons si deltaX est supérieur à deltaY pour décider si l'utilisateur a glissé horizontalement (gauche / droite) ou verticalement (haut / bas).

Si c'était un glissement horizontal, alors :

  • deltaX négatif signifie qu'ils ont glissé vers la gauche.
  • deltaX positif signifie qu'ils ont glissé vers la droite.

Si c'était un glissement vertical, alors :

  • deltaY négatif signifie qu'ils ont glissé vers le haut.
  • deltaY positif signifie qu'ils ont glissé vers le bas.

Maintenant, concentrons-nous sur le composant MobileSwiper. Vous pouvez le trouver dans l'instruction return. Nous passons le helper handleSwipe à la propriété onSwipe et enveloppons tout le code HTML du composant Board pour activer le glissement dessus.

Maintenant, lorsque nous l'essayons, le résultat n'est pas idéal. Les événements de défilement sont mélangés avec les glissements mobiles, comme vous pouvez le voir ci-dessous :

Image Tableau avec des événements dupliqués – glissements et défilement

Cela se produit parce que les navigateurs modernes utilisent des écouteurs d'événements passifs pour améliorer l'expérience de défilement sur les appareils mobiles. Cela signifie que le preventDefault que nous avons ajouté à nos rappels d'événements ne se produit jamais.

Pour désactiver le comportement de défilement, nous devons désactiver les écouteurs passifs sur le composant MobileSwiper :

import { useCallback, useEffect, useState, useRef } from "react"

export default function MobileSwiper({ children, onSwipe }) {
    // ... supprimé pour améliorer la visibilité ...

     useEffect(() => {
        window.addEventListener("touchstart", handleTouchStart, { passive: false })
        window.addEventListener("touchend", handleTouchEnd, { passive: false })
        // ... supprimé pour améliorer la visibilité ...
      }, [handleTouchStart, handleTouchEnd])

    // ... supprimé pour améliorer la visibilité ...
}

Maintenant, le comportement de défilement a disparu et le jeu 2048 est simplement génial :

Image Résultat final - le glissement fonctionne !

🥋 Résumé

Aujourd'hui, je vous ai montré que vous n'avez pas toujours besoin de bibliothèques pour gérer les gestes mobiles dans React. Certains événements simples comme le glissement peuvent être implémentés en utilisant des fonctionnalités de base de React. Nous avons simplement utilisé un ensemble de hooks React et écrit deux gestionnaires d'événements simples.

L'implémentation complète a exactement 50 lignes de code. J'espère vous avoir inspiré à essayer de gérer les événements mobiles par vous-même.

Si cet article vous a aidé, faites-le moi savoir sur Twitter. Les éducateurs comme moi ont souvent l'impression de parler dans le vide et que personne ne se soucie de ce que nous enseignons. Un simple "shoutout" montre que cela valait l'effort et m'inspire à créer plus de contenu comme celui-ci.

Veuillez partager cet article sur vos réseaux sociaux. Merci !

🎥 Créez votre propre jeu 2048

Cet article fait partie de mon cours sur Udemy où j'enseigne comment créer un jeu 2048 entièrement fonctionnel dans Next.js à partir de zéro.

🧑200d🎓 Rejoignez mon cours Next.js sur Udemy

Utilisez le code FREECODECAMP pour vous inscrire et obtenir une réduction de 60%.

Je crois que la programmation devrait être amusante et libérer la créativité. Vous n'avez pas à construire une autre liste de tâches ou un panier d'achat. Au lieu de cela, vous pouvez construire quelque chose que vous pouvez montrer à vos amis ou peut-être même à un responsable de recrutement !

PS. Si vous préférez regarder des screencasts, cette leçon est disponible gratuitement sur Udemy. Vous pouvez la trouver sous la section "Responsive Layout and Missing Game Feature" dans la conférence intitulée "Game layout and mobile swipes".