Penggeser sentuh JavaScript

Ketika saya mulai belajar JavaScript, saya benar-benar ingin memahami cara kerja slider dan dibuat yang dapat diputar dengan swipe atau mouse, tetapi saya tidak menemukan bahan dengan penjelasan yang baik tentang apa yang saya butuhkan. Setelah beberapa waktu, saya berhasil melakukan hal serupa. Dan sekarang saya ingin menulis artikel tentang ini, sehingga orang lain yang ingin memahami cara kerjanya dan membuat acara sentuh untuk slider (dan tidak hanya) membuatnya lebih mudah untuk mengetahuinya. Saya akan mencoba untuk mengatur agar dan mendukung penjelasan dengan contoh ilustratif.


Saya tidak menganggap diri saya seorang spesialis hebat dalam JavaScript, selalu ada sesuatu untuk dipelajari, jadi jika Anda tahu cara menulis beberapa fragmen kode lebih baik / lebih mudah / lebih efisien, pastikan untuk menulis di komentar.


Kandungan:


  1. Fungsi apa yang akan kita lakukan?
  2. Menulis HTML dan CSS
  3. Bagaimana cara kerjanya?
  4. Menulis javascript
  5. Total
  6. Kode versi lengkap

Anda bisa langsung melihat contohnya .



1. Fungsi apa yang akan kita lakukan?


Mari kita menulis dengan 0 slider sederhana yang akan memiliki fungsi-fungsi berikut:


  • kami menerapkan sentuhan dan seret untuk membalik slide;
  • beralih slide menggunakan tombol panah;
  • kunci tombol panah โ† dan โ†’ masing-masing pada slide pertama dan terakhir.

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



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 .


    • posX2 โ€” posX1 event.clientX. swipeAction, , - , " " = 161, 160 โ€” 161 = -1, -1px. :


    , , X, posX2 , . 0.5 10, ( , ฬ , , ).


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


    • posFinal = posInit โ€“ posX1 ( , 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 .


  • Slider dapat "meraih" ketika slide belum selesai bergerak.

Untuk memperbaiki perilaku ini, Anda perlu mendeklarasikan variabel allowSwipedan menyesuaikan larangan geser di atasnya.


Saya tidak akan menjelaskannya secara rinci. Lay out saja kode di bawah ini .
Dan dalam contoh, semua kondisi ini sudah dibuat. Juga, untuk kejelasan, kursor pada slider berubah.



Kode lengkap (geser, seret, panah)
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();
  });


Kode paling lengkap
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