Карусельный компонент изготовления колес (swiper)

Vue.js

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

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

  • Откройте холодильникЗапустить Гитхаб
  • поискswiper,slider,Albumдругие ключевые слова
  • найти нужную библиотеку,npm installИз

Ничего плохого в таком подходе нет, конечно есть готовые колеса, ведь в проекте используютсяvue, поэтому я посмотрел в Интернете на основеvueБиблиотека компонентов карусели из , и нашел две подходящие библиотеки:vue-awesome-swiper,vue-swipe

Наиболее известные карусельные фреймворки обычно используют эту библиотеку в первую очередь, она обладает богатыми функциями и подходит для различных карусельных сценариев, таких как левая и правая кнопки, динамические индикаторы, индикаторы индикатора выполнения, вертикальное переключение и одновременное отображение нескольких изображений.slides...функция не слишком идеальнаbutЯ просто хочу использовать малую часть базовых функций.По мне так много функций не только сложно читать документацию,но и что более важно они будут вносить в проект слишком много избыточного кода.Объем упал,и как Результат, он вернулся до освобождения из-за введения пакета.

Ele.me — это библиотека, созданная фронтенд-командой, относительно упрощенная и с небольшим объемом кода, но слишком упрощенная, например, не поддерживающая бесконечные карусели и не поддерживающая кастомизацию.swiperItemНо всегда чувствовал немного жесткого чувства

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

Давайте посмотрим на окончательный эффект:

Или, если вы хотите испытать это на себе, есть также письменныйDemo

Я упаковал эту функцию вnpm packageПомимо прямой загрузки, шаблон кода, включая объем сжатия, включает в себя18KB, меньше, чем после сжатия7KB,исходный кодзагружено

скользящая форма

Для удобства описания сначала определите существительное и назовите каждый скользящий блок какswiperItem, вызовите контейнер, который содержит все скользящие частиswiper:

В настоящее время большинство библиотек скользящих компонентов реализуют скользящие компоненты двумя способами.

Первый, визуализировать только три одновременноswiperItem, каждый провод к следующемуswiperItemПосле этого сразу обновить триswiperItem

Преимущество этого подхода в том, что независимо от того, сколькоswiperItemЭто не повлияет на производительность рендеринга браузера, потому что независимо от того, сколько их, каждый раз рендерятся только три из них Недостаток в том, что еслиswiperItemЧисло уже меньше трех, что требует дополнительной обработки, и потому, что вы можете скользить не более чем по одному за раз.swiperItemРасстояние не так гладко использовать,vue-swipeиспользуя это

Во-вторых, рендерить все сразуswiperItem, а иногда и для более плавного восприятия оригиналswiperItemв начале и в конце добавить еще по одномуswiperItemНапример, оригиналswiperItemДанные1, 2, 3, 4, 5, после обработки становится5, 1, 2, 3, 4, 5, 1,vue-awesome-swiperиспользуя это

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

После всестороннего рассмотрения я решил принять второй

обработка данных

Эта библиотека компонентов обеспечивает два входящихswiperItemпуть данных

  • Первый заключается в прямом прохожденииpropsПередать массив изображений

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

<swiper :urlList="urlList" />

В этом случае сквозная операция добавления относительно проста, по сути, это работа с массивом:

this.currentList = this.urlList.length > 1
  ? this.urlList.slice(-1).concat(this.urlList, this.urlList.slice(0, 1)).map((url, index) => ({ url, _id: index }))
  : this.urlList.map((url, index) => ({ url, _id: index }))

Затем визуализируйте непосредственно в шаблон:

<div class="img-box" v-for="item in currentList" :key="item._id" :style="{
  backgroundImage: `url(${item.url})`,
  backgroundSize
}"></div>

Кстати, про расположение картинок я прямо не писалimgВместо этого изображение визуализируется как фоновое изображение.Преимущество этой обработки заключается в том, что легко понять, является ли изображение длинным, широким или позиционным.UIControl, если вы хотите, чтобы картинка отображалась полностью, тоbackground-size: contain, если вы хотите заполнить его полностьюbackground-size: cover, либо непосредственно специфичные для настройки пикселей, горизонтальная и вертикальная центровка вообще ничего не требуетdisplay: flex;, эта штука подвержена проблемам совместимости на некоторых устройствах в некоторых случаях напрямуюbackground-position: 50%;возьми

Кроме того, я обычно сталкиваюсь с небольшимиiconМакетvertical-alignНастраивайте медленно, проблем с совместимостью не будет

  • Второй – получитьswiperItemПодсборка

Этот метод дает разработчикам широкие возможности настройки и позволяет настраиватьswiperItemконтент, а не одно изображение, но начать немного сложнее, потому чтоslotКак что-то на уровне компонентов, это непросто обрабатывать динамически, поэтому трудно напрямую манипулировать нативнымAPI? Да-да, но так как фреймворк уже используется, меняем его напрямуюDOMКажется, атмосфера немного не та... Долго запутавшись, я подумал о динамических компонентах.componentтак же какrenderфункция, это решает

Основная идея заключается в том, чтобы представитьswiperItemтак какslotнормально отображается вswiperвнутри этого родительского компонента, но в то же время вslotдо и после, а затем визуализировать по одномуcomponentДинамические компоненты:

<swiper>
  <swiperItem />
  <swiperItem />
  <swiperItem />
</swiper>
<!-- 这是 swiper父组件 -->
<component :is="firstSwiperItem"></component>
<slot></slot>
<component :is="lastSwiperItem"></component>

Эти два размещеныslotпереднее и заднее положениеcomponentдинамические компонентыfirstSwiperItemа такжеlastSwiperItem, как сказано выше5,1,2,3,4,5,1середина5а также1:

updateChild (slots) {
  this.firstSwiperItem = {
    render (h) {
      return h('div', {
        staticClass: 'swiper-item-box'
      }, slots.slice(-1))
    }
  }
  this.lastSwiperItem = {
    render (h) {
      return h('div', {
        staticClass: 'swiper-item-box'
      }, slots.slice(0, 1))
    }
  }
}

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

сенсорное событие

правильноtouchПрослушивание, и комбинированноеtranslate3dИзменение смещения в реальном времени — суть скольжения.

существуетtouchstartКоординаты исходной позиции записываются в событии, вtouchmoveРассчитайте разницу расстояний в случае изменения положения в реальном времени, вtouchendзаканчивая

Логика очень понятна, но некоторые детали все еще вызывают головную боль.

Например, что, если пользователь оперирует несколькими пальцами? еслиtouchstartПри использовании двух пальцевtouchmoveЧто делать, если у меня остался только один палец? Если пользователь сначала прокручивает влево и вправо, как определить, является ли это пролистыванием влево или вправо по сравнению с первоначальным? Если провести по несколькимswiperItem, как определить, был ли это свайп влево или вправо в конце...

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

Для проблемы многопальцевой работы я всегда используюe.touchesПоследний в списке преобладает:

stStartX = e.touches[touchCount - 1].clientX

Проблема свайпа влево-вправо, потом пройтиdiffXс базовой стоимостьюcriticalWidthсравнение в сочетании со скользящими координатамиtoXСделайте двойное суждение и сделайте вывод, используя как можно меньше кода:

// diffX 大于0 说明是右滑,小于0 则是左滑
if (diffX > 0) {
  stDirectionFlag = -1
  stAutoNext = diffX > criticalWidth
  toX = stAutoNext ? -clientW * (activeIndex - 1) : -clientW * activeIndex
} else if (diffX < 0) {
  stDirectionFlag = 1
  stAutoNext = Math.abs(diffX) > criticalWidth
  toX = stAutoNext ? -clientW * (activeIndex + 1) : -clientW * activeIndex
} else {
  stDirectionFlag = 0
  stAutoNext = false
  toX = -clientW * activeIndex
}

Проведите по несколькимswiperItem, то относитесь к этому как к обычному случаю, то есть скользите не более чем по одномуswiperItemслучай, который необходимо обработать:

// 如果连续滑过超过一个 swiperItem 块
if (Math.abs(diffX) > clientW) {
  activeIndex = Math.ceil(-this.transX / clientW)
  diffX = diffX - clientW * wholeBlock
}

Ближе к родному гладкому опыту

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

Оглядываясь вокруг, кажетсяvue-awesome-swiperа такжеvue-swipeНи один из них не предоставляет эту возможность, хотя она и безобидна, но из-за отсутствия этой возможности мне всегда кажется, что нет нативного плавного опыта, поэтому я решил добавить

Для этой функции мы изначально хотелиавто слайддля этого действия используйтеjsдля динамического расчета с использованиемrequestAnimationFrameАвтоматически имитировать слайды анимации, чтобы их можно было легко получить в любое время.swiperItemизtranslateЦенность сейчас есть, а возможность добиться перехвата очень проста.

Но потом я подумал об использованииjsЭкономическая эффективность симуляционной анимации слишком низкая, и легко столкнуться с ситуацией заикания в реальном производственном процессе, поэтому пришлось обратиться к другой реализации.

Передана анимация автоматического скольженияcssДля обработки, когда палец касается скользящейswiperItemкогда черезgetBoundingClientRect APIПолучить положение в реальном времени

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

const isSupportGetBoundingClientRect = typeof document.documentElement.getBoundingClientRect === 'function'
// ...
if (this.isTransToX) {
  if (!isSupportGetBoundingClientRect) {
    return touchStatus = 0
  }
  this.isTransToX = false
  this.transX = stPrevX = this.$refs.sliderWrapper.getBoundingClientRect().left - this.$refs.swiperContainer.getBoundingClientRect().left
}

Суммировать

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

Исходный код был размещенgithubUp, комментарии к коду довольно подробные, если вам интересно, вы можете обратиться к нему, если у вас есть какие-либо вопросы, пожалуйста, не стесняйтесь спрашивать.issues