Created
June 7, 2020 12:20
-
-
Save YaroslavShapoval/9dae0649bfeb94cd2c7951cdd40c660c to your computer and use it in GitHub Desktop.
Pure js slider class (work in progress - swipes don't work)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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