DevGang
Авторизоваться

Как создать компонент мобильного смахивания в React

Сегодня каждый хочет иметь доступ к Интернету со своих мобильных телефонов. Поэтому важно учитывать доступность вашего веб-приложения на Android и iPhone.

Самая сложная часть — создание навигации, и особенно смахивание может вызвать много головной боли. Есть библиотеки, которые вы можете использовать, чтобы помочь в этом, но почему бы не создать их самостоятельно?

В этом уроке я научу вас, как создать собственный компонент мобильного смахивания в React. Вы можете сделать это в 50 строках JavaScript.

Изначально я создал этот компонент для своего любимого проекта — игры 2048 в React, а теперь решил поделиться тем, как я это сделал.

Если вы хотите опробовать ее, прежде чем читать всю статью, вы можете поиграть в игру здесь (используйте свое мобильное устройство). В этом уроке мы сосредоточимся только на мобильных устройствах.

На GitHub вы можете найти следующие ресурсы:

Вот краткий обзор (качество не самое лучшее, потому что я старался сохранить небольшой размер):

Конечный результат моего любимого проекта<br>
Конечный результат моего любимого проекта

🛠️ Подготовка

Прежде чем мы начнем, убедитесь, что вы немного знаете о React и JavaScript. Вам не потребуются какие-либо сложные инструменты, но если вы хотите запустить пример проекта на своем компьютере, вам сначала потребуется установить Node.js.

Кроме того, я использую Google Chrome для имитации мобильного устройства на своем компьютере. Имейте в виду, что если вы используете другой браузер, действия могут немного отличаться.

🔍 Как имитировать мобильное устройство в браузере

Прежде чем мы начнем программировать, мне нужно показать вам, как имитировать мобильное устройство в Google Chrome.

Откройте браузер, щелкните правой кнопкой мыши на своей странице и выберите «Проверить» в раскрывающемся меню.

Как открыть инструменты разработчика в Google Chrome
Как открыть инструменты разработчика в Google Chrome

Теперь представление вашего браузера должно быть разделено на две части. Вторая часть содержит инструменты разработчика Chrome.

Теперь нажмите значок «Ноутбук и мобильный телефон» (1 на изображении ниже) в верхнем левом углу инструментов разработчика. Затем выберите «Размеры» (2) устройства, которое вы хотите смоделировать. Я выбрал iPhone SE, потому что у этого устройства наименьшее разрешение, поддерживаемое моей игрой.

Выберите размеры устройства в инструментах разработчика (Google Chrome).
Выберите размеры устройства в инструментах разработчика (Google Chrome).

Теперь мы готовы имитировать смахивание с помощью мыши или тачпада. Просто обратите внимание на гифку ниже — мой курсор превратился в круг, который должен визуализировать область, покрытую пальцем человека.

Когда я пытаюсь провести пальцем по экрану, мой браузер думает, что я на самом деле пролистываю сайт. В моем случае это не ожидаемое поведение. Вместо этого я хотел бы использовать свайпы для игры.

По умолчанию события прокрутки связаны с пролистыванием.
По умолчанию события прокрутки связаны с пролистыванием.

🤓 Компонент MobileSwiper

Сначала нам нужно создать файл mobile-swiper.jsx. В этом файле мы объявляем стандартный компонент React. Наш компонент должен принимать два свойства — Children и onSwipe.

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

Позвольте мне кратко их объяснить:

  • Children позволяет MobileSwiper принимать и отображать любой контент, помещенный между открывающим и закрывающим тегами. Это позволяет вам вставить всю плату внутрь этого компонента, но мы вернемся к этому позже.
  • onSwipe — это обратный вызов, который наш компонент будет запускать каждый раз, когда пользователь проводит пальцем по «перелистываемой» области.

Давайте теперь определим область перелистывания. Во-первых, вам нужно добавить ссылку на элемент DOM, в котором вы хотите разрешить перелистывание. Вы можете сделать это, используя хук useRef. Итак, объявите константу с именем WrapperRef:

import { useRef } from "react"

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

    return null
    
} 

Примечание: useRef — это React Hook, который позволяет вам ссылаться на значение, которое не требуется для рендеринга. Его особенно часто используют для манипулирования DOM. В React есть встроенная поддержка для этого.

Теперь вам просто нужно вернуть <div />, который ссылается на константу, созданную вами с помощью перехватчика useRef.

import { useRef } from "react"

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

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

Давайте на мгновение задумаемся, как мы можем обнаружить смахивание. Я считаю, что самый простой способ — сравнить начальное и конечное положение пальца пользователя.

Это означает, что нам нужно сохранить в состоянии начальное положение пальца пользователя. По сути, мы будем хранить координаты x и y, используя хук 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>
} 

Теперь нам нужно создать два обратных вызова событий:

  • handleTouchStart установит начальную позицию пальца пользователя.
  • handleTouchEnd установит конечное положение пальца пользователя и рассчитает сдвиг (дельту) на основе начальной точки.

Начнем с обработчика событий 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>
} 

Позвольте мне кратко объяснить это:

  • Я включил этот помощник в хук useCallback, чтобы кэшировать и повысить его производительность. Если вы не знаете этот крючок, вы можете прочитать о нем в официальной документации React.
  • Оператор if проверяет, проводит ли пользователь пальцем по перелистываемой области. Если они проведут пальцем за пределы этой области, мы продолжим прокрутку.
  • e.preventDefault() отключает событие прокрутки по умолчанию.
  • Последние две строки хранят координаты x и y в состоянии.

Теперь давайте реализуем обработчик события 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>
} 

Как видите, шаги 1–3 точно такие же, как и в обратном вызове handleTouchStart. Теперь мы возьмем окончательные координаты x и y пальца пользователя и вычтем из них начальные x и y. Благодаря этому мы можем рассчитать горизонтальное и вертикальное смещение пальца пользователя (дельт).

Затем мы передаем эти отклонения в обратный вызов onSwipe. Если вы помните, мы объявили это в свойствах компонента вначале.

Теперь нам нужно подключить эти обратные вызовы к прослушивателю событий. Для этого мы можем использовать хук useEffect из 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>
} 

Подробнее о хуке useEffect можно прочитать в официальной документации React.

Компонент MobileSwiper готов.

🔌 Давайте сделаем его перелистываемым

Последнее, что нам нужно сделать, это подключить наш компонент к приложению. Как я уже упоминал, я буду использовать этот компонент в своей игре 2048 (исходный код). Если вы захотите использовать его где-то еще, помощник handleSwipe и компонент MobileSwiper останутся прежними.

Давайте подключим его к компоненту 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);
	
    // ... removed irrelevant parts ...
    
    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])

 	// ... removed irrelevant parts ...

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

Начнем с обработчика handleSwipe. Как видите, мы сравниваем, больше ли deltaX, чем deltaY, чтобы определить, провел ли пользователь горизонтально (влево/вправо) или вертикально (вверх/вниз).

Если это был горизонтальный свайп, то:

  • отрицательное deltaX означает, что они провели пальцем влево.
  • положительное deltaX означает, что они провели вправо.

Если это был вертикальный свайп, то:

  • отрицательная дельта означает, что они провели пальцем вверх.
  • положительная дельта означает, что они смахнули вниз.

Теперь давайте сосредоточимся на компоненте MobileSwiper. Вы можете найти его в операторе возврата. Мы передаем вспомогательный метод handleSwipe свойству onSwipe и обертываем весь HTML-код компонента Board, чтобы включить возможность пролистывания по нему.

Теперь, когда мы пробуем это, результат не идеален. События прокрутки смешаны с мобильными пролистываниями, как вы можете видеть ниже:

Доска с дублированными событиями – свайпы и прокрутка
Доска с дублированными событиями – свайпы и прокрутка

Это происходит потому, что современные браузеры используют пассивные прослушиватели событий для улучшения качества прокрутки на мобильных устройствах. Это означает, что preventDefault, которое мы добавили в наши обратные вызовы событий, никогда не произойдет.

Чтобы отключить поведение прокрутки, нам нужно отключить пассивные прослушиватели в компоненте MobileSwiper:

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

export default function MobileSwiper({ children, onSwipe }) {
	// ... removed to improve visibility ...
    
 	useEffect(() => {
        window.addEventListener("touchstart", handleTouchStart, { passive: false })
        window.addEventListener("touchend", handleTouchEnd, { passive: false })
        // ... removed to improve visibility ...
  	}, [handleTouchStart, handleTouchEnd])
    
	// ... removed to improve visibility ...
} 

Теперь поведение прокрутки исчезло, и игра 2048 выглядит просто потрясающе:

Конечный результат — смахивание работает!
Конечный результат — смахивание работает!

🏁 Краткое содержание

Сегодня я показал вам, что вам не всегда нужны библиотеки для обработки мобильных жестов в React. Некоторые простые события, такие как перелистывание, можно реализовать с помощью базовых функций React. Мы просто использовали кучу хуков React и написали два простых обработчика событий.

Вся реализация имеет ровно 50 строк кода. Надеюсь, я вдохновил вас попробовать разобраться с мобильными событиями самостоятельно.

Если эта статья помогла вам, пожалуйста, дайте мне знать в Twitter. Преподавателям вроде меня часто кажется, что мы говорим в вакууме, и никого не волнует, чему мы учим. Простое «приветствие» показывает, что усилия того стоили, и вдохновляет меня на создание большего количества подобного контента.

Пожалуйста, поделитесь этой статьей в своих социальных сетях. Спасибо!

🎥 Создайте свою собственную игру 2048

Эта статья является частью моего курса по Udemy, где я учу, как создать полнофункциональную игру 2048 в Next.js с нуля.

🧑‍🎓 Присоединяйтесь к моему курсу Next.js на Udemy.

Используйте код FREECODECAMP для регистрации и получите скидку 60%.

Я считаю, что программирование должно приносить удовольствие и раскрывать творческий потенциал. Вам не нужно создавать еще один список дел или корзину покупок. Вместо этого вы можете создать что-то, что сможете показать своим друзьям или, возможно, даже менеджеру по найму!

ПС. Если вы предпочитаете смотреть скринкасты, то этот урок доступен на Udemy бесплатно. Вы можете найти его в разделе «Адаптивный макет и недостающая функция игры» лекции «Макет игры и мобильные смахивания».

Источник:

#React
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу