Effet personnalisé qui simule une roue 3D avec Swiper 5

9

J'ai besoin de construire un carrousel avec 12 éléments qui simulent une roue 3D tournant à l'infini. Pour être clair, je dois créer précisément cet effet:

https://codepen.io/SitePoint/pen/yXWXaw (trouvé ici )

mais avec ces fonctionnalités supplémentaires (sur ordinateur et mobile en particulier):

  1. les diapositives doivent suivre étape par étape le balayage, c'est-à-dire que les diapositives doivent se déplacer pendant le balayage (comme le fait Swiper).
  2. Avec un balayage rapide, il devrait faire défiler de nombreuses diapositives avec élan (comme Swiper le fait avec freeScroll).
  3. Ensuite, lorsque la roue arrête de tourner, elle s'enclenche sur la glissière avant (comme Swiper le fait avec freeModeStickyet centeredSlides) que c'est celle choisie par l'utilisateur.
  4. J'ai besoin d'un rappel chaque fois qu'un changement de diapositive (comme un événement slideChanged) (comme le fait Swiper).

Pour toutes ces raisons, je pensais que Swiper 5.3.0 serait un bon point de départ.

J'ai essayé différentes solutions de contournement, la meilleure est avec cette configuration, mais loop: truec'est une solution de contournement horrible et provoque des problèmes (consultez les commentaires):

  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,

  });

Certainement pas la bonne façon.

Je pense que la bonne façon de développer une coutume Chipeur effect(comme intégré cubeEffect, coverflowEffect...) qui simule la roue, sans utiliser loop:trueque les questions de causes. Par exemple, ici, un gars crée son propre effet personnalisé qu'il définit ensuite dans l' effectattribut de Swiper: https://codepen.io/paralleluniv3rse/pen/yGQjMv

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

Comment développer un effet personnalisé comme la roue 3D dont j'ai besoin?

Fred K
la source
Je me demande si travailler avec cet effet comme point de départ serait le meilleur moyen bénéfique: swiperjs.com/demos/240-effect-coverflow.html . Je suis curieux de déplacer les "diapositives passées" sur un axe x négatif, mais de revenir sur le côté droit du curseur pour les reproduire dans le spectacle ...
Phlume
1
@Phlume J'ai déjà essayé de travailler avec coverflowEffectcomme point de départ et de "pirater" ses paramètres, mais ce n'est qu'une solution de contournement, et je ne peux pas obtenir l'effet du premier codepen. Les diapositives ne seront tout simplement pas placées sur une surface circulaire.
Fred K
Désolé, pouvez-vous préciser ce que vous aimeriez faire? Voulez-vous que le carrousel puisse être filé sans cliquer sur les boutons précédent / suivant?
Mukyuu
1
@Mukyuu Message de question mis à jour avec détails
Fred K

Réponses:

2

Je pense que c'est ce que vous voulez: https://codepen.io/mukyuu/pen/GRgPYqG .

Il a presque rempli vos conditions, sauf qu'il n'utilise pas Swiper 5 et Snap.

  1. Il tourne avec la direction du balayage.
  2. Avec un balayage rapide, il devrait faire défiler de nombreuses diapositives avec élan (comme le fait Swiper).
  3. Puis, lorsque la roue cesse de tourner, elle s'enclenche sur une glissière (comme le fait Swiper).
  4. En ontouchfonction il y a un rappel.

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)

Testé dans les navigateurs Android 9 et Windows 10.

Mukyuu
la source
3
euhhh .. Je glisse de gauche à droite et la roue tourne à gauche .... ça a l'air cool
quand
2
En attendant, merci beaucoup pour votre réponse, mais elle ne répond pas à beaucoup d'exigences: 1) comme indiqué par @Tschallacka, elle tourne en sens inverse. 2) les diapositives ne suivent pas le balayage, les diapositives doivent déplacer le balayage pendant le balayage (comme le fait Swiper). 3) Avec un balayage rapide, il devrait faire défiler de nombreuses diapositives avec élan (comme le fait Swiper). 4) Puis, lorsque la roue cesse de tourner, elle s'enclenche sur une glissière (comme le fait Swiper). 5) J'ai besoin d'un rappel sur un événement comme slideChanged(comme le fait Swiper). Pour toutes ces raisons, je pensais que Swiper serait un bon point de départ ...
Fred K
C'est noté. J'ai modifié les rotations en sens inverse et ajouté un peu d'élan, je vais essayer de voir ce que je pourrais proposer avec les Swiperjs. Dites-moi si quelque chose doit être amélioré.
Mukyuu