Я давно не был на Наггетсах и обнаружил, что в черновом ящике есть несколько неопубликованных статей, с которыми недавно разобрались и опубликовали.
прошлое
- Серия интервьюеров (1): Как реализовать глубокое клонирование
- Серия интервьюеров (2): Реализация шины событий
- Серия интервьюеров (3): Реализация внешней маршрутизации
- Серия интервьюеров (4): Преимущества двустороннего связывания на основе перехвата данных прокси
- Серия интервью (5): Почему вы используете интерфейсный фреймворк
предисловие
Проектирование front-end компонентов — это один из тестов, который может проверить базовые навыки разработчиков, потому что каждый может это сделать, вызывая API готовых библиотек компонентов, таких как Material design, Antd и iView, но многие люди не знают принципы проектирования многих часто используемых компонентов.
Можно ли спроектировать общий интерфейсный компонент, также является одним из критериев, позволяющих различать интерфейсных инженеров и лиц, вызывающих интерфейсные API, так как же разработать общий компонент?
упомянуто нижеБиблиотека компонентовОбычно относится к одному компоненту, а не к концепции коллекции. Библиотека компонентов концепции коллекции — это Antd iView. Библиотека компонентов, которую мы вызываем, относится к одному компоненту в коллекции. Библиотека компонентов коллекции должна быть считается больше.
Каталог статей
- Принципы проектирования библиотеки интерфейсных компонентов
- Выбор технологии библиотеки компонентов
- Как быстро запустить проект библиотеки компонентов
- Как спроектировать компонент карусели
1. Библиотека компонентов принципа внешнего интерфейса
1.1 Детальные соображения
Когда мы изучаем шаблоны проектирования, мы сталкиваемся со многими принципами проектирования, одним из которых являетсяПринцип единой ответственности, то же самое относится и к разработке библиотек компонентов.В принципе, компонент фокусируется только на чем-то одном.Преимущества компонента с одной ответственностью очевидны.Благодаря единой ответственности компоненты можно повторно использовать в максимально возможной степени,но это также создает проблему. Чрезмерное количество компонентов с одной ответственностью также может привести к чрезмерной абстракции и фрагментации библиотеки компонентов.
Например, компонент автозаполнения (AutoComplete) на самом деле состоит из компонента ввода и компонента выбора, поэтому мы можем полностью повторно использовать предыдущие связанные компоненты, такие как компонент автозаполнения Antd повторно использует компонент выбора. такие компоненты, как Calendar, Form и т. д., повторно используют компонент Select, поэтому детализация Select подходит, потому что детализация, поддерживаемая Select, легко используется повторно.
Затем есть еще один пример, компонент номера значка (Badge), который будет иметь красную точку в правом верхнем углу, которая может быть числом или значком.Конечно, его обязанности также очень едины.Конечно, этот красный точка также может использоваться отдельно, она абстрагируется как независимый компонент, но мы обычно не используем ее как независимый компонент, потому что этот компонент нельзя использовать повторно в других сценариях, потому что нет подобной сцены, для которой требуется маленькая красная точка, поэтому он используется как независимый компонент.Он слишком мелкий, поэтому мы часто используем его как внутренний компонент Badge.Например, в Antd он существует как внутренний компонент Badge под именем ScrollNumber.
Следовательно, так называемый компонент с одной ответственностью должен основываться на возможности повторного использования, а компонент с одной ответственностью, не подлежащий повторному использованию, мы можем использовать только как внутренний компонент независимого компонента.
1.2 Общие соображения
То, что мы хотим разработать, — это общая библиотека компонентов. В отличие от наших общих бизнес-компонентов, общие компоненты отделены от бизнеса, но служат развитию бизнеса. Тогда возникает вопрос, как обеспечить универсальность компонентов. Высокая универсальность должна быть хорошей вещью?
Например, когда мы разрабатываем компонент селектора (Select), мы обычно разрабатываем его следующим образом.
Это один из наших самых распространенных и часто используемых селекторов, но проблема в том, что его универсальность сильно снижается.Когда у нас есть такое требование, наш предыдущий компонент селектора не соответствует требованиям, потому что в нижней части этого компонента «Выбрать» должна быть раскрывающаяся кнопка элемента.
В настоящее время нам нужно повторно модифицировать предыдущий компонент селектора или даже создать компонент селектора, который соответствует требованиям?Если это произойдет, это может означать только то, что предыдущий компонент селектора недостаточно универсален и нуждается в редизайне.
Компонент Select от Antd зарезервированdropdownRender
для выполнения пользовательского рендеринга, который зависит отrc-select
Код в компоненте выглядит следующим образом
Antd опирается на множество
rc-
низкоуровневые компоненты, которые начинаются сreact-componentОн поддерживается командой (также известной как команда Antd), которая в основном реализует базовую логику компонента. Antd реализуется путем добавления языка проектирования Ant Design на этой основе.
Конечно, есть много подобных дизайнов.Универсальный дизайн фактически отказывается от контроля над DOM в определенном смысле и передает право принятия решений по структуре DOM разработчику.dropdownRender
На самом деле, это отказ от контроля над пунктами в раскрывающемся меню Select.Компонент Antd Select на самом деле имеет метод, который не отражен в документации.getInputElement
Это должен быть пользовательский метод для компонента Input.Вся конструкция компонента Select в Antd очень сложна, в основном предоставляя разработчикам все права управления структурой DOM, и он отвечает только за базовую логику и самую базовую структуру DOM.
Это окончательная jsx-структура повторного выбора, на которую опирается Antd.Его структура DOM очень проста, но предоставляет разработчикам множество настраиваемых интерфейсов рендеринга.
return (
<SelectTrigger
onPopupFocus={this.onPopupFocus}
onMouseEnter={this.props.onMouseEnter}
onMouseLeave={this.props.onMouseLeave}
dropdownAlign={props.dropdownAlign}
dropdownClassName={props.dropdownClassName}
dropdownMatchSelectWidth={props.dropdownMatchSelectWidth}
defaultActiveFirstOption={props.defaultActiveFirstOption}
dropdownMenuStyle={props.dropdownMenuStyle}
transitionName={props.transitionName}
animation={props.animation}
prefixCls={props.prefixCls}
dropdownStyle={props.dropdownStyle}
combobox={props.combobox}
showSearch={props.showSearch}
options={options}
multiple={multiple}
disabled={disabled}
visible={realOpen}
inputValue={state.inputValue}
value={state.value}
backfillValue={state.backfillValue}
firstActiveValue={props.firstActiveValue}
onDropdownVisibleChange={this.onDropdownVisibleChange}
getPopupContainer={props.getPopupContainer}
onMenuSelect={this.onMenuSelect}
onMenuDeselect={this.onMenuDeselect}
onPopupScroll={props.onPopupScroll}
showAction={props.showAction}
ref={this.saveSelectTriggerRef}
menuItemSelectedIcon={props.menuItemSelectedIcon}
dropdownRender={props.dropdownRender}
ariaId={this.ariaId}
>
<div
id={props.id}
style={props.style}
ref={this.saveRootRef}
onBlur={this.onOuterBlur}
onFocus={this.onOuterFocus}
className={classnames(rootCls)}
onMouseDown={this.markMouseDown}
onMouseUp={this.markMouseLeave}
onMouseOut={this.markMouseLeave}
>
<div
ref={this.saveSelectionRef}
key="selection"
className={`${prefixCls}-selection
${prefixCls}-selection--${multiple ? 'multiple' : 'single'}`}
role="combobox"
aria-autocomplete="list"
aria-haspopup="true"
aria-controls={this.ariaId}
aria-expanded={realOpen}
{...extraSelectionProps}
>
{ctrlNode}
{this.renderClear()}
{this.renderArrow(!!multiple)}
</div>
</div>
</SelectTrigger>
);
Итак, есть так много мест, которые нужно настроить, не сложно ли использовать этот компонент Select?Потому что кажется, что все места должны быть настроены разработчиками, а универсальный дизайн оставляет решение структуры DOM разработчикам, сохраняя при этом значение по умолчанию. структура.Когда разработчик не отображает настройки, по умолчанию используется структура рендеринга по умолчанию.На самом деле базовое использование Select очень удобно, а именно:
<Select defaultValue="lucy" style={{ width: 120 }} disabled>
<Option value="lucy">Lucy</Option>
</Select>
Форма (DOM-структура) компонента постоянно меняется, но его поведение (логика) фиксировано, поэтому один из секретов общего компонента состоит в том, чтобы передать управление DOM-структурой разработчику, а компонент только отвечает за поведение и самую базовую структуру DOM
2 Технический отбор
2.1 css-решение
Из-за множества дефектов самого CSS, таких как громоздкость написания (не поддерживает вложенность), конфликты стилей (нет концепции области видимости), отсутствие переменных (неудобно менять темы одним кликом) и так далее. Чтобы решить эти проблемы, решения в сообществе также появлялись одно за другим, от самого раннего препроцессора CSS (SASS, LESS, Stylus) до более поздней восходящей звезды PostCSS, до CSS-модулей, Styled-Components и так далее.
Antd выбрал меньше в качестве решения для предварительной обработки для css, а Bootstrap выбрал Scss, Преимущества и недостатки этих двух решений обсуждаются уже много лет:
Каковы преимущества SCSS перед LESS?
Но какое бы решение это ни было, есть очень неприятный момент, то есть в нем нужно вводить дополнительные css.Например, Antd нужно выводить введение вот так:
import Button from 'antd/lib/button';
import 'antd/lib/button/style';
Чтобы разрешить эту неловкую ситуацию, Antd используетПлагин БабельВзломать эту ситуацию
а такжеmaterial-ui
Нет такой ситуации, ему не нужно отображать введение css, самая популярная библиотека интерфейсных компонентов React имеет только два вида кода js и ts, нет кода, связанного с css, почему?
они используютjss
В качестве решения css-in-js введение jsx соединило js и html, а css-in-js также соединило css. В настоящее время компонент не должен отображать введение css, а напрямую ссылается к js.
Разве это не регресс к доисторической эре написания встроенных стилей?
Нет,инлайновый стиль доисторического фронтенда это состояние связанности всего проекта.Конечно его надо выкинуть в помойку истории.Последующее разделение стиля и логики это собственно процесс расцепления js, css и html со страницей в качестве размерности.Сегодняшняя эпоха - эпоха компонентизации.jsx обрамил js и html в один компонент, а css до сих пор находится в отдельном состоянии, что приводит к необходимости отображать введение css каждый время, когда компонент упоминается, и css-in-js официально и полностью компонентизированы.
Конечно, лично я сейчас использую styled-components, преимуществаЦитироватьследующим образом:
-
Прежде всего, весь синтаксис styled-components является стандартным синтаксисом CSS и поддерживает общий синтаксис, такой как вложение scss, охватывающий все сценарии CSS.
-
В случае репликации стилей styled-components поддерживает вставку глобального CSS в любом месте, точно так же, как при написании обычного CSS.
-
styled-components поддерживает настраиваемое имя класса двумя способами, один из которых заключается в использованииПлагин Бабель, другой способ — использовать styled.div.withConfig({ componentId: "prefix-button-container" }), что эквивалентно добавлению className="prefix-button-container"
-
Classname более семинженно, это также оригинальное намерение класса имени.
-
Более подходящее использование библиотеки компонентов, прямая ссылка на импорт «модуля», в противном случае у вас есть три варианта: например, antd как отдельные ссылки css, вам нужно добавить css-loader в node_modules; внутренние компоненты напрямую импортируют файл css, если какой-либо бизнес-элемент никакой css-загрузчик не выдаст ошибку; компонент scss использует цитируемые, все бизнес-проекты настроены с помощью scss-загрузчика на node_modules; три — это библиотека компонентов, прямой ссылки на дружбу нет
-
Когда вы пишете библиотеку компонентов, вам нужно выпустить пакет отдельно, и есть требование к файлу конфигурации в едином стиле.Если файл конфигурации js, все компоненты напрямую ссылаются, и нет необходимости обращать внимание на внешний мир. В противном случае, если это файл конфигурации scss, перед вами еще три пути: каждый компонент обращается к файлу scss отдельно, и каждому бизнес-проекту необходимо добавить scss-loader в node_modules (если бизнес использует меньше, то это необходимо установить копию scss); Или, пока бизнес-сторона использует вашу библиотеку компонентов, она должна ссылаться на ваш файл scss в файле ввода. Например, ваш компонент называется button, а scss может называться common-css .Никто об этом не слышал,и нужно сверяться с документацией;или Ненаучно со стороны бизнеса цитировать ваш common-css отдельно в конфигурации вебпака.Если используются три библиотеки компонентов,то и менять неудобно конфигурацию веб-пакета каждый день.
-
Когда половина CSS стилизована, а другая половина действительно нуждается в js для динамической передачи, приходится смешивать css + css-in-js.Проект используется давно.Во время обслуживания выяснилось, что некоторые css-in-js не изменились.Вы можете Это затвердевает в css, а фиксированное значение в css становится переменным из-за новых запросов.Вы должны взять его и положить в css-in-js.Вы будете знать как это раздражает после тренировки.
2.2 js-решение
Выберите Typescript, потому что это отличный способ сделать это...
Вы можете увидеть мой ответ под вопросом кворапочему бы тебе не использовать машинописный текст
Или посмотрите эту статьюОтчет об исследовании системы TypeScript
3. Как быстро запустить проект библиотеки компонентов
Конкретная реализация некоторых компонентов курса является основной библиотекой компонентов, но другие части также необходимы в современной интерфейсной библиотеке. Нам нужен набор инструментов, которые помогут нам в разработке, таких как инструменты компиляции, инструменты обнаружения кода, упаковочные инструменты и так далее.
3.1 Инструменты упаковки (свертывание против веб-пакета)
На рынке существует бесчисленное множество инструментов для упаковки, и самым популярным, конечно же, является необходимость.инженер-конфигураторСпециально сконфигурированный веб-пакет, но у него есть грозный противник в области разработки библиотеки классов — роллап.
Основные библиотеки на современном рынке в основном выбирают rollup в качестве инструмента упаковки, включая Angular React и Vue, преимущества rollup как инструмента упаковки для библиотек базовых классов заключаются в следующем:
- Tree Shaking: автоматически удалять неиспользуемый код, выводить файлы меньшего размера.
- Scope Hoisting: все модули встроены в одну функцию, и выполнение становится более эффективным.
- Файл конфигурации поддерживает запись в формате модуля ESM. Несколько форматов могут быть выведены одновременно:
- Технические характеристики модуля: IIFE, AMD, CJS, UMD, ESM Версии разработки и производства: .js, .min.js
Хотя некоторые из вышеперечисленных функций были реализованы в веб-пакете, свертки, очевидно, были введены раньше, а Scope Hoisting — это убийца, потому что веб-пакету приходится создавать модульную систему в коде пакета для адаптации к разработке приложений (модульная система очень полезна для одиночная библиотека классов), подход Scope Hoisting к построению модулей внутри функции больше подходит для упаковки библиотек классов.
3.2 Обнаружение кода
Из-за различных странных особенностей JavaScript и появления масштабных фронтенд-проектов инструменты обнаружения кода стали стандартом для фронтенд-разработчиков. Crockford personal color., Антон Ковалев выпустил расширяемый JSHint в 2011 году, потому что не выносил нерасширяемого поведения JSLint, и JSHint на какое-то время стал популярным решением для фронтенд-инспекции кода.
Затем, в 2013 году, Николас С. Закас разработал новый инструмент ESLint на основе AST для Lint ввиду недостаточной гибкости расширения JSHint. С ростом популярности ES6 он доминировал во внешнем мире. Функция ESLint для анализа JavaScript на основе Esprima чрезвычайно простое расширение, JSHint долгое время не мог поддерживать синтаксис ES6, и его обогнал ESLint.
Однако ESLint находится в слабом положении в области Typescript, появление TSLint происходит намного раньше, чем ESLint официально поддерживает Typescript.В настоящее время TSLint, по-видимому, является де-факто инструментом проверки кода для TS..
Примечание: статья была написана ранее, я не ожидал, что TS официально назначил ESLint некоторое время назад, и TSLint потерял популярность.Официальным стандартным инструментом обнаружения кода на будущее должен быть ESLint, но TSLint все еще широко используется, и это все еще можно безопасно использовать
Инструменты обнаружения кода являются одним из аспектов, а стили обнаружения кода также требуют, чтобы мы выбрали. Самый популярный стиль обнаружения кода на рынке должен быть произведен Airbnbeslint-config-airbnb
, его самая большая особенность в том, что он крайне строг и не оставляет разработчикам никакого выбора.Конечно, такой строгий стиль кода располагает к совместной разработке масштабных front-end проектов, но как инструмент обнаружения кода для класса библиотека, она не не подходит, поэтому мы выбралиeslint-config-standard
Это относительно более свободный стиль проверки кода.
3.3 спецификация фиксации
Какой из следующих двух коммитов является более строгим и простым в обслуживании?
Когда я впервые использовал фиксацию, я часто совершал ошибку, показанную на рисунке ниже. Я не осознавал свою ошибку, пока не увидел коммиты многих библиотек звездных классов. Написание хорошего сообщения о коммите не только помогает другим просматривать, но также может эффективно вывод CHANGELOG, да Управление проектом на самом деле очень важно.
В настоящее время популярным решением является команда Angular.Технические характеристики, общая спецификация головы выглядит следующим образом:
- тип: тип фиксации
- подвиг: новые функции
- исправить: решить проблему
- рефакторинг: рефакторинг кода
- документы: Изменения в документации
- стиль: модификация формата кода, обратите внимание, что это не модификация css
- тест: модификация тестового примера
- рутинная работа: другие модификации, такие как процесс сборки, управление зависимостями.
- scope: область затронутой фиксации, например: маршрут, компонент, утилиты, сборка...
- предмет: Обзор фиксации, предложенной для соблюдения форматирования 50/72
- тело: зафиксируйте определенное содержимое модификации, которое можно разделить на несколько строк, рекомендуется соответствовать форматированию 50/72
- нижний колонтитул: некоторые примечания, обычно ссылка на КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ или исправленную ошибку.
Конечно, люди не обязательно следуют нормам, и я не следовал строго таким нормам, когда впервые узнал о них, потому что люди всегда ленивы, пока не воспользуются ими.commitizen
Интегрируйте эту спецификацию в поток инструментов, и каждый коммит должен будет следовать спецификации.
Я специально ссылаюсь на эту статью:Изящно зафиксируйте сообщение Git Commit Message
3.4 Инструменты тестирования
Из-за частых изменений требований к интерфейсу при разработке бизнеса требования к интерфейсу для тестирования не так высоки, как к бэкенду, поскольку бизнес-логика бэкенда меняется очень мало, она больше подходит для тестирования. .
Тем не менее, базовая библиотека классов как повторно зависимый модуль и относительно стабильный спрос должны быть протестированы.Внешнюю тестовую библиотеку также можно описать как разновидность.После сравнения я выбрал текущую наиболее популярную также выбранную три основных фрейма JEST как тестовый инструмент, его преимущества очевидны:
- Из коробки встроенные ассерты, инструменты тестового покрытия, если используете MoCha, можно вручную настроить еще n
- Функция моментального снимка, Jest может использовать свою уникальную функцию проверки моментальных снимков для сравнения файлов моментальных снимков, сгенерированных кодом пользовательского интерфейса.
- Преимущество в скорости, тестовые случаи Jest выполняются параллельно, и выполняются только тесты, соответствующие измененным файлам, что повышает скорость тестирования.
3.5 Другое
Конечно, вышеперечисленное является выбором основных инструментов, есть такие, как:
- Инструмент улучшения кода делает красивее, освобождает человеческую плоть для украшения и в то же время способствует одинаковому стилю сотрудничества между разными людьми.
- Инструмент непрерывной интеграции travis-ci освобождает человеческую плоть для тестирования ворсинок, что полезно для обеспечения надежности каждого нажатия.
3.6 Быстрый запуск лесов
Итак, мы должны каждый раз писать выше конфигурацию? Конкретная реализация компонента является ядром библиотеки компонентов. Почему мы тратим столько времени на конфигурацию?
Когда мы создаем проекты APP, мы обычно используем официальные шаблоны, предоставляемые фреймворком, такие как create-react-app в React, Angular-Cli в Angular и т. д., поэтому можем ли мы иметь быстрый старт для разработки компонентов?
Да, недавно я разработал инструмент командной строки, чтобы быстро начать разработку библиотеки компонентов.create-component
использовать
create-component init <name>
Чтобы быстро начать проект, мы предоставляем множество дополнительных конфигураций. Пока вы сделали технический выбор, вы можете выбрать конфигурацию в соответствии с подсказками. Create-component автоматически сгенерирует леса в соответствии с конфигурацией. Вдохновение исходит от vue-кли и угловой-кли.
4. Как спроектировать карусельный компонент
Сказал много теорий, то как бороться с ним? Общий дизайн компонентов попробуйте!
4.1 Основной принцип карусели
Карусель, известная как вращающийся свет в Antd, может быть одним из самых распространенных компонентов для разработчиков интерфейса, и мы всегда можем увидеть его на стороне ПК или на стороне мобильных устройств.
Итак, как мы обычно используем карусель?Код Antd выглядит следующим образом
<Carousel>
<div><h3>1</h3></div>
<div><h3>2</h3></div>
<div><h3>3</h3></div>
<div><h3>4</h3></div>
</Carousel>
Проблема в том, что мыCarousel
четыре группыdiv
Зачем показывать только одну группу за раз?
На рисунке красная рамка представляет собой видимую область, расположение видимой области исправлено, и нам нужно только вернуться назад.div
Позиция 1 2 3 4 может обеспечить эффект карусели из четырех подкомпонентов, тогда подкомпонент 2 можно увидеть в видимой области в настоящее время, 1 3 4 должен быть скрыт, что требует от нас установить свойство переполнения в скрытое чтобы скрыть подкомпоненты в невидимых областях.
Скопируйте и просмотрите анимацию:Блог images2015.cn на.com/blog/979044…
Так что это более очевидно, мы проектируем компонент визуального окнаFrame
, то четыреdiv
Соберите вместе компоненты портфолио слайдовSlideList
в сочетании сSlideItem
соответственноdiv
В завершенном виде фактический код должен выглядеть так:
<Frame>
<SlideList>
<SlideItem>
<div><h3>1</h3></div>
</SlideItem>
<SlideItem>
<div><h3>2</h3></div>
</SlideItem>
<SlideItem>
<div><h3>3</h3></div>
</SlideItem>
<SlideItem>
<div><h3>4</h3></div>
</SlideItem>
</SlideList>
</Frame>
мы продолжаем использоватьtranslateX
изменитьSlideList
положение для достижения эффекта карусели, как показано на рисунке ниже, каждый раз, когда карусель запускается путем измененияtransform: translateX()
работать
4.2 Базовая реализация карусельной диаграммы
Это относительно легко реализовать, если вы понимаете основные принципы.Давайте возьмем реализацию мобильного терминала в качестве примера для реализации базовой карусельной карты мобильного терминала.
Сначала мы должны определитьвидимое окноширина, потому что нам нужна эта ширина для вычисленияSlideList
длина(SlideList
Длина обычно кратна видимому окну, например, если вы хотите поставить три картинки, тоSlideList
должно быть как минимум в 3 раза больше видимого окна), иначе мы не сможем пройтиtranslateX
чтобы переместить его.
мы проходимgetBoundingClientRect
чтобы получить реальную длину видимой области,SlideList
Тогда длина:
slideListWidth = (len + 2) * width
(len — количество входящих подкомпонентов, width — ширина видимой области)
Что касается того, почему+2
будут упомянуты позже.
/**
* 设置轮播区域尺寸
* @param x
*/
private setSize(x?: number) {
const { width } = this.frameRef.current!.getBoundingClientRect()
const len = React.Children.count(this.props.children)
const total = len + 2
this.setState({
slideItemWidth: width,
slideListWidth: total * width,
total,
translateX: -width * this.state.currentIndex,
startPositionX: x !== undefined ? x : 0,
})
}
Как реализовать карусель после получения общей длины? Нам нужно запустить карусель на основе отзывов пользователей. На мобильной стороне карусель обычно запускается скольжением пальца, для чего требуется три событияonTouchStart
onTouchMove
onTouchEnd
.
onTouchStart
Как следует из названия, это событие, которое запускается, когда палец касается экрана.В этом случае нам нужно только записать координату x горизонтальной оси пальца, касающегося экрана, потому что мы будем судить, запущена ли карусель расстояние его горизонтального скольжения.
/**
* 处理触摸起始时的事件
*
* @private
* @param {React.TouchEvent} e
* @memberof Carousel
*/
private onTouchStart(e: React.TouchEvent) {
clearInterval(this.autoPlayTimer)
// 获取起始的横轴坐标
const { x } = getPosition(e)
this.setSize(x)
this.setState({
startPositionX: x,
})
}
onTouchMove
Как следует из названия, это событие в скользящем состоянии, это событие находится вonTouchStart
После запуска,onTouchEnd
Перед запуском мы в основном делаем две вещи в этом событии: одна — определить направление скольжения, потому что пользователь может провести пальцем влево или вправо, а другая — позволить карусели двигаться пальцем, что необходимо для обратной связи с пользователем.
/**
* 当触摸滑动时处理事件
*
* @private
* @param {React.TouchEvent} e
* @memberof Carousel
*/
private onTouchMove(e: React.TouchEvent) {
const { slideItemWidth, currentIndex, startPositionX } = this.state
const { x } = getPosition(e)
const deltaX = x - startPositionX
// 判断滑动方向
const direction = deltaX > 0 ? 'right' : 'left'
this.setState({
direction,
moveDeltaX: deltaX,
// 改变translateX来达到轮播组件跟随手指移动的效果
translateX: -(slideItemWidth * currentIndex) + deltaX,
})
}
onTouchEnd
Как следует из названия, это событие, которое запускается, когда скольжение завершено, В этом событии мы в основном делаем одну вещь, то есть, чтобы определить, запускать ли карусель, мы установим порогthreshold
, Когда расстояние скольжения превышает этот порог, срабатывает карусель.В конце концов, если нет порога, если пользователь немного коснется изображения карусели, это вызовет карусель, а неправильная работа вызовет очень плохой пользовательский опыт.
/**
* 滑动结束处理的事件
*
* @private
* @memberof Carousel
*/
private onTouchEnd() {
this.autoPlay()
const { moveDeltaX, slideItemWidth, direction } = this.state
const threshold = slideItemWidth * THRESHOLD_PERCENTAGE
// 判断是否轮播
const moveToNext = Math.abs(moveDeltaX) > threshold
if (moveToNext) {
// 如果轮播触发那么进行轮播操作
this.handleSwipe(direction!)
} else {
// 轮播不触发,那么轮播图回到原位
this.handleMisoperation()
}
}
4.3 Анимационный эффект карусели
Наша обычная карусель точно не тупой переключатель, обычно в карусели будет градиентная или плавная анимация, что требует от нас добавления анимационных эффектов.
Обычно у нас есть два варианта создания анимации: один — использовать анимационные эффекты, поставляемые с CSS3, а другой — использовать анимационные эффекты, предоставляемые браузером.requestAnimationFrame API
Что лучше?CSS3 прост в использовании, прост в использовании и имеет хорошую совместимость.requestAnimationFrame
Он более гибкий и может реализовать анимации, которых не может достичь CSS3, например, множество анимаций плавности CSS3 беспомощен, поэтому мы, без сомнения, выбралиrequestAnimationFrame
.
См. Чжан Синьсюй для сравнения двух сторон.Анимация CSS3 настолько сильна, что requestAnimationFrame все еще используется для шерсти?
я хочу использоватьrequestAnimationFrame
Для достижения эффекта плавности требуется определенная функция плавности Ниже приведена типичная функция плавности.
type tweenFunction = (t: number, b: number, _c: number, d: number) => number
const easeInOutQuad: tweenFunction = (t, b, _c, d) => {
const c = _c - b;
if ((t /= d / 2) < 1) {
return c / 2 * t * t + b;
} else {
return -c / 2 * ((--t) * (t - 2) - 1) + b;
}
}
Функция смягчения принимает четыре параметра, а именно:
- т: время
- б: исходное положение
- _c: конечная позиция
- д: скорость
С помощью этой функции мы можем вычислить положение каждого кадра карусели следующим образом:
После получения позиции, соответствующей каждому кадру, нам нужно использоватьrequestAnimationFrame
Сохраняйте рекурсивные вызовы для перемещения позиций по очереди, мы продолжаем вызыватьanimation
Функция находится внутри тела своей триггерной функции.this.setState({ translateX: tweenQueue[0], })
Чтобы достичь цели перемещения позиции карусели, в настоящее время быстрое выполнение 30 позиций в массиве по очереди является эффектом замедления анимации.
/**
* 递归调用,根据轨迹运动
*
* @private
* @param {number[]} tweenQueue
* @param {number} newIndex
* @memberof Carousel
*/
private animation(tweenQueue: number[], newIndex: number) {
if (tweenQueue.length < 1) {
this.handleOperationEnd(newIndex)
return
}
this.setState({
translateX: tweenQueue[0],
})
tweenQueue.shift()
this.rafId = requestAnimationFrame(() => this.animation(tweenQueue, newIndex))
}
Но мы обнаружили проблему, когда мы передвинули карусель в конец, возникла проблема с анимацией,Когда мы прокручиваем последнюю карусель влевоdiv4
, в этом случае картинка должна скользить влево, а затем первая карусельная картинкаdiv1
Входим в видимую область, но ненормально то, что картинка быстро сползает вправоdiv1
Появляется в районе но...
Поскольку мы устанавливаем положение 4 в положение 1 в это время, чтобы достичь цели непрерывного обращения, но это также вызывает этот побочный эффект, поведение изображения противоречит поведению пользователя (пользователь проводит пальцем влево, изображение переходит Направо).
В настоящее время общепринятой практикой в отрасли является соединение изображений встык, например, изображение 1 соединяется с изображением 4 впереди, а за изображением 4 следует изображение 1. Вот почему необходимо рассчитать длина перед.+2
slideListWidth = (len + 2) * width
(len — количество входящих подкомпонентов, width — ширина видимой области)
Когда мы перемещаем изображение 4, описанная выше ситуация сдвига изображения влево, но перемещение вправо не произойдет, потому что реальная ситуация такова:
图片4 -- 滑动为 -> 伪图片1
То есть позиция 5 становится позицией 6
Когда анимация закончилась, быстро ставим伪图片1
установлен на真图片1
, на самом деле это обман, то есть анимация на самом деле выполняется во время图片4
прибыть伪图片1
процесс, когда он закончится, мы тайно伪图片1
заменить真图片1
, потому что две картинки совершенно одинаковые, поэтому пользователь вообще не может видеть процесс конвертации...
Таким образом, мы можем добиться плавного переключения карусельных изображений.
4.4 Направление улучшения
Мы реализовали основные функции карусели, но ее универсальность все еще несовершенна:
- Кастомизация точек подсказки: Моя реализация — это маленькая точка, а антд — полоса, это место может полностью передать решение структуры dom разработчику.
- Настройка ориентации: эта карусель может быть реализована только в горизонтальном направлении, на самом деле она также может иметь вертикальную карусель.
- Множественная карусель: в дополнение к одной карусели также доступны несколько каруселей.
Выше перечислены все направления, в которых можно расширить карусельную карту, связанные с оптимизацией производительности.
В нашем конкретном коде есть связанная реализация. Наше карусельное изображение на самом деле имеет функцию автоматической карусели, но во многих случаях страница не отображается на странице пользователя. Мы можем автоматически отменить завершение таймера в зависимости от того, скрыта страница или нет. играть.
githubадрес проекта
Вышеприведенная демонстрация предназначена только для справки.В реальной разработке проекта лучше использовать зрелые компоненты с открытым исходным кодом.Необходимо иметь возможность строить колеса и осознавать, что нельзя строить колеса.