Пользовательский эффект, имитирующий 3D-колесо в Swiper 5

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

https://codepen.io/SitePoint/pen/yXWXaw (найдено здесь)

но с этими дополнительными функциями (особенно на настольных компьютерах и мобильных устройствах):

  1. слайды должны последовательно следовать за смахиванием, то есть слайды должны перемещаться при смахивании (как это делает Swiper).
  2. При быстром смахивании он должен быстро прокрутить множество слайдов (как Swiper делает с freeScroll).
  3. Затем, когда колесо перестает вращаться, оно переключается на передний суппорт (как Swiper делает с freeModeSticky и centeredSlides), что он выбран пользователем.
  4. Мне нужен обратный вызов каждый раз при смене слайда (такое событие, как slideChanged) (как это делает Swiper).

По всем этим причинам я подумал, что Swiper 5.3.0 будет хорошей отправной точкой.

Я пробовал различные обходные пути, лучший вариант с этой настройкой, но loop: true - ужасный обходной путь и вызывает проблемы (проверьте комментарии):

  var swiper = new Swiper(el_class, {
    slidesPerView: 1.5,
    spaceBetween: 25,
    centeredSlides: true,
    grabCursor: true,
    speed: 550,
    loop: true, // <== repeat infinitely the 12 items. with fast scroll at the end of a cycle it waits a while before render the next cycle. Awful
    loopAdditionalSlides: 10, 

    // Free mode
    freeMode: true, // <== free scrolling. Good
    freeModeMomentumRatio: 1,
    freeModeMomentumVelocityRatio: 1.5,
    freeModeMomentumBounceRatio: 1,
    freeModeMinimumVelocity: 0.02,
    freeModeSticky: true, // <== snap to the slides. Good

    // Touch Resistance
    resistanceRatio: 0.85,

    // Prevent blurry texts
    roundLengths: true,

  });

Определенно неправильный путь.

Я думаю, что правильный способ - разработать собственный Swiper effect (например, встроенный cubeEffect, coverflowEffect, ...), который имитирует колесо, без использования loop:true, вызывающего проблемы. Например, здесь парень создает свой собственный эффект, который затем устанавливает в атрибуте effect Swiper: https://codepen.io/paralleluniv3rse/pen/yGQjMv

...
effect: "myCustomTransition",
...

Как создать собственный эффект, например, нужное мне 3D-колесо?


person Fred K    schedule 16.01.2020    source источник
comment
Интересно, будет ли работа с этим эффектом в качестве отправной точки наиболее выгодным способом: swiperjs. com / demos / 240-effect-coverflow.html. Мне любопытно перемещать прошлые слайды по отрицательной оси x, хотя, чтобы вернуться к правой стороне слайдера для повторного воспроизведения в шоу ...   -  person Phlume    schedule 20.01.2020
comment
@Phlume Уже пытался работать с coverflowEffect в качестве отправной точки и взламывать его параметры, но это всего лишь обходной путь, и я не могу получить эффект от первого кода. Слайды просто невозможно разместить на круглой поверхности.   -  person Fred K    schedule 20.01.2020
comment
Извините, не могли бы вы пояснить, что бы вы хотели сделать? Вы хотите, чтобы карусель можно было вращать без нажатия кнопок «предыдущий / следующий»?   -  person Mukyuu    schedule 22.01.2020
comment
@Mukyuu Обновлен вопрос с подробностями   -  person Fred K    schedule 22.01.2020


Ответы (1)


Думаю, это то, что вам нужно: https://codepen.io/mukyuu/pen/GRgPYqG.

Он почти выполнил ваши условия, за исключением того, что он не использует Swiper 5 и оснастку.

  1. Он вращается вместе с направлением движения.
  2. При быстром смахивании он должен быстро прокрутить множество слайдов (как это делает Swiper).
  3. Затем, когда колесо перестает вращаться, оно фиксируется на слайде (как это делает Swiper).
  4. В функции ontouch есть обратный вызов.

HTML:

<div class="carousel" id="wrapper">
    <figure>
    <img src="https://source.unsplash.com/7mUXaBBrhoA/800x533" alt="">
    <img src="https://source.unsplash.com/bjhrzvzZeq4/800x533" alt="">
        <img src="https://source.unsplash.com/EbuaKnSm8Zw/800x533" alt="">
        <img src="https://source.unsplash.com/kG38b7CFzTY/800x533" alt="">
        <img src="https://source.unsplash.com/nvzvOPQW0gc/800x533" alt="">
        <img src="https://source.unsplash.com/mCg0ZgD7BgU/800x533" alt="">
    <img src="https://source.unsplash.com/1FWICvPQdkY/800x533" alt="">
        <img src="https://source.unsplash.com/VkwRmha1_tI/800x533" alt="">
    </figure>
</div>

S (CSS):

body {
    margin: 0;
    font-family: 'Roboto';
    font-size: 16px;

    display: flex;
    flex-direction: column;
    height: 100vh;
    justify-content: center;
}

// Carousel configuration parameters
$n: 8;
$item-width: 400px;
$item-separation: 80px;
$viewer-distance: 500px;

// Derived variables
$theta: 2 * 3.141592653589793 / $n; 
$apothem: 482.842712474619px;

.carousel {
    padding: 20px;

    perspective: $viewer-distance;
    overflow: hidden;

    display: flex;
    flex-direction: column;
    align-items: center;
    > * {
        flex: 0 0 auto;
    }

    figure {
        cursor: grab;
        margin: 0;

        width: $item-width;
        transform-style: preserve-3d;
        transition: transform 0.5s;
        transform-origin: 50% 50% (-$apothem);

        img {
            width: 100%;
            box-sizing: border-box;
            padding: 0 $item-separation / 2;

            opacity: 0.9;

            &:not(:first-of-type) {
                position: absolute;
                left: 0;
                top: 0;
                transform-origin: 50% 50% (-$apothem);
            }

            @for $i from 2 through $n {
                &:nth-child(#{$i}) {
                    transform: rotateY(#{($i - 1) * $theta}rad);
                }
            }
        }
    }

    nav {
        display: flex;
        justify-content: center;
        margin: 20px 0 0;

        button {
            flex: 0 0 auto;
            margin: 0 5px;

            cursor: pointer;

            color: #333;
            background: none;
            border: 1px solid;
            letter-spacing: 1px;
            padding: 5px 10px;
        }
    }
}

JS:

var
    carousel = document.querySelector('.carousel'),
    figure = carousel.querySelector('figure'),
    nav = carousel.querySelector('nav'),
    numImages = figure.childElementCount,
    theta =  2 * Math.PI / numImages,
    currImage = 0
;

// add touch detect:
function ontouch(el, callback){
 // Modified from http://www.javascriptkit.com/javatutors/touchevents3.shtml
    var touchsurface = el,
    dir,
    swipeType,
    startX,
    startY,
    distX,
    distY,
    threshold = 150, //required min distance traveled to be considered swipe
    restraint = 100, // maximum distance allowed at the same time in perpendicular direction
    allowedTime = 500, // maximum time allowed to travel that distance
    elapsedTime,
    startTime,
    handletouch = callback || function(evt, dir, phase, swipetype, distance){}

    touchsurface.addEventListener('touchstart', function(e){
        var touchobj = e.changedTouches[0]
        dir = 'none'
        swipeType = 'none'
        dist = 0
        startX = touchobj.pageX
        startY = touchobj.pageY
        startTime = new Date().getTime() // record time when finger first makes contact with surface
        handletouch(e, 'none', 'start', swipeType, 0) // fire callback function with params dir="none", phase="start", swipetype="none" etc
        e.preventDefault()

    }, false)

    touchsurface.addEventListener('touchmove', function(e){
        var touchobj = e.changedTouches[0]
        distX = touchobj.pageX - startX // get horizontal dist traveled by finger while in contact with surface
        distY = touchobj.pageY - startY // get vertical dist traveled by finger while in contact with surface
        if (Math.abs(distX) > Math.abs(distY)){ // if distance traveled horizontally is greater than vertically, consider this a horizontal movement
            dir = (distX < 0)? 'left' : 'right'
            handletouch(e, dir, 'move', swipeType, distX) // fire callback function with params dir="left|right", phase="move", swipetype="none" etc
        }
        else{ // else consider this a vertical movement
            dir = (distY < 0)? 'up' : 'down'
            handletouch(e, dir, 'move', swipeType, distY) // fire callback function with params dir="up|down", phase="move", swipetype="none" etc
        }
        e.preventDefault() // prevent scrolling when inside DIV
    }, false)

    touchsurface.addEventListener('touchend', function(e){
        var touchobj = e.changedTouches[0]
        elapsedTime = new Date().getTime() - startTime // get time elapsed
        if (elapsedTime <= allowedTime){ // first condition for awipe met
            if (Math.abs(distX) >= threshold && Math.abs(distY) <= restraint){ // 2nd condition for horizontal swipe met
                swipeType = dir // set swipeType to either "left" or "right"
            }
            else if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint){ // 2nd condition for vertical swipe met
                swipeType = dir // set swipeType to either "top" or "down"
            }
        }
        // Fire callback function with params dir="left|right|up|down", phase="end", swipetype=dir etc:
        handletouch(e, dir, 'end', swipeType, (dir =='left' || dir =='right')? distX : distY)
        e.preventDefault()
    }, false)
}
function DoSomething(dir, distance) {
  //modifiy this function for wheel rotation (prev/next) images
  var momentum = 100; // modify this value for how much momentum expected to switch to next/prev images
  switch (dir){
    case 'left':
    case 'right':
      currImage+= Math.round(distance/momentum);
      break;
  }
    figure.style.transform = `rotateY(${currImage * -theta}rad)`;
}
document.getElementById('wrapper').ondragstart = function() { return false; }; // prevent image dragged on mouse drag
window.addEventListener('load', function() {
  var dir, phase, el = document.getElementById('wrapper'),
    position = {
      X: 0,
      Y: 0
    };

  el.onmousedown = function(down) {
    position.X = down.clientX;
    position.Y = down.clientY;
  };

  el.onmouseup = function(up) {
    distX = up.clientX - position.X; // get horizontal dist traveled by finger while in contact with surface
    distY = position.Y - up.clientY; // get vertical dist traveled by finger while in contact with surface
    if (Math.abs(distX) > Math.abs(distY)) { // if distance traveled horizontally is greater than vertically, consider this a horizontal movement
      dir = (distX < 0) ? 'left' : 'right';
      distance = distX;
    } else { // else consider this a vertical movement
      dir = (distY < 0) ? 'down' : 'up';
      distance = distY;
    }
    dir = (distance == 0) ? 'none' : dir;
    DoSomething(dir, distance); // simulate touch from mouse control
  }; 
  ontouch(el, function(evt, dir, phase, swipetype, distance){
 // evt: contains original Event object
 // dir: contains "none", "left", "right", "top", or "down"
 // phase: contains "start", "move", or "end"
 // swipetype: contains "none", "left", "right", "top", or "down"
 // distance: distance traveled either horizontally or vertically, depending on dir value

 if ( phase == 'end' && (dir =='left' || dir == 'right') ) // on succesful swipe
   DoSomething(dir, distance);
})
}, false)

Протестировано в браузерах Android 9 и Windows 10.

person Mukyuu    schedule 22.01.2020
comment
эээ .. я смахиваю слева направо, и колесо вращается влево .... хотя выглядит круто - person Tschallacka; 22.01.2020
comment
Между тем, большое спасибо за ваш ответ, но он не отвечает многим требованиям: 1), как указано @Tschallacka, он вращается в обратном направлении. 2) слайды не следует за смахиванием, слайды должны перемещать смахивание при смахивании (как это делает Swiper). 3) При быстром смахивании он должен быстро прокрутить множество слайдов (как это делает Swiper). 4) Затем, когда колесо перестает вращаться, оно переключается на ползун (как это делает Swiper). 5) Мне нужен обратный вызов для такого события, как slideChanged (как это делает Swiper). По всем этим причинам я подумал, что Swiper будет хорошей отправной точкой ... - person Fred K; 22.01.2020
comment
Принято к сведению. Я изменил вращение в обратном направлении и добавил немного импульса, я постараюсь посмотреть, что я мог бы придумать с помощью Swiperjs. Скажите, если что-то нужно улучшить. - person Mukyuu; 23.01.2020