Компонент предварительного просмотра изображения для создания колес (предварительная версия)

Vue.js

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

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

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

Ничего плохого в таком подходе нет, конечно есть готовые колеса, ведь в проекте используютсяvue, поэтому я посмотрел в Интернете на основеvueУвеличенная библиотека компонентов предварительного просмотра в формате .Мудрость угаслаДело в том, что большинство этих скудных библиотек компонентов основаны наPhotoSwipeВ дополнение к вторичной инкапсуляции, выполняемой этой библиотекой с открытым исходным кодом, библиотека компонентов предварительного просмотра, которую можно использовать в реальном производстве (image gallery)...Похоже, что нет (а может, мои познания недальновидны), эта ситуация не только отражена вvueВ библиотеке другие рамки и даже нативные связанные библиотеки одинаковы

Хотя переделывать колеса не рекомендуется, немного неразумно, что колес слишком мало и выбора нет.PhotoSwipeОн прост в использовании и имеет полный набор функций, достаточный для работы с большинством сценариев в реальной производственной среде.

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

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

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

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

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

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

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

обработка данных иКарусельный компонент изготовления колес (swiper)Первый способ такой же, поэтому больше говорить не буду:

<VueActivePreview :urlList="urlList" />

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

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

Смахивание одним пальцем

Основная логика скольжения одним пальцем иКарусельный компонент изготовления колес (swiper)похожи друг на друга, как рассчитывают расстояние, на которое скользит палец, так и путем постоянного измененияtranslateзначение для сдвига

Разведите пальцы, чтобы увеличить

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

Получите расстояние между двумя пальцами:

getDistance (p1, p2) {
  return Math.sqrt(Math.pow(p2.clientX - p1.clientX, 2) + Math.pow(p2.clientY - p1.clientY, 2))
}

Получите коэффициент масштабирования изображения:

this.scaleValue = this.getDistance(targetTouch1, targetTouch2) / doubleTransferInfo.startDistance

Путем измененияtransform: scale(scaleValue), вы можете реализовать масштабирование изображения в реальном времени

Однако в настоящее время существует проблема, заключающаяся в том, чтоCSS3 scaleКоордината центра масштабирования, по умолчанию50% 50% 0, то есть центральное положение элемента, поэтому, если вы не установитеtransform-originВ случае прямой установкиscale, то картинку тоже можно нормально масштабировать, но результат масштабирования не обязательно будет таким, как вы хотите

Например, координаты центра двух пальцев равны(10, 56), по нормальным привычкам, при приближении должно приближаться все изображение с этой точкой как центром, а не центром изображения

Есть два способа решить эту проблему

  • Динамические настройкиtransform-origin

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

Так что это должно быть установлено динамическиtransform-origin

const targetTouch1 = e.touches[0]
const targetTouch2 = e.touches[1]
this.transOriginX = (targetTouch1.clientX + targetTouch2.clientX) / 2 - this.left
this.transOriginY = (targetTouch1.clientY + targetTouch2.clientY) / 2 - this.top
  • Динамически задавать координаты положения изображения

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

Например, изображение по умолчаниюtransform-originда(100, 100), если увеличить вдвое по центру, то после увеличения левый верхний угол изображения смещается влево по сравнению с исходным состоянием100единицы, но теперь координаты начального центра двух пальцев равны(0, 0)(просто предположение, для простоты расчета) и установите это значениеtransform-roginПри двукратном увеличении левый верхний угол изображения будет смещен влево по сравнению с исходным состоянием.0единиц, т.е. без какого-либо смещения

Итак, когда центр масштабирования(0, 0), без измененияtransform-originВ случае , чтобы сохранить визуальное единство, изображение должно непрерывно перемещаться вправо в процессе увеличения изображения, чтобы гарантировать, что расстояние, перемещаемое в каждом кадре, может быть смещено, потому чтоtransform-originдовести разрыв до окончательного удаления100единицы

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

but, я вскоре обнаружил, что я все еще мыслил слишком упрощенно

Предположим теперь, что после отрыва пальца от экрана картинка начинается с(10, 56)Увеличено для увеличения центра2раз, а затем снова коснитесь экрана двумя пальцами. В это время центральные координаты двух пальцев(70, 88), в это время, согласно вышеизложенному, необходимо динамическиtransform-originпредыдущим(10, 56)изменить на(70, 88), но если вы действительно измените его, вы обнаружите, что картинка сразу прыгает

Это связано с тем, что перед вторым касанием экрана двумя пальцами состояние, в котором изображение двоится, основано наtransform-origin(10, 56), сейчас изменилосьtransform-origin, что эквивалентно изменению базовой точки увеличения изображения, и состояние изображения неизбежно изменится

Стоит ли переходить на второй способ? Но всегда хочется часто менятьсяleft/topЗначение немного неверно, и метод расчета этого метода также более сложен, беспокоясь о влиянии на производительность.

Подумайте об этом внимательно, это может быть решено

Причина, по которой второй зум вызовет прыжки, заключается в том, что состояние после первого раза изменяется, потому что это состояние не является фиксированным.scaleа такжеtransform-originВсе они модифицируются динамически.Пока это состояние можно зафиксировать и зафиксировать на значение состояния по умолчанию, то оно не будет работать?

Что касается того, как исправить это состояние, то это на самом деле очень просто

Для размера100*100картинки к(10, 10)дляtransform-originувеличить2раз, размер увеличенного изображения200*200, смещение верхнего левого угла равно(-10, -10) SOСразу после первого масштабирования установите ширину и высоту изображения на200*200, и датьleft: -10; top: -10смещение, то вы можетеscaleа такжеtransform-originВосстановлено состояние по умолчанию, состояние изображения на данный момент равносильно тому, что не используются никакиеtransformАтрибуты

Затем при масштабировании во второй раз начальное состояние является текущим.200*200изображение является начальным состоянием, а не началом100*100, то вам не нужно заботиться о состоянии, потому что каждый масштаб — это совершенно новое состояние

Интуитивно понятный пример:

transform: scale(2);  =>  width: 200; height: 200;
transform-origin: 10 10;  =>  left: -10; top: -10;

Пример кода:

this.left = left
this.top = top
this.currentW = currentW
this.currentH = currentH
this.scaleValue = 1

Проведите для просмотра во время масштабирования

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

Основная логика этой функции относительно проста, путем мониторингаtouchсобытие, рассчитанное для получения времени между каждым кадромmoveРасстояние, вы можете динамически перемещать положение изображения

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

Есть два решения для этого сценария

  • css анимация

В момент окончания касания, используя в качестве условия текущую скорость, рассчитайте, как далеко должна проскользнуть картинка, прежде чем она остановится, и установите скорость, которая постепенно уменьшается.transitionанимация

  • js динамическая анимация

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

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

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

главным образом в силуrequestAnimationFrameэтоAPI(уже несовместимо с этимAPIустройство было понижено):

rafHandler = raf(() => {
  speedX *= 0.9
  speedY *= 0.9
  // ...
  if (Math.abs(speedX) < 1) speedX = 0
  if (Math.abs(speedY) < 1) speedY = 0
  if (speedX !== 0 || speedY !== 0) {
    this.frictionMove(speedX, speedY)
  } else {
    // ...
  }
})

Суммировать

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

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