Curseur tactile JavaScript

Quand j'ai commencé à apprendre JavaScript, je voulais vraiment comprendre comment fonctionnent les curseurs qui peuvent être retournés avec un glissement ou une souris, mais je n'ai pas trouvé de matériel avec une bonne explication de ce dont j'avais besoin. Après un certain temps, j'ai réussi à faire quelque chose de similaire. Et maintenant, je veux écrire un article à ce sujet, afin que d'autres personnes qui veulent comprendre comment tout cela fonctionne et créer des événements tactiles pour le curseur (et pas seulement) facilitent la compréhension. Je vais essayer de mettre en ordre et de soutenir les explications avec des exemples illustratifs.


Je ne me considère pas comme un grand spécialiste du JavaScript, il y a toujours quelque chose à apprendre, donc si vous savez comment écrire des fragments de code mieux / plus facilement / plus efficacement, assurez-vous d'écrire dans les commentaires.


Contenu:


  1. Quelle fonctionnalité ferons-nous?
  2. Écriture HTML et CSS
  3. Comment ça va marcher?
  4. Écriture javascript
  5. Total
  6. Code de version complète

Vous pouvez immédiatement voir un exemple .



1. Quelles fonctionnalités ferons-nous?


Écrivons avec 0 un simple curseur qui aura les fonctions suivantes:


  • nous implémentons le toucher et le glissement en retournant les diapositives;
  • changer de diapos à l'aide des touches fléchées;
  • verrouillez les touches fléchées ← et → sur la première et la dernière diapositive, respectivement.

.
- , - , -, , , .. . . - , .



2. HTML CSS


HTML CSS?


(1 ) 200200 . - .
:



.
. , .slider-track display: flex. , , flex-shrink: 0, .



. track , .slider-list. , overflow: hidden. , .



-. ́ .slider-arrows . .slider-list (overflow: hidden) , :


  • .slider-list padding-bottom ;
  • .slider-list , .slider ( ) .

́ CSS.


HTML :


<div class="slider">
  <div class="slider-list">
    <div class="slider-track">
      <div class="slide">1</div>
      <div class="slide">2</div>
      <div class="slide">3</div>
      <div class="slide">4</div>
      <div class="slide">5</div>
    </div>
  </div>
  <div class="slider-arrows">
    <button type="button" class="prev">&larr;</button>
    <button type="button" class="next">&rarr;</button>
  </div>
</div>

&larr; &rarr; HTML-, , .


CSS :


.slider {
  position: relative;
  width: 200px;
  height: 200px;
  margin: 50px auto 0;
  /*           */
  user-select: none;
  /*    ,        X */
  touch-action: pan-y;
}

/*  -    ,
    pointer-events: none,
     */

.slider img {
  poiner-events: none;
}

.slider-list {
  width: 200px;
  height: 200px;
  overflow: hidden;
}

.slider-list.grab {
  cursor: grab;
}

.slider-list.grabbing{
  cursor: grabbing;
}

.slider-track {
  display: flex;
}

.slide {
  width: 200px;
  height: 200px;
  /*     */
  flex-shrink: 0;
  /*       */
  font-size: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #000;
}

.slider-arrows {
  margin-top: 15px;
  text-align: center;
}

.next,
.prev {
  background: none;
  border: none;
  margin: 0 10px;
  font-size: 30px;
  cursor: pointer;
}

.next.disabled,
.prev.disabled {
  opacity: .25;
  pointer-events: none;
}

:



overflow: hidden:



, .



3. ?


transform: translate3d(x, y, z), . transform: translateX(x) will-change: transform. style match().
slideIndex. , (200px). — transform: translate3d(x, y, z) x : * . -.


, .


. touchstart ( mousedown), — touchmove (mousemove), — touchend (mouseup).


event.clientX ( ) :


  1. 3 — swipeStart, swipeAction swipeEnd.


  2. () X ( posX1 posInit, posX1 , posInit ).


  3. , posX1, posX2, style.transform, . posX1 .
    :


    • posX1 posInit — , ( event.clientX). swipeStart, , 320px, posInit posX1 160. swipeAction posX1 .


    • posX2posX1 event.clientX. swipeAction, , - , " " = 161, 160 — 161 = -1, -1px. :


    , , X, posX2 , . 0.5 10, ( , ́ , , ).


  4. , (posFinal) "" (posThreshold), .
    . . , :


    • posFinal = posInitposX1 ( , swipeAction, , 200px, , posFinal 100).


    • posThreshold = (200px) * 0,3 = 60. posFinal, , posFinal > 60, , . :
      if (posFinal >= posThreshold) nextSlide()  prevSlide(); else currentSlide();

    else - , .


    posThreshold , , prev() next() . posInit posX1. 320px, ́ , 0px-320px ( 0, 320). , , posInit 160. , ( ), posX1 160 0. , posX1 c 160 320. , , posInit 160, posX1 100, posThreshold . :


    if (posInit > posX1) nextSlide(); else if (posInit < posX1) prevSlide();

    , , click. posInit === posX1. , .



. JavaScript.



4. JavaScript


, , "" , , . , . , .


:


  • .slider-track transfrom: translate3d(Xpx, 0px, 0px);
  • transfrom: translate3d(0px, 0px, 0px) style, ;
  • style.transform match() ;
  • swipeStart .slider-list touchstart mousedown;
  • swipeStart document swipeAction swipeEnd touchmove (mousemove) touchend (mouseup) , ;
  • posThreshold, slideIndex ;
  • posThreshold , ;
  • -, .

.


. HTML- querySelector DOM, querySelector. , . .


let slider = document.querySelector('.slider'),
  sliderList = slider.querySelector('.slider-list'),
  sliderTrack = slider.querySelector('.slider-track'),
  slides = slider.querySelectorAll('.slide'),
  arrows = slider.querySelector('.slider-arrows'),
  prev = arrows.children[0],
  next = arrows.children[1],
  slideWidth = slides[0].offsetWidth,
  slideIndex = 0,
  posInit = 0,
  posX1 = 0,
  posX2 = 0,
  posFinal = 0,
  posThreshold = slideWidth * .35,
  trfRegExp = /[-0-9.]+(?=px)/,
  slide = function() {
    sliderTrack.style.transition = 'transform .5s';
    sliderTrack.style.transform = `translate3d(-${slideIndex * slideWidth}px, 0px, 0px)`;

    //   prev    
    //     
    //   next    
    //     
    prev.classList.toggle('disabled', slideIndex === 0);
    next.classList.toggle('disabled', slideIndex === --slides.length);
  } ...

trfRegExp — , transform .slider-track.


event.clientX, touches — event.touches[0].clientX. event .. , getEvent. event.type touch touch event. .


getEvent = function() {
  return event.type.search('touch') !== -1 ? event.touches[0] : event;
  // p.s. event -     
},
//  es6
getEvent = () => event.type.search('touch') !== -1 ? event.touches[0] : event,

swipeStart = function() {
  let evt = getEvent();

  //       
  posInit = posX1 = evt.clientX;

  //   ,  track     
  // ..      slide()
  sliderTrack.style.transition = '';

  //        
  document.addEventListener('touchmove', swipeAction);
  document.addEventListener('touchend', swipeEnd);
  document.addEventListener('mousemove', swipeAction);
  document.addEventListener('mouseup', swipeEnd);
},
swipeAction = function() {
  let evt = getEvent(),
    //          transform
    style = sliderTrack.style.transform,
    //           
    transform = +style.match(trfRegExp)[0];

  posX2 = posX1 - evt.clientX;
  posX1 = evt.clientX;

  sliderTrack.style.transform = `translate3d(${transform - posX2}px, 0px, 0px)`;
  //       .replace():
  // sliderTrack.style.transform = style.replace(trfRegExp, match => match - posX2);
  //          
} ...

swipeAction:
transform. :
translate3d(0px, 0px, 0px) "px". transform. .
: [-0-9.]+(?=px), :


  • [-0-9.] — , "" " 0 9" "";
  • + — , 1 , , : 5, 101.10, -19, -12.5 ..;
  • (?=px) — , , "px".

.slider-track , sliderTrack.style.transform = 'translate3d(0px, 0px, 0px)', swipeAction . :



( ) — swipeAction , .slider-track . swipeEnd , , :


swipeEnd = function() {
  //   
  posFinal = posInit - posX1;

  document.removeEventListener('touchmove', swipeAction);
  document.removeEventListener('mousemove', swipeAction);
  document.removeEventListener('touchend', swipeEnd);
  document.removeEventListener('mouseup', swipeEnd);

  //         
  if (Math.abs(posFinal) > posThreshold) {
    //    ,     
    if (posInit < posX1) {
      slideIndex--;
    //    ,     
    } else if (posInit > posX1) {
      slideIndex++;
    }
  }

  //   ,     
  if (posInit !== posX1) {
    slide();
  }

};

touch drag , . : posFinal posThreshold, , , . Math.abs() .



. , .


arrows.addEventListener('click', function() {
  let target = event.target;

  if (target === next) {
    slideIndex++;
  } else if (target === prev) {
    slideIndex--;
  } else {
    return;
  }

  slide();
});

sliderTrack.style.transform = 'translate3d(0px, 0px, 0px)';

slider.addEventListener('touchstart', swipeStart);
slider.addEventListener('mousedown', swipeStart);

. .




. tocuh , drag -. , , :


  • , . , , :


Y, , ( ).


  • , :


, , .


  • , :


, . swipeAction, swipeStart 1 .


  • Le curseur peut être "saisi" lorsque la diapositive n'a pas encore fini de bouger.

Pour corriger ce problème, vous devez déclarer une variable allowSwipeet ajuster l'interdiction de glisser dessus.


Je ne le décrirai pas en détail. Présentez simplement ce code ci-dessous .
Et dans l' exemple, toutes ces conditions seront déjà réalisées. De plus, pour plus de clarté, le curseur sur le curseur change.



Code complet (glisser, glisser, flèches)
let slider = document.querySelector('.slider'),
  sliderList = slider.querySelector('.slider-list'),
  sliderTrack = slider.querySelector('.slider-track'),
  slides = slider.querySelectorAll('.slide'),
  arrows = slider.querySelector('.slider-arrows'),
  prev = arrows.children[0],
  next = arrows.children[1],
  slideWidth = slides[0].offsetWidth,
  slideIndex = 0,
  posInit = 0,
  posX1 = 0,
  posX2 = 0,
  posFinal = 0,
  posThreshold = slides[0].offsetWidth * 0.35,
  trfRegExp = /([-0-9.]+(?=px))/,
  getEvent = function() {
    return (event.type.search('touch') !== -1) ? event.touches[0] : event;
  },
  slide = function() {
    if (transition) {
      sliderTrack.style.transition = 'transform .5s';
    }
    sliderTrack.style.transform = `translate3d(-${slideIndex * slideWidth}px, 0px, 0px)`;

    prev.classList.toggle('disabled', slideIndex === 0);
    next.classList.toggle('disabled', slideIndex === --slides.length);
  },
  swipeStart = function() {
    let evt = getEvent();

    posInit = posX1 = evt.clientX;

    sliderTrack.style.transition = '';

    document.addEventListener('touchmove', swipeAction);
    document.addEventListener('mousemove', swipeAction);
    document.addEventListener('touchend', swipeEnd);
    document.addEventListener('mouseup', swipeEnd);
  },
  swipeAction = function() {

    let evt = getEvent(),
      style = sliderTrack.style.transform,
      transform = +style.match(trfRegExp)[0];

    posX2 = posX1 - evt.clientX;
    posX1 = evt.clientX;

    sliderTrack.style.transform = `translate3d(${transform - posX2}px, 0px, 0px)`;
  },
  swipeEnd = function() {
    posFinal = posInit - posX1;

    document.removeEventListener('touchmove', swipeAction);
    document.removeEventListener('mousemove', swipeAction);
    document.removeEventListener('touchend', swipeEnd);
    document.removeEventListener('mouseup', swipeEnd);

    if (Math.abs(posFinal) > posThreshold) {
      if (posInit < posX1) {
        slideIndex--;
      } else if (posInit > posX1) {
        slideIndex++;
      }
    }

    if (posInit !== posX1) {
      slide();
    }
  };

  sliderTrack.style.transform = 'translate3d(0px, 0px, 0px)';

  slider.addEventListener('touchstart', swipeStart);
  slider.addEventListener('mousedown', swipeStart);

  arrows.addEventListener('click', function() {
    let target = event.target;

    if (target === next) {
      slideIndex++;
    } else if (target === prev) {
      slideIndex--;
    } else {
      return;
    }

    slide();
  });


Le code le plus complet
let slider = document.querySelector('.slider'),
  sliderList = slider.querySelector('.slider-list'),
  sliderTrack = slider.querySelector('.slider-track'),
  slides = slider.querySelectorAll('.slide'),
  arrows = slider.querySelector('.slider-arrows'),
  prev = arrows.children[0],
  next = arrows.children[1],
  slideWidth = slides[0].offsetWidth,
  slideIndex = 0,
  posInit = 0,
  posX1 = 0,
  posX2 = 0,
  posY1 = 0,
  posY2 = 0,
  posFinal = 0,
  isSwipe = false,
  isScroll = false,
  allowSwipe = true,
  transition = true,
  nextTrf = 0,
  prevTrf = 0,
  lastTrf = --slides.length * slideWidth,
  posThreshold = slides[0].offsetWidth * 0.35,
  trfRegExp = /([-0-9.]+(?=px))/,
  getEvent = function() {
    return (event.type.search('touch') !== -1) ? event.touches[0] : event;
  },
  slide = function() {
    if (transition) {
      sliderTrack.style.transition = 'transform .5s';
    }
    sliderTrack.style.transform = `translate3d(-${slideIndex * slideWidth}px, 0px, 0px)`;

    prev.classList.toggle('disabled', slideIndex === 0);
    next.classList.toggle('disabled', slideIndex === --slides.length);
  },
  swipeStart = function() {
    let evt = getEvent();

    if (allowSwipe) {

      transition = true;

      nextTrf = (slideIndex + 1) * -slideWidth;
      prevTrf = (slideIndex - 1) * -slideWidth;

      posInit = posX1 = evt.clientX;
      posY1 = evt.clientY;

      sliderTrack.style.transition = '';

      document.addEventListener('touchmove', swipeAction);
      document.addEventListener('mousemove', swipeAction);
      document.addEventListener('touchend', swipeEnd);
      document.addEventListener('mouseup', swipeEnd);

      sliderList.classList.remove('grab');
      sliderList.classList.add('grabbing');
    }
  },
  swipeAction = function() {

    let evt = getEvent(),
      style = sliderTrack.style.transform,
      transform = +style.match(trfRegExp)[0];

    posX2 = posX1 - evt.clientX;
    posX1 = evt.clientX;

    posY2 = posY1 - evt.clientY;
    posY1 = evt.clientY;

    //     
    if (!isSwipe && !isScroll) {
      let posY = Math.abs(posY2);
      if (posY > 7 || posX2 === 0) {
        isScroll = true;
        allowSwipe = false;
      } else if (posY < 7) {
        isSwipe = true;
      }
    }

    if (isSwipe) {
      //      
      if (slideIndex === 0) {
        if (posInit < posX1) {
          setTransform(transform, 0);
          return;
        } else {
          allowSwipe = true;
        }
      }

      //      
      if (slideIndex === --slides.length) {
        if (posInit > posX1) {
          setTransform(transform, lastTrf);
          return;
        } else {
          allowSwipe = true;
        }
      }

      //     
      if (posInit > posX1 && transform < nextTrf || posInit < posX1 && transform > prevTrf) {
        reachEdge();
        return;
      }

      //  
      sliderTrack.style.transform = `translate3d(${transform - posX2}px, 0px, 0px)`;
    }

  },
  swipeEnd = function() {
    posFinal = posInit - posX1;

    isScroll = false;
    isSwipe = false;

    document.removeEventListener('touchmove', swipeAction);
    document.removeEventListener('mousemove', swipeAction);
    document.removeEventListener('touchend', swipeEnd);
    document.removeEventListener('mouseup', swipeEnd);

    sliderList.classList.add('grab');
    sliderList.classList.remove('grabbing');

    if (allowSwipe) {
      if (Math.abs(posFinal) > posThreshold) {
        if (posInit < posX1) {
          slideIndex--;
        } else if (posInit > posX1) {
          slideIndex++;
        }
      }

      if (posInit !== posX1) {
        allowSwipe = false;
        slide();
      } else {
        allowSwipe = true;
      }

    } else {
      allowSwipe = true;
    }

  },
  setTransform = function(transform, comapreTransform) {
    if (transform >= comapreTransform) {
      if (transform > comapreTransform) {
        sliderTrack.style.transform = `translate3d(${comapreTransform}px, 0px, 0px)`;
      }
    }
    allowSwipe = false;
  },
  reachEdge = function() {
    transition = false;
    swipeEnd();
    allowSwipe = true;
  };

sliderTrack.style.transform = 'translate3d(0px, 0px, 0px)';
sliderList.classList.add('grab');

sliderTrack.addEventListener('transitionend', () => allowSwipe = true);
slider.addEventListener('touchstart', swipeStart);
slider.addEventListener('mousedown', swipeStart);

arrows.addEventListener('click', function() {
  let target = event.target;

  if (target.classList.contains('next')) {
    slideIndex++;
  } else if (target.classList.contains('prev')) {
    slideIndex--;
  } else {
    return;
  }

  slide();
});

codepen.io


All Articles