Skip to content

Instantly share code, notes, and snippets.

@m3g4p0p
Created September 5, 2017 05:34
Show Gist options
  • Save m3g4p0p/c88752316d8c7745e41dee0b3f8bdf01 to your computer and use it in GitHub Desktop.
Save m3g4p0p/c88752316d8c7745e41dee0b3f8bdf01 to your computer and use it in GitHub Desktop.
An infinite swipe slider
const KEY = {
LEFT: 37,
RIGHT: 39
}
const cycle = (value, max) => (value + max) % max
const toTouchClientX = func => (event) => {
const { changedTouches: [first] } = event
if (first) {
event.preventDefault()
func({ clientX: first.clientX })
}
}
export default class Slider {
constructor (el, {
slideContainer = '.slider-bar',
pagination = '.pagination',
buttonLeft = '.button-left',
buttonRight = '.button-right',
activeClass = 'active'
} = {}) {
this.el = el
this.sliderBar = el.querySelector(slideContainer)
this.pagination = el.querySelector(pagination)
this.buttonLeft = el.querySelector(buttonLeft)
this.buttonRight = el.querySelector(buttonRight)
this.slides = Array.from(this.sliderBar.children)
this.width = el.offsetWidth
this.currentIndex = 0
this.activeClass = activeClass
this.adjustPosition = this.adjustPosition.bind(this)
this.enableTransition = this.enableTransition.bind(this)
this.onKeyDown = this.onKeyDown.bind(this)
this.onDragStart = this.onDragStart.bind(this)
this.onDragEnd = this.onDragEnd.bind(this)
this.onDragMove = this.onDragMove.bind(this)
this.el.addEventListener('keydown', this.onKeyDown)
this.sliderBar.addEventListener('mousedown', this.onDragStart)
this.sliderBar.addEventListener('mouseup', this.onDragEnd)
this.sliderBar.addEventListener('mouseleave', this.onDragEnd)
this.sliderBar.addEventListener('mousemove', this.onDragMove)
this.sliderBar.addEventListener('transitionend', this.adjustPosition)
this.sliderBar.addEventListener('touchstart', toTouchClientX(this.onDragStart))
this.sliderBar.addEventListener('touchend', toTouchClientX(this.onDragEnd))
this.sliderBar.addEventListener('touchcancel', toTouchClientX(this.onDragEnd))
this.sliderBar.addEventListener('touchmove', toTouchClientX(this.onDragMove))
if (this.pagination) {
this.initPagination()
}
if (this.buttonLeft) {
this.buttonLeft.addEventListener('click', this.goLeft.bind(this))
}
if (this.buttonRight) {
this.buttonRight.addEventListener('click', this.goRight.bind(this))
}
this.prepareSlides()
this.adjustTransform()
this.setActive()
}
prepareSlides () {
const { sliderBar, slides } = this
const first = slides[0]
const [last] = slides.slice(-1)
sliderBar.insertBefore(last.cloneNode(true), first)
sliderBar.appendChild(first.cloneNode(true))
}
initPagination () {
const { pagination, slides } = this
const { children } = pagination
const addGoToHandler = (item, index) => {
item.addEventListener('click', () => {
this.goTo(index)
})
}
if (children.length) {
Array.from(children).forEach(addGoToHandler)
} else {
const frag = slides.reduce((carry, _, index) => {
const item = document.createElement('div')
addGoToHandler(item, index)
carry.appendChild(item)
return carry
}, new window.DocumentFragment())
pagination.appendChild(frag)
}
}
enableTransition () {
this.sliderBar.style.transition = null
}
disableTransition () {
this.sliderBar.style.transition = 'none'
}
adjustPosition () {
this.disableTransition()
this.currentIndex = cycle(this.currentIndex, this.slides.length)
this.adjustTransform()
this.setActive()
window.setTimeout(this.enableTransition)
}
adjustTransform (offset = 0) {
const translation = (this.currentIndex + 1) * this.width - offset
this.sliderBar.style.transform = `translateX(-${translation}px)`
}
goLeft () {
this.currentIndex--
this.adjustTransform()
}
goRight () {
this.currentIndex++
this.adjustTransform()
}
goTo (index) {
this.currentIndex = index
this.adjustTransform()
}
setActive () {
const {
slides,
pagination,
currentIndex,
activeClass
} = this
slides.forEach((slide, index) => {
if (index === currentIndex) {
slide.classList.add(activeClass)
if (pagination) {
pagination.children[index].classList.add(activeClass)
}
} else {
slide.classList.remove(activeClass)
if (pagination) {
pagination.children[index].classList.remove(activeClass)
}
}
})
}
onKeyDown ({ keyCode }) {
switch (keyCode) {
case KEY.LEFT:
this.goLeft()
break
case KEY.RIGHT:
this.goRight()
break
default:
// Do nothing
}
}
onDragStart ({ clientX }) {
this.disableTransition()
this.dragOffset = clientX
this.slides[this.currentIndex].classList.add('dragging')
}
onDragEnd ({ clientX }) {
if (this.dragOffset === undefined) {
return
}
const currentOffset = clientX - this.dragOffset
const { width } = this
this.slides[this.currentIndex].classList.remove('dragging')
this.enableTransition()
delete this.dragOffset
if (currentOffset > width / 3) {
this.goLeft()
} else if (-currentOffset > width / 3) {
this.goRight()
} else {
this.adjustTransform()
}
}
onDragMove ({ clientX }) {
if (this.dragOffset === undefined) {
return
}
window.requestAnimationFrame(() => {
this.adjustTransform(clientX - this.dragOffset)
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment