Skip to content

Instantly share code, notes, and snippets.

@YaroslavShapoval
Created June 7, 2020 12:20
Show Gist options
  • Select an option

  • Save YaroslavShapoval/9dae0649bfeb94cd2c7951cdd40c660c to your computer and use it in GitHub Desktop.

Select an option

Save YaroslavShapoval/9dae0649bfeb94cd2c7951cdd40c660c to your computer and use it in GitHub Desktop.
Pure js slider class (work in progress - swipes don't work)
import { throttle } from "lodash";
export default class Slider {
/** @type { HTMLElement } */
#parentNode;
/** @type { HTMLElement } */
#trackNode;
/** @type { HTMLElement[] } */
#slideNodes;
/** @type { HTMLElement } */
#prevButtonNode;
/** @type { HTMLElement } */
#nextButtonNode;
#elementClasses = {
'track': '.slider__track',
'item': '.slider__item',
'prev_button': '.slider__prev-button',
'next_button': '.slider__next-button',
};
#isDisabledClass = "is-disabled";
#isAnimateClass = "is-animate";
#eventThrottlingTime = 100;
#currentSlideIndex = 0;
#currentTrackLeftOffset = 0;
#isOnProgress = true;
#touchThreshold = 100;
#touchPositions = {
'initial': 0
};
#eventReferences = {
'prev_click': undefined,
'next_click': undefined,
'track_mousedown': undefined,
'track_mousemove': undefined,
'track_mouseup': undefined,
'track_touchstart': undefined,
'track_touchmove': undefined,
'track_touchend': undefined,
'track_transitionend': undefined,
'window_resize': undefined,
};
constructor(parentNode, initialSlideIndex = 0, elementClasses = {}) {
this.#elementClasses = { ...this.#elementClasses, ...elementClasses };
this.#parentNode = parentNode;
this.#trackNode = parentNode.querySelector(this.#elementClasses['track']);
this.#slideNodes = [...parentNode.querySelectorAll(this.#elementClasses['item'])];
this.#prevButtonNode = parentNode.querySelector(this.#elementClasses['prev_button']);
this.#nextButtonNode = parentNode.querySelector(this.#elementClasses['next_button']);
this.#setCurrentSlideIndex(initialSlideIndex);
// this.#eventReferences = {};
this.#prevButtonNode.addEventListener(
'click',
this.#eventReferences['prev_click'] = throttle(this.#onPrevButtonClick.bind(this), this.#eventThrottlingTime)
);
this.#nextButtonNode.addEventListener(
'click',
this.#eventReferences['next_click'] = throttle(this.#onNextButtonClick.bind(this), this.#eventThrottlingTime)
);
this.#parentNode.addEventListener(
'mousedown',
this.#eventReferences['track_mousedown'] = this.#mousemoveStart.bind(this)
);
this.#parentNode.addEventListener(
'touchstart',
this.#eventReferences['track_touchstart'] = this.#touchStart.bind(this)
);
this.#parentNode.addEventListener(
'transitionend',
this.#eventReferences['track_transitionend'] = this.#onAnimationFinished.bind(this)
);
window.addEventListener(
'resize',
this.#eventReferences['window_resize'] = throttle(this.#onWindowResize.bind(this), this.#eventThrottlingTime),
false
);
this.#isOnProgress = false;
}
destroy() {
this.#prevButtonNode.removeEventListener('click', this.#eventReferences['prev_click']);
this.#nextButtonNode.removeEventListener('click', this.#eventReferences['next_click']);
this.#trackNode.removeEventListener('mousedown', this.#eventReferences['track_mousedown']);
this.#trackNode.removeEventListener('mousemove', this.#eventReferences['track_mousemove']);
this.#trackNode.removeEventListener('mouseup', this.#eventReferences['track_mouseup']);
this.#trackNode.removeEventListener('touchstart', this.#eventReferences['track_touchstart']);
this.#trackNode.removeEventListener('touchmove', this.#eventReferences['track_touchmove']);
this.#trackNode.removeEventListener('touchend', this.#eventReferences['track_touchend']);
this.#trackNode.removeEventListener('transitionend', this.#eventReferences['track_transitionend']);
window.removeEventListener('resize', this.#eventReferences['window_resize']);
}
getCurrentSlideIndex() {
return this.#currentSlideIndex;
}
#setCurrentSlideIndex(slideIndex) {
this.#currentSlideIndex = Math.min(Math.max(slideIndex, 0), this.#slideNodes.length - 1);
if (!this.#isNextAvailable()) {
this.#goToLastSlide();
} else {
this.#updateSliderPosition();
}
}
#onAnimationFinished() {
this.#trackNode.classList.remove(this.#isAnimateClass);
this.#isOnProgress = false;
this.#setPrevButtonDisabling();
this.#setNextButtonDisabling();
}
#updateSliderPosition() {
if (!this.#isOnProgress) {
this.#trackNode.classList.add(this.#isAnimateClass);
const currentSlidePos = this.#slideNodes[this.#currentSlideIndex].getBoundingClientRect().left;
const firstSlidePos = this.#slideNodes[0].getBoundingClientRect().left;
this.#currentTrackLeftOffset = currentSlidePos - firstSlidePos;
this.#setSliderPosition(this.#currentTrackLeftOffset);
// Turn off the arrow buttons when animation is running
this.#setPrevButtonDisabling(true);
this.#setNextButtonDisabling(true);
this.#isOnProgress = true;
}
}
#setSliderPosition(newSliderPosition) {
this.#trackNode.style.left = `-${newSliderPosition}px`;
}
#goToPrevSlide() {
if (this.#isPrevAvailable()) {
this.#setCurrentSlideIndex(this.#currentSlideIndex-1);
}
}
#goToNextSlide() {
if (this.#isNextAvailable()) {
this.#setCurrentSlideIndex(this.#currentSlideIndex+1);
}
}
#isPrevAvailable() {
return this.#currentSlideIndex > 0;
}
#isNextAvailable() {
const lastSlideRightPos = this.#slideNodes[this.#slideNodes.length - 1].getBoundingClientRect().right;
const parentNodeRightPos = this.#parentNode.getBoundingClientRect().right;
return lastSlideRightPos >= parentNodeRightPos;
}
#goToLastSlide() {
const lastSlideRightPos = this.#slideNodes[this.#slideNodes.length - 1].getBoundingClientRect().right;
const parentNodeWidth = this.#parentNode.getBoundingClientRect().width;
// Need to find the index of the slide,
// the distance from which to the last slide is less than the width of the entire slider
let lastPossibleSlideId = this.#slideNodes.length - 1;
for (let i = this.#slideNodes.length - 2; i >= 0; i--) {
const slideLeftPos = this.#slideNodes[i].getBoundingClientRect().left;
if (lastSlideRightPos - slideLeftPos > parentNodeWidth) {
break;
}
lastPossibleSlideId = i;
}
this.#setCurrentSlideIndex(lastPossibleSlideId);
}
#onPrevButtonClick() {
if (!this.#isOnProgress) {
this.#goToPrevSlide();
}
}
#onNextButtonClick() {
if (!this.#isOnProgress) {
this.#goToNextSlide();
}
}
#onWindowResize() {
if (!this.#isNextAvailable()) {
this.#goToLastSlide();
} else {
this.#updateSliderPosition();
}
}
/** @param { MouseEvent } e */
#mousemoveStart(e) {
if (e.button !== 0) {
return;
}
console.log(e);
console.log(e.button);
this.#touchPositions['initial'] = e.clientX;
this.#parentNode.addEventListener(
'mousemove',
this.#eventReferences['track_mousemove'] = this.#mousemoveAction.bind(this)
);
this.#parentNode.addEventListener(
'mouseup',
this.#eventReferences['track_mouseup'] = this.#mousemoveEnd.bind(this)
);
}
/** @param { MouseEvent } e */
#mousemoveAction(e) {
const diff = this.#touchPositions['initial'] - e.clientX;
this.#setSliderPosition(this.#currentTrackLeftOffset + diff);
}
/** @param { MouseEvent } e */
#mousemoveEnd(e) {
this.#parentNode.removeEventListener('mousemove', this.#eventReferences['track_mousemove']);
this.#parentNode.removeEventListener('mouseup', this.#eventReferences['track_mouseup']);
console.log('mousemoveEnd');
// console.log(e);
// this.#setToClosestSlide();
// this.#updateSliderPosition();
// threshold = 100;
}
/** @param { TouchEvent } e */
#touchStart(e) {
console.log(e);
console.log(e.touches);
// posX1 = e.touches[0].clientX;
this.#touchPositions['initial'] = e.touches[0].clientX;
this.#parentNode.addEventListener(
'touchmove',
this.#eventReferences['track_touchmove'] = this.#touchAction.bind(this)
);
this.#parentNode.addEventListener(
'touchend',
this.#eventReferences['track_touchend'] = this.#touchEnd.bind(this)
);
}
/** @param { TouchEvent } e */
#touchAction(e) {
console.log(e);
console.log(e.touches);
const diff = this.#touchPositions['initial'] - e.touches[0].clientX;
this.#setSliderPosition(this.#currentTrackLeftOffset + diff);
// posX2 = posX1 - e.touches[0].clientX;
// posX1 = e.touches[0].clientX;
// items.style.left = (items.offsetLeft - posX2) + "px";
}
/** @param { TouchEvent } e */
#touchEnd(e) {
this.#parentNode.removeEventListener('touchmove', this.#eventReferences['track_touchmove']);
this.#parentNode.removeEventListener('touchend', this.#eventReferences['track_touchend']);
console.log(e);
}
#setToClosestSlide() {
const parentNodeLeftPos = this.#parentNode.getBoundingClientRect().left;
let closestSlideDistance = this.#parentNode.getBoundingClientRect().width;
let closestSlideIndex = 0;
for (let i = 0; i < this.#slideNodes.length; i++) {
const slideLeftPos = this.#slideNodes[i].getBoundingClientRect().left;
if (Math.abs(parentNodeLeftPos - slideLeftPos) < closestSlideDistance) {
closestSlideDistance = slideLeftPos;
closestSlideIndex = i;
}
}
this.#setCurrentSlideIndex(closestSlideIndex);
}
#setPrevButtonDisabling(status = undefined) {
if (status === undefined) {
if (!this.#isPrevAvailable()) {
this.#prevButtonNode.classList.add(this.#isDisabledClass);
} else if (this.#prevButtonNode.classList.contains(this.#isDisabledClass)) {
this.#prevButtonNode.classList.remove(this.#isDisabledClass);
}
} else {
if (status === false) {
this.#prevButtonNode.classList.remove(this.#isDisabledClass);
}
if (status === true) {
this.#prevButtonNode.classList.add(this.#isDisabledClass);
}
}
}
#setNextButtonDisabling(status = undefined) {
if (status === undefined) {
if (!this.#isNextAvailable()) {
this.#nextButtonNode.classList.add(this.#isDisabledClass);
} else if (this.#nextButtonNode.classList.contains(this.#isDisabledClass)) {
this.#nextButtonNode.classList.remove(this.#isDisabledClass);
}
} else {
if (status === false) {
this.#nextButtonNode.classList.remove(this.#isDisabledClass);
}
if (status === true) {
this.#nextButtonNode.classList.add(this.#isDisabledClass);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment