Efecto personalizado que simula una rueda 3D con Swiper 5

9

Necesito construir un carrusel con 12 elementos que simulan una rueda 3d que gira infinitamente. Para ser claro, necesito crear precisamente este efecto:

https://codepen.io/SitePoint/pen/yXWXaw (encontrado aquí )

pero con estas características adicionales (especialmente en computadoras de escritorio y dispositivos móviles):

  1. las diapositivas deben seguir paso a paso el deslizamiento, es decir, las diapositivas deben moverse mientras se desliza (como lo hace Swiper).
  2. Con un deslizamiento rápido, debe desplazar muchas diapositivas con impulso (como lo hace Swiper con freeScroll).
  3. Luego, cuando la rueda deja de girar, se ajusta a la corredera delantera (como lo hace Swiper con freeModeStickyy centeredSlides) que es la elegida por el usuario.
  4. Necesito una devolución de llamada cada vez que un cambio de diapositiva (como evento slideChanged) (como lo hace Swiper).

Por todas estas razones, pensé que Swiper 5.3.0 sería un buen punto de partida.

Intenté varias soluciones, la mejor es con esta configuración, pero loop: truees una solución horrible y causa problemas (verifique los comentarios):

  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,

  });

Definitivamente no es la forma correcta.

Creo que el camino correcto es el desarrollo de una costumbre Swiper effect(como el incorporado en cubeEffect, coverflowEffect, ...) que simula la rueda, sin necesidad de utilizar loop:trueque las cuestiones causas. Por ejemplo, aquí un chico crea su propio efecto personalizado que luego establece en el effectatributo de Swiper: https://codepen.io/paralleluniv3rse/pen/yGQjMv

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

¿Cómo desarrollar un efecto personalizado como la rueda 3d que necesito?

Fred K
fuente
Me pregunto si trabajar con este efecto como punto de partida sería la mejor manera beneficiosa: swiperjs.com/demos/240-effect-coverflow.html . Sin embargo, tengo curiosidad por mover las "diapositivas pasadas" en un eje x negativo para volver al lado derecho del control deslizante para volver a
producir
1
@Phlume Ya intenté trabajar coverflowEffectcomo punto de partida y "piratear" sus parámetros, pero es solo una solución y no puedo obtener el efecto del primer codepen. Las diapositivas simplemente no se colocarán en una superficie circular.
Fred K
Lo sentimos, ¿puedes aclarar lo que te gustaría hacer? ¿Desea que el carrusel pueda girar sin hacer clic en los botones anterior / siguiente?
Mukyuu
1
@Mukyuu Publicación actualizada de preguntas con detalles
Fred K

Respuestas:

2

Creo que esto es lo que quieres: https://codepen.io/mukyuu/pen/GRgPYqG .

Casi cumplió con sus condiciones, excepto que no está usando Swiper 5 y snap.

  1. Está girando con la dirección del deslizamiento.
  2. Con un deslizamiento rápido, debe desplazar muchas diapositivas con impulso (como lo hace Swiper).
  3. Luego, cuando la rueda deja de girar, se ajusta a un tobogán (como lo hace Swiper).
  4. En ontouchfunción hay una devolución de llamada.

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)

Probado en los navegadores Android 9 y Windows 10.

Mukyuu
fuente
3
uhhh ... deslizo de izquierda a derecha y la rueda gira a la izquierda ... se ve genial
Tschallacka
2
Mientras tanto, muchas gracias por su respuesta, pero no responde a muchos requisitos: 1) según lo indicado por @Tschallacka, gira en reversa. 2) las diapositivas no siguen el deslizamiento, las diapositivas deben mover el deslizamiento mientras se desliza (como lo hace Swiper). 3) Con un deslizamiento rápido, debe desplazar muchas diapositivas con impulso (como lo hace Swiper). 4) Luego, cuando la rueda deja de girar, se ajusta a un tobogán (como lo hace Swiper). 5) Necesito una devolución de llamada en un evento como slideChanged(como lo hace Swiper). Por todas estas razones, pensé que Swiper sería un buen punto de partida ...
Fred K
Célebre. Modifiqué las rotaciones en reversa y agregué un poco de impulso, trataré de ver qué puedo hacer con los Swiperjs. Dime si algo necesita más mejoras.
Mukyuu