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

9

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

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?

Фред К
источник
Интересно, будет ли лучше работать с этим эффектом в качестве отправной точки: swiperjs.com/demos/240-effect-coverflow.html . Мне любопытно перенести «прошлые слайды» на отрицательную ось х, хотя я вернусь обратно к правой стороне слайдера для повторного представления на шоу ...
Phlume
1
@Phlume Уже пытался работать в coverflowEffectкачестве отправной точки и «взламывать» его параметры, но это всего лишь обходной путь, и я не могу получить эффект от первого кода ручки. Слайды просто не будут размещены на круглой поверхности.
Фред К
Извините, не могли бы вы уточнить, что вы хотели бы сделать? Хотите, чтобы карусель вращалась без нажатия кнопок «предыдущий / следующий»?
Mukyuu
1
@Mukyuu Обновленный вопросный пост с деталями
Фред К

Ответы:

2

Я думаю, что это то, что вы хотите: https://codepen.io/mukyuu/pen/GRgPYqG .

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

  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.

Mukyuu
источник
3
э-э-э ... я провожу слева направо, а колесо вращается влево .... хотя выглядит круто
Чаллака
2
Между тем очень большое спасибо за ваш ответ, но он не отвечает многим требованиям: 1) как заявлено @Tschallacka, он вращается в обратном направлении. 2) слайды не следуют за движением, слайды должны перемещать движение во время смахивания (как это делает Swiper). 3) Быстро проведя пальцем, он должен прокрутить множество слайдов с импульсом (как это делает Swiper). 4) Затем, когда колесо перестает вращаться, оно защелкивается на слайде (как это делает Swiper).5) Мне нужен обратный вызов на событие, как slideChanged(как делает Swiper). По всем этим причинам я думал, что Swiper будет хорошей отправной точкой ...
Фред К
Отметил. Я изменил вращение в обратном направлении и добавил немного импульса, я постараюсь посмотреть, что я могу придумать со Swiperjs. Скажите, если что-то нуждается в улучшении.
Мукюу