Рубрики
Без рубрики

Создайте экран выбора символов в реакции

Найди меня на среднем React – это библиотека JavaScript, хорошо известная своей простотой, пока все еще имеется в состоянии … Помечено в React, JavaScript, Node, WebDev.

Найди меня на средний

РЕАКТ – это библиотека JavaScript, хорошо известная своей простоты, в то же время, в которой все еще могут создавать удивительные пользовательские интерфейсы. Огромные установленные сайты, такие как Facebook , Netflix и NY Times были очень успешными поддержанием своих веб-приложений, использующих реагирование. И благодаря Невероятно активное участие С библиотекой с открытым исходным кодом это становится только лучше каждый день.

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

Намерение этого поста – это очевидно, чтобы построить экран выбора символов, но и показать мыслительный процесс, когда мы идем и пролить свет на то, как решить проблемы до Они придумывают. Надеюсь, этот подход помогает более новым разработчикам реагирования!

К концу этого поста вы должны иметь возможность иметь что-то подобное:

Если вы хотите ссылку на REPO GitHUB, который включает в себя дополнительные символы, нажмите здесь Отказ

Я не буду использовать какие-либо библиотеки пользовательских интерфейсов, чтобы сделать любые эффекты в этом посте, помимо установки загрузки спиннера, потому что я хочу, чтобы все могли следовать, без необходимости исследовать API библиотеки, чтобы понять примеры.

И без дальнейшего ADO, давайте начнем!

( Обновить : Я хотел добавить больше вещей, но этот пост был смехотворно длительным!)

В этом руководстве мы собираемся быстро генерировать реагировать проект с Create-React-App

Продолжайте и создайте проект, используя команду ниже. Для этого учебника я позвоню нашему проекту символ – выберите Отказ

npx create-react-app character-select

Теперь перейдите в каталог, когда это сделано:

cd character-select

Внутри главной записи SRC/index.js мы собираемся очистить его немного:

import React from 'react'
import ReactDOM from 'react-dom'
import * as serviceWorker from './serviceWorker'
import App from './App'

ReactDOM.render(, document.getElementById('root'))
serviceWorker.unregister()

Вот начинающиеся стили:

SRC/styles.css.

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
    'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
    'Helvetica Neue', sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background: rgb(23, 30, 34);
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

.root {
  padding: 20px 0;
}

Теперь перейдите к SRC/App.js и начните с корневого элемента, поскольку у нас уже есть стили:

import React from 'react'
import styles from './styles.module.css'

const App = () => 
{null}
export default App

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

Позвольте сделать вид, что мы играем в игру MMORPG. Все игроки начинаются с создания персонажа. Каждый игрок начинает скиды с Новичок класс по умолчанию, и как только они сделают его до уровня 10, они могут превратить в класс уровня 2 (для этого поста у нас будет просто Колдунья и а Рыцарь Доступно, но MMORPG игры обычно имеют больше классов, таких как Archer и NecroMancer и т. Д.). После того, как они выберут класс для MORPH, когда они достигают уровня 10, они смогут нажать кнопку, которая говорит «Морф», и их персонаж изменит внешний вид. Между этими действиями они автоматически будут автоматически прокручивать, так как они идут так, чтобы мы сохраняем его интерактивным для игрока. Затем, в конце, который будет руководствоваться где-то еще на новый экран.

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

Если они выберут колдунья Они смогут превратиться в колдунья и продолжать движение с их путешествием, чтобы стать лучшим игроком в игре. Та же концепция идет на вариант рыцаря. Однако есть секретный вариант. Если проигрыватель умный и носи достаточно, они поймут, что они смогут выбрать оба Из них и Морф во что-то необычное, что человечество еще не свидетельствует. Жизнь полна сюрпризов, и наша игра должна отражать, что для того, чтобы оставаться реалистичными. Хе-хе.

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

(Изображения можно найти в ссылке GitHub выше, если вы хотите использовать одни и те же изображения, следующие вместе)

src/app.js.

import React from 'react'
import noviceImg from './resources/novice.jpg'
import styles from './styles.module.css'

const App = () => (
  

You are a Novice

Congratulations on reaching level 10!
) export default App

Вот новые дополнения к CSS:

styles.csss.

.content {
  display: flex;
  justify-content: center;
}

.header {
  text-align: center;
  color: rgb(252, 216, 169);
  font-weight: 300;
  margin: 0;
}

.subheader {
  color: #fff;
  text-align: center;
  font-weight: 300;
  width: 100%;
  display: block;
}

.characterBox {
  transition: all 0.1s ease-out;
  width: 300px;
  height: 250px;
  border: 1px solid rgb(194, 5, 115);
  background: rgb(82, 26, 134);
  margin: 12px 6px;
  overflow: hidden;
}

.characterBox img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: pointer;
}

Глядя на компонент, мы видим, что корневой элемент включает в себя заголовок, контейнер контента и подзаголовок в качестве непосредственных детей. Вскоре назад я упомянул, что мы собираемся показать фотографию новичка пользователю, и вот что происходит внутри элемента div с классом styles.content. :

Мы определили названия классов CSS для заголовка и подзаголовок, потому что что-то говорит мне, что они могут быть повторно использованы для интерфейсов Farcher, вроде когда игрок направлен на новый раздел. Когда я думаю о словом «раздел», я думаю об этом, содержащий некоторый заголовок и тело, так что это похоже на действительный ход. Мы также можем использовать «коробку символов» для других персонажей, таких как маги или что-то еще, поэтому мы определили .characterBox Имя класса, чтобы держать это мыслей, пока мы продолжим.

Теперь, когда мы создали самую легкую часть этого учебника, давайте перейдем к более интересным частям!

Следующее, что мы собираемся сделать, это сделать Варианты или Выбор экран. Этот экран будет отвечать за отображение выбора класса символов к плееру. Эти символы называются Колдунья и Рыцарь Отказ Это экранные игроки будут предложены, как только они достигли уровня 10.

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

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

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

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

Обычно я извлекил эти повторные компоненты в свой собственный файл, чтобы мы могли напрямую Импорт их как отдельные модули, но чтобы спасти нас некоторое время и пространство, мы просто поймаем их все в SRC/Компоненты.js.

Так что идите вперед и создайте Компоненты.js Файл в том же каталоге и определите повторное использование в качестве именованного экспорта:

src/portent.js.

export const Header = ({ children, ...rest }) => (
  // eslint-disable-next-line
  

{children}

) export const Subheader = ({ children, ...rest }) => ( {children} ) export const Content = ({ children, ...rest }) => (
{children}
)

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

src/app.js.

import React from 'react'
import noviceImg from './resources/novice.jpg'
import styles from './styles.module.css'
import { Header, Subheader, Content } from './components'

const App = () => (
  
You are a Novice
Congratulations on reaching level 10!
) export default App

Кодекс начинает выглядеть немного приятнее сейчас, не так ли? Теперь мы пойдем вперед и сделаем экран, показывающий классы символов:

src/app.js.

import React from 'react'
import noviceImg from './resources/novice.jpg'
import sorceressImg from './resources/sorceress.jpg'
import knightImg from './resources/knight.jpg'
import styles from './styles.module.css'
import { Header, Subheader, Content } from './components'

const App = () => (
  
You are a Novice
Congratulations on reaching level 10!
Choose your destiny
Choose one. Or all, if you know what I mean.

Sorceress

Knight

) export default App

И вот новое дополнение к styles.module.css :

.characterBox h2 {
  transition: all 0.3s ease-out;
  text-align: center;
  color: rgb(213, 202, 255);
  font-style: italic;
  font-weight: 500;
}

С этим изменением наш интерфейс теперь выглядит так:

Вы можете увидеть, что в коде мы смогли повторно использовать Заголовок , Subheader и Содержание Для следующего Раздел Отказ Интерфейс выглядит последовательный И мы получили очень важное преимущество: теперь нам нужно только изменить заголовок/подзаголовку/компоненты контента в один Место вместо нескольких мест в будущем! Некоторые другие заметные выгоды, полученные из этого подхода, является Неявная документация (теперь мы просто знать То, что это заголовок и компоненты подзаголовки и могут легко вернуться и понять код в оснастке).

Следующая вещь, которую мы собираемся сделать, это сделать колдунь и рыцарскую коробку вызывать некоторые действия, как только они нажаты.

Мы просто определим бесполезное onselect обработчик просто так мы определяем некоторые “структура” Таким образом, мы можем постоянно напомнить, что есть некоторое значение Click для последующего использования:

const App = () => {
  const onSelect = (e) => {
    console.log("Don't mind me. I'm useless until I become useful")
  }

  return (
    
You are a Novice
Congratulations on reaching level 10!
Choose your destiny
Choose one. Or all, if you know what I mean.

Sorceress

Knight

) }

Вещи выглядят великолепно, однако нет способа сказать, какой символ игрок выбрал без каких-либо визуальных изменений (поскольку все мы делаем, это ведение журнала «Я бесполезно» на консоль):

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

Сейчас мы собираемся начать объединение имен классов, чтобы мы могли иметь несколько влияний, происходящих параллельно для отдельных элементов, я собираюсь установить удобное классы Библиотека, чтобы сделать слияние для нас:

npm install --save classnames

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

Давайте теперь добавим некоторые стили для элементов коробки символов:

.characterBox:hover h2 {
  color: rgb(191, 255, 241);
}

.characterBox img {
  transition: all 0.3s ease-out;
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: pointer;
}

.characterBox img.tier2:hover {
  animation: hueRotate 2s infinite;
  transform: scale(1.05);
}

@keyframes hueRotate {
  0% {
    filter: hue-rotate(0deg);
  }
  50% {
    filter: hue-rotate(260deg) grayscale(100%);
  }
  100% {
    filter: hue-rotate(0deg);
  }
}

Мы применяем бесконечно изменение Hue-Votate Фильтр, когда игрок охватывает, чтобы они выделились. Игрок должен быть взволнован в MORPH в класс Tier2!:)

Прямо сейчас эти эффекты наводчика не сделают ничего, потому что нам нужно применить новые имена классов, как показано в CSS. Все, что нам нужно сделать, это применить Классическое имя Атрибут элементам изображения Tier2:

src/app.js.

Choose your destiny
Choose one. Or all, if you know what I mean.

Sorceress

Knight

И VOILA! Теперь у нас есть какой-либо эффект изменения цвета

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

С этим сказанным, теперь мы можем ввести реактуальные крючки в игру. Так что давайте пойдем вперед и создать пользовательский крючок под названием USELEVELUPSCREEN прямо над компонентом приложения и определите Выберите Состояние вместе с onselect Обработчик для обновления:

src/app.js.

import React from 'react'
import cx from 'classnames'
import noviceImg from './resources/novice.jpg'
import sorceressImg from './resources/sorceress.jpg'
import knightImg from './resources/knight.jpg'
import styles from './styles.module.css'
import { Header, Subheader, Content } from './components'

const useLevelUpScreen = () => {
  const [selected, setSelected] = React.useState([])

  const onSelect = (type) => (e) => {
    setSelected((prevSelected) => {
      if (prevSelected.includes(type)) {
        return prevSelected.filter((t) => t !== type)
      }
      return [...prevSelected, type]
    })
  }

  return {
    selected,
    onSelect,
  }
}

const App = () => {
  const { selected, onSelect } = useLevelUpScreen()

  return (
    
You are a Novice
Congratulations on reaching level 10!
Choose your destiny
Choose one. Or all, if you know what I mean.

Sorceress

Knight

) } export default App

Внутри USELEVELUPSCREEN Мы определили выбран Государство, которое поможет нам определить, какой класс класса Tier2 выбрал игрок. onselect Обработчик – это API для обновления этого состояния. Он использует версию обратного вызова Уместите Чтобы гарантировать, что это точно получает последнее обновление для выбран государство. Внутри обратного вызова он проверяет, если Тип (что либо Рыцарь или Колдунья В нашем случае) уже выбран. Если это то, тогда мы предположим, что игрок решил отменить выбор выбора, поэтому мы отфильтровали его для следующего состояния обновления и наоборот.

Затем мы применили onselect обработчик к элементам, которые нуждаются в них в Приложение компонент:

src/app.js.

const App = () => {
  const { selected, onSelect } = useLevelUpScreen()

  return (
    
You are a Novice
Congratulations on reaching level 10!
Choose your destiny
Choose one. Or all, if you know what I mean.

Sorceress

Knight

) }

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

src/app.js.

const App = () => {
  const { selected, onSelect } = useLevelUpScreen()

  return (
    
You are a Novice
Congratulations on reaching level 10!
Choose your destiny
Choose one. Or all, if you know what I mean.

Sorceress

Knight

) }

С добавлением этих новых стилей мы должны обновить стили, чтобы вместить его:

SRC/styles.css.

.selectedBox {
  border: 1px solid rgb(24, 240, 255) !important;
}

.characterBox img.tier2:hover,
.characterBox img.selected {
  animation: hueRotate 2s infinite;
  transform: scale(1.05);
}

^ (Обратите внимание на подлый » .Characterbox IMG.Велебрация ” был добавлен как братья и сестры после Hover линия)

С этим изменением мы должны иметь два красивых ящика выбора реактивных характере!

Ранее в руководстве мы упомянули, что если игрок был Nosy и Smart достаточно, они выяснили, что если они выберут оба Волшебность и рыцарь и попытка превратиться в Morph (оригинальное намерение игрока – это выбрать один характер, но мало кто знает, что мы предоставили крутой секретный характер), они смогут сделать это и превратить в что-то неожиданное Отказ Нам, очевидно, надо придать игроку своего рода элемента (ов) пользовательских интерфейсов, чтобы они могли морфироваться их новичком от их имени. В таком случае нам нужен кнопка Отказ

Мы напишем простой кнопка и приложите OnMorph обработчик, который мы собираемся создавать, а также применять Стили. Гррф к кнопке Классическое имя :

src/app.js.

const App = () => {
  const { selected, onSelect, morphed, onMorph } = useLevelUpScreen()

  return (
    
You are a Novice
Congratulations on reaching level 10!
Choose your destiny
Choose one. Or all, if you know what I mean.

Sorceress

Knight

) }

Если вы посмотрите на то, что вернулось из USELEVELUPSCREEN Крюк мы видим, что есть два новых дополнения: Морфировали и onmorph . И они будут определены внутри пользовательского крючка:

SRC/USELEVELUPSCREEN.JS.

const useLevelUpScreen = () => {
  const [selected, setSelected] = React.useState([])
  const [morphed, setMorphed] = React.useState(false)

  const onSelect = (type) => (e) => {
    setSelected((prevSelected) => {
      if (prevSelected.includes(type)) {
        return prevSelected.filter((t) => t !== type)
      }
      return [...prevSelected, type]
    })
  }

  const onMorph = () => {
    setTimeout(() => {
      setMorphed(true)
    }, 1500) // simulating a real server / api call response time
  }

  return {
    selected,
    onSelect,
    morphed,
    onMorph,
  }
}

И вот стиль для Стили. Гррф ClassName:

SRC/Styles.module.css.

.morph {
  margin: 50px auto;
  text-align: center;
}

Вот что мы имеем до сих пор:

Лично я предпочитаю скрывать кнопку MORPH, пока не будет сделан выбор, чтобы игрок был сосредоточен только на выборе класса символов. Поэтому я бы применил какой-то скрытый эффект видимости до выбран населен чем-то:

{
  !!selected.length && (
    
) }

Примечание: мы завернули кнопку с помощью Div Элемент, чтобы мы могли иметь более тонкий контроль над позиционированием и интервалом кнопки.

Теперь пользователь четко знает, что он или ей нужно выбрать что-то с тех пор, как это будет единственный вариант, который мы сделаем для них доступным:

Кнопка кажется немного простой. Мы пытаемся держать игрока мотивированного и счастливого, что они прошли это далеко до уровня 10. Таким образом, для этого следующего шага я поместил значки слева и справа от кнопки MORPH, которую вы также можете использовать для последующей установки React-Icons Отказ

npm install --save react-icons

(Примечание. Если вы получаете ошибку с установкой пакета с помощью NPM, попробуйте установить пряжу и еще раз добавить пакет, запустив пряжа Добавить иконы на реакцию . Вы можете вернуться к NPM, чтобы начать сервер позже)

Затем я импортировал этот компонент значка:

import { MdKeyboardTab } from 'react-icons/md'

И вставлен один прямо перед кнопкой, а потом потом. Теперь значки стрелки все еще и они оба сталкиваются с тем же направлением. Мы должны добавить их в жизнь, указывая их и давая им бесконечно зацикливание эффекта изменения цвета, в дополнение к фиксации направления на значке стрелки вправо, чтобы указать на кнопку:

Используемые стили:

src.styles.module.css.

.morphArrow {
  color: rgb(123, 247, 199);
  transform: scale(2);
  animation: morphArrow 2s infinite;
}

.morphArrowFlipped {
  composes: morphArrow;
  transform: scale(-2, 2);
}

@keyframes morphArrow {
  0% {
    opacity: 1;
    color: rgb(123, 247, 199);
  }
  40% {
    opacity: 0.4;
    color: rgb(248, 244, 20);
  }
  100% {
    opacity: 1;
    color: rgb(123, 247, 199);
  }
}

Теперь, по сравнению с ранее, мы можем быть уверены, что игрок будет более взволнован, чтобы начать следующую главу их приключений!

Наш текущий OnMorph Функция устанавливает Морфировали Состояние в True после нажатия, поэтому теперь мы можем отобразить секретный класс символов, который игрок решил превратиться в, как только Морфировали Переходы к true. Мы собираемся разместить это прямо под элементом div, содержащего кнопку MORPH:

src/app.js.

// at the top:
import sageImg from './resources/sage.jpg'

// ...

{
  morphed && (
    
Congratulations!
You have morphed into a Sage
) }

SRC/Styles.module.css.

.morphed {
  animation: flashfade 4s forwards;
  opacity: 0;
}

@keyframes flashfade {
  0% {
    opacity: 0;
  }
  60% {
    opacity: 0.7;
  }
  100% {
    opacity: 1;
  }
}

Секретный класс, в котором они будут морфировать, это мудрец! Вот как это выглядит сейчас:

Я мог бы Будь в порядке с тем, как это пьесы. Но все все еще выглядит немного «жестко» для меня. Другими словами, я думаю, что игрок нуждается в некотором прокрутке к действию, чтобы они постоянно заняты нашим интерфейсом. Мы собираемся установить небольшую библиотеку под названием Реагистрационно-прокрутка к компоненту Это позволит нам прокрутить экран игрока в любой элемент, пройдя в ссылке элемента:

npm install --save react-scroll-to-component

Импортировать его внутри SRC/App.js :

import scrollToComponent from 'react-scroll-to-component'

Теперь давайте пойдем вперед и создадим Ref в дополнение к подключению к элементу:

const App = () => {
  const morphedRef = React.createRef()
  const { selected, onSelect, morphed, onMorph } = useLevelUpScreen({ morphedRef })

// ...

   {morphed && (
    
Congratulations!
You have morphed into a Sage
)}

Поскольку мы хотим, чтобы этот эффект прокрутки выглядеть гладко, нам нужно добавить больше высоты до нижней части страницы, поэтому у нас есть больше места. На самом деле мы можем сделать это легко, добавив пустой div с высотой, когда Морфировали Переключается на true:

{
  morphed && (
    
Congratulations!
You have morphed into a Sage
) } { morphed &&
}

Но сейчас проблема. Высота не сможет сделать его на экране вовремя до того, как зона шалфея становится видимой. Другими словами, прокрутка к функциональности не работает, потому что во время вызова его нет Посмотреть дальнейшее пространство еще. Мы собираемся обойти это, добавив дополнительное состояние под названием Морфинг что даст нам некоторое время, чтобы разрушить хаос в интерфейсе UI до Морфировали становится истинный :

const useLevelUpScreen = ({ morphedRef }) => {
  const [selected, setSelected] = React.useState([])
  const [morphing, setMorphing] = React.useState(false)
  const [morphed, setMorphed] = React.useState(false)

  const onSelect = (type) => (e) => {
    setSelected((prevSelected) => {
      if (prevSelected.includes(type)) {
        return prevSelected.filter((t) => t !== type)
      }
      return [...prevSelected, type]
    })
  }

  const onMorph = () => {
    setMorphing(true)
    setTimeout(() => {
      setMorphed(true)
      setMorphing(false)
    }, 1500)
  }

  React.useEffect(() => {
    if (morphed) {
      scrollToComponent(morphedRef.current, {
        offset: 100,
        align: 'middle',
        duration: 1000,
      })
    }
  }, [morphed, morphedRef])

  return {
    selected,
    onSelect,
    morphed,
    onMorph,
    morphing,
  }
}

Однако мы столкнулись с новой проблемой. Похоже …| Морфировали Блокирует элементы внутри от рендеринга, таким образом блокируя нас от нанесения логики в пределах 1,5 секунды рама:

const App = () => {
  const morphedRef = React.createRef()
  const { selected, onSelect, morphing, morphed, onMorph } = useLevelUpScreen()

// ...

{morphed && (
  
Congratulations!
You have morphed into a Sage
)} {morphed &&
}

Что мы собираемся сделать, это вынимать морфировал && Условный и вместо этого используйте классы Пакет, чтобы объединить некоторые дополнительные стили. Эти стили имитируют поведение и будут Держите элементы в дереве реагирования Так что они могут поддерживать такие функции, как анимация:

;
Congratulations!
You have morphed into a Sage
{ morphing || (morphed &&
) }

Редактировать : Я забыл включить часть, где мы также применяем другую кнопку MORPH, чтобы сделать страницу прокручивать туда, когда проигрыватель выбирает класс символов. Извини за это!

SRC/App.js :

const useLevelUpScreen = ({ morphRef, morphedRef }) => {
// ...

const onSelect = (type) => (e) => {
  setSelected((prevSelected) => {
    if (prevSelected.includes(type)) {
      return prevSelected.filter((t) => t !== type)
    }
    return [...prevSelected, type]
  })
  scrollToComponent(morphRef.current, {
    offset: 300,
    align: 'bottom',
    duration: 1000,
  })
}

const onMorph = () => {
  if (!morphing) setMorphing(true)
  setTimeout(() => {
    setMorphing(false)
    setMorphed(true)
  }, 1500)
}

// ...

return {
  selected,
  onSelect,
  morphed,
  morphing,
  onMorph,
}

const App = () => {
  const morphRef = React.createRef()
  const morphedRef = React.createRef()

  // ...

 

В приведенном выше примере мы применили A Стиль = {{непрозрачность: морфин? '0,4': 1}} После того, как MORPH будет сделан для сигнализации игрока, что кнопка больше не будет доступна. Мы применили отключить атрибут для отключения событий щелчков с Отключено = {Morped} Отказ Мы также изменили текст в соответствии с обновлениями состояния MORPH с {морфинг? « Морфинг ... ': Морфировали? « Морфировал «:« Морф »} Чтобы пользователю постоянно занятым, глядя на вещи, которые меняются. О да, мы также удалили {!! Selection.length && ( , которая упаковала кнопку MORPH, потому что она блокировала нашу анимацию, как мы недавно говорили с другой частью кода и применяли Морфриф Ref для этого, как показано выше. О да и в пользовательском крючке мы также применили Scrolltocomponent Реализация в конце onselect Функция для анимирования прокрутки к кнопке MORPH.

* Закончилось редактировать

Как только MORPHING завершен, мы собираемся имитировать какую-то анимацию загрузки, чтобы сообщить пользователю, что мы обрабатываем следующие шаги:

Loading...

Стили:

.next {
  text-align: center;
  margin: 35px auto;
  display: flex;
  justify-content: center;
}

.next p {
  font-family: Patua One, sans-serif;
  font-weight: 300;
  text-align: center;
  color: #fff;
}

Вы можете увидеть, что есть также новое состояние, называемое готов Поэтому мы должны реализовать, что в пользовательском крючке:

const useLevelUpScreen = ({ morphRef, morphedRef }) => {
  const [selected, setSelected] = React.useState([])
  const [morphing, setMorphing] = React.useState(false)
  const [morphed, setMorphed] = React.useState(false)
  const [ready, setReady] = React.useState(false)

  const onSelect = (type) => (e) => {
    setSelected((prevSelected) => {
      if (prevSelected.includes(type)) {
        return prevSelected.filter((t) => t !== type)
      }
      return [...prevSelected, type]
    })
    scrollToComponent(morphRef.current, {
      offset: 300,
      align: 'bottom',
      duration: 1000,
    })
  }

  const onMorph = () => {
    setMorphing(true)
    setTimeout(() => {
      setMorphing(false)
      setMorphed(true)
    }, 1500)
  }

  React.useEffect(() => {
    if (morphed && !ready) {
      scrollToComponent(morphedRef.current, {
        offset: 100,
        align: 'middle',
        duration: 1000,
      })
      setTimeout(() => {
        setReady(true)
      }, 2000)
    }
  }, [morphed, morphedRef, ready])

  return {
    selected,
    onSelect,
    morphed,
    morphing,
    onMorph,
    ready,
  }
}

Наконец, мы собираемся исчезнуть всю страницу после этого, чтобы мы могли начать следующие страницы, поскольку текущий закончен. Это означает, что мы собираемся добавить еще одно состояние на пользовательский крючок под названием Выключение и применить новое имя класса в корне Div элемент. Выключение Государство переключится на True только когда готов становится правдой.

const useLevelUpScreen = ({ morphRef, morphedRef }) => {
  const [selected, setSelected] = React.useState([])
  const [morphing, setMorphing] = React.useState(false)
  const [morphed, setMorphed] = React.useState(false)
  const [ready, setReady] = React.useState(false)
  const [shutdown, setShutdown] = React.useState(false)

  const onSelect = (type) => (e) => {
    setSelected((prevSelected) => {
      if (prevSelected.includes(type)) {
        return prevSelected.filter((t) => t !== type)
      }
      return [...prevSelected, type]
    })
    scrollToComponent(morphRef.current, {
      offset: 300,
      align: 'bottom',
      duration: 1000,
    })
  }

  const onMorph = () => {
    setMorphing(true)
    setTimeout(() => {
      setMorphing(false)
      setMorphed(true)
    }, 1500)
  }

  React.useEffect(() => {
    if (morphed && !ready) {
      scrollToComponent(morphedRef.current, {
        offset: 100,
        align: 'middle',
        duration: 1000,
      })
    setTimeout(() => {
      setReady(true)
    }, 2000)
    }
  }, [morphed, morphedRef, ready])

  React.useEffect(() => {
    if (ready && !shutdown) {
      setTimeout(() => {
        setShutdown(true)
      }, 2000)
    }
  }, [ready, shutdown])

  return {
    selected,
    onSelect,
    morphed,
    morphing,
    onMorph,
    ready,
    shutdown,
  }
}

const App = () => {
  const morphRef = React.createRef()
  const morphedRef = React.createRef()
  const {
    selected,
    onSelect,
    morphing,
    morphed,
    onMorph,
    ready,
    shutdown,
  } = useLevelUpScreen({
    morphRef,
    morphedRef,
  })

  const onClick = (e) => {
    console.log("Don't mind me. I'm useless until I become useful")
  }

  return (
    

А вот окончательный результат!

Вот что выглядит весь код:

src/app.js.

import React from 'react'
import cx from 'classnames'
import { RingLoader } from 'react-spinners'
import { MdKeyboardTab } from 'react-icons/md'
import scrollToComponent from 'react-scroll-to-component'
import noviceImg from './resources/novice.jpg'
import sorceressImg from './resources/sorceress.jpg'
import knightImg from './resources/knight.jpg'
import sageImg from './resources/sage.jpg'
import styles from './styles.module.css'
import { Header, Subheader, Content } from './components'

const useLevelUpScreen = ({ morphRef, morphedRef }) => {
  const [selected, setSelected] = React.useState([])
  const [morphing, setMorphing] = React.useState(false)
  const [morphed, setMorphed] = React.useState(false)
  const [ready, setReady] = React.useState(false)
  const [shutdown, setShutdown] = React.useState(false)

  const onSelect = (type) => (e) => {
    setSelected((prevSelected) => {
      if (prevSelected.includes(type)) {
        return prevSelected.filter((t) => t !== type)
      }
      return [...prevSelected, type]
    })
    scrollToComponent(morphRef.current, {
      offset: 300,
      align: 'bottom',
      duration: 1000,
    })
  }

  const onMorph = () => {
    setMorphing(true)
    setTimeout(() => {
      setMorphing(false)
      setMorphed(true)
    }, 1500)
  }

  React.useEffect(() => {
    if (morphed && !ready) {
      scrollToComponent(morphedRef.current, {
        offset: 100,
        align: 'middle',
        duration: 1000,
      })
      setTimeout(() => {
        setReady(true)
      }, 2000)
    }
  }, [morphed, morphedRef, ready])

  React.useEffect(() => {
    if (ready && !shutdown) {
      setTimeout(() => {
        setShutdown(true)
      }, 2000)
    }
  }, [ready, shutdown])

  return {
    selected,
    onSelect,
    morphed,
    morphing,
    onMorph,
    ready,
    shutdown,
  }
}

const App = () => {
  const morphRef = React.createRef()
  const morphedRef = React.createRef()
  const {
    selected,
    onSelect,
    morphing,
    morphed,
    onMorph,
    ready,
    shutdown,
  } = useLevelUpScreen({
    morphRef,
    morphedRef,
  })

  return (
    
You are a Novice
Congratulations on reaching level 10!
Choose your destiny
Choose one. Or all, if you know what I mean.

Sorceress

Knight

Congratulations!
You have morphed into a Sage

Loading...

) } export default App

src/portent.js.

import React from 'react'
import cx from 'classnames'
import styles from './styles.module.css'

export const Header = ({ children, ...rest }) => (
  // eslint-disable-next-line
  

{children}

) export const Subheader = ({ children, ...rest }) => ( {children} ) export const Content = ({ children, ...rest }) => (
{children}
)

SRC/Styles.module.css.

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
    'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
    'Helvetica Neue', sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background: rgb(23, 30, 34);
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

.root {
  padding: 20px 0;
}

.container {
  display: flex;
  justify-content: center;
}

.header {
  text-align: center;
  color: rgb(252, 216, 169);
  font-weight: 300;
  margin: 0;
}

.subheader {
  color: #fff;
  text-align: center;
  font-weight: 300;
  width: 100%;
  display: block;
}

.characterBox {
  transition: all 0.1s ease-out;
  width: 300px;
  height: 250px;
  border: 1px solid rgb(194, 5, 115);
  background: rgb(82, 26, 134);
  margin: 12px 6px;
  overflow: hidden;
}

.characterBox img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: pointer;
}

.selectedBox {
  border: 1px solid rgb(24, 240, 255) !important;
}

.characterBox h2 {
  transition: all 0.3s ease-out;
  text-align: center;
  color: rgb(213, 202, 255);
  font-style: italic;
  font-weight: 500;
}

.characterBox:hover h2 {
  color: rgb(191, 255, 241);
}

.characterBox img {
  transition: all 0.3s ease-out;
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: pointer;
}

.characterBox img.tier2:hover,
.characterBox img.selected {
  animation: hueRotate 2s infinite;
  transform: scale(1.05);
}

.morph {
  margin: 30px auto;
  text-align: center;
}

.morphArrow {
  color: rgb(123, 247, 199);
  transform: scale(2);
  animation: morphArrow 2s infinite;
}

.morphArrowFlipped {
  composes: morphArrow;
  transform: scale(-2, 2);
}

@keyframes morphArrow {
  0% {
    opacity: 1;
    color: rgb(123, 247, 199);
  }
  40% {
    opacity: 0.4;
    color: rgb(248, 244, 20);
  }
  100% {
    opacity: 1;
    color: rgb(123, 247, 199);
  }
}

button.morph {
  cursor: pointer;
  transition: all 0.2s ease-out;
  border-radius: 25px;
  padding: 14px 22px;
  color: #fff;
  background: rgb(35, 153, 147);
  border: 1px solid #fff;
  font-family: Patua One, sans-serif;
  font-size: 1.2rem;
  text-transform: uppercase;
  letter-spacing: 2px;
  margin: 0 20px;
}

button.morph:hover {
  background: none;
  border: 1px solid rgb(35, 153, 147);
  color: rgb(35, 153, 147);
}

.morphed {
  animation: flashfade 4s forwards;
  opacity: 0;
}

@keyframes flashfade {
  0% {
    opacity: 0;
  }
  60% {
    opacity: 0.7;
  }
  100% {
    opacity: 1;
  }
}

.hidden {
  visibility: hidden;
}

.next {
  text-align: center;
  margin: 35px auto;
  display: flex;
  justify-content: center;
}

.next p {
  font-family: Patua One, sans-serif;
  font-weight: 300;
  text-align: center;
  color: #fff;
}

@keyframes hueRotate {
  0% {
    filter: hue-rotate(0deg);
  }
  50% {
    filter: hue-rotate(260deg) grayscale(100%);
  }
  100% {
    filter: hue-rotate(0deg);
  }
}

.shutdown {
  animation: shutdown 3s forwards;
}

@keyframes shutdown {
  100% {
    opacity: 0;
  }
}

Если вы заметили на протяжении всего руководства, была пара повторных кодов. Притворись, что вам пришлось внезапно приспособиться к коробкам выбора символов, таких как регулировка размера. Если вы изменили один, вам придется сканировать весь файл, чтобы найти другие окна выбора, чтобы сделать интерфейс согласован. В настоящее время Колдунья и Рыцарь Ящики выбора идентичны, и они должны оставаться в синхронизации. Но что, если мы добавили больше персонажей Tier2 в игру? У вас будет куча повторяющегося кода, поэтому это хорошая идея, чтобы решить это в свой компонент. Это приносит очень важное преимущество: лучше Настройка способности.

Вот что может выглядеть код, если вы абстрагировали коробки выбора символов:

src/app.js.

const characterSelections = [
  { type: 'Sorceress', src: sorceressImg },
  { type: 'Knight', src: knightImg },
  { type: 'Shapeshifter', src: shapeshifterImg },
  { type: 'Bandit', src: banditImg },
  { type: 'Archer', src: archerImg },
  { type: 'Blade Master', src: bladeMasterImg },
  { type: 'Destroyer', src: destroyerImg },
  { type: 'Summoner', src: summonerImg },
  { type: 'Phantom', src: phantomImg },
]

const charSelectionMapper = characterSelections.reduce(
  (acc, { type, src }) => ({
    ...acc,
    [type]: src,
  }),
  {},
)

const App = () => {
  const morphRef = React.createRef()
  const morphedRef = React.createRef()
  const {
    selected,
    onSelect,
    morphing,
    morphed,
    onMorph,
    ready,
    shutdown,
  } = useLevelUpScreen({
    morphRef,
    morphedRef,
  })

  return (
    
You are a Novice
Congratulations on reaching level 10!
Choose your destiny
Choose one. Or all, if you know what I mean. {characterSelections.map((props, index) => ( ))}
Congratulations!
You have morphed into a {selected}

Loading...

) }

src/portent.js.

// ...

const CharacterBox = React.forwardRef(
  (
    {
      isSelected,
      type,
      headerProps = {},
      imgProps = {},
      src,
      disableFlashing,
      ...rest
    },
    ref,
  ) => (
    
{type &&

{type}

}
), )

Вывод

И это завершает конец этой статьи! Я надеюсь, что вам понравилось и продолжал посмотреть больше в будущем!

Найди меня на средний

Оригинал: “https://dev.to/jsmanifest/build-a-character-select-screen-in-react-4o2e”