Last active
March 20, 2018 15:21
-
-
Save jorgeortega/56d8159ec9b0afaebcd0bb05403e8832 to your computer and use it in GitHub Desktop.
Utility class to handle events of a scroller wheel
This file contains 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/throttle' | |
export class Wheel { | |
constructor ({ id, wheelEndCallback }) { | |
this.initialPointerY = -1 | |
this.pointerY = 0 | |
this.currentItemIndex = -1 | |
this.elementHeight = -1 | |
this.childrenHeight = -1 | |
this.isDragging = false | |
this.id = id | |
this.wheelEndCallback = wheelEndCallback | |
this.theta = -1 | |
this.rotation = -1 | |
} | |
// Gets the initial element position | |
// at touchstart or mousedown | |
start (e) { | |
const event = e || window.event | |
this.isDragging = true | |
if (event) { | |
// touch event | |
if (event.type === 'touchstart' && event.changedTouches && event.changedTouches.length) { | |
this.initialPointerY = event.changedTouches[0].clientY | |
// mouse event | |
} else if (event.type === 'mousedown' && event.y) { | |
this.initialPointerY = event.y | |
} | |
} | |
} | |
// Touchmove or mousemove event | |
// Calculates the position of the pointer as it moves | |
move (e) { | |
const event = e || window.event | |
if (event) { | |
e.preventDefault() | |
e.stopPropagation() | |
if (this.isDragging) { | |
this.handleMoveEvent(event) | |
} else { | |
if (event.type === 'wheel') { | |
this.handleMouseWheelEvent(event.deltaY) | |
} | |
} | |
} | |
return false | |
} | |
handleMoveEvent (event) { | |
if (event.type === 'touchmove' && event.changedTouches && event.changedTouches.length) { | |
this.pointerY = event.changedTouches[0].clientY | |
} else if (event.type === 'mousemove') { | |
this.pointerY = event.clientY | |
} | |
const pathLength = this.initialPointerY - this.pointerY | |
let wheelDirection = 0 | |
// if movement is small | |
if (Math.abs(pathLength) < 10) { | |
wheelDirection = 0 | |
} else { | |
// checks if it should spin up or down | |
wheelDirection = this.initialPointerY - this.pointerY > 0 ? -1 : 1 | |
} | |
this.moveCarouselSteps(wheelDirection) | |
this.initialPointerY = this.pointerY | |
} | |
handleMouseWheelEvent (deltaY) { | |
if (deltaY > 0) { | |
this.moveCarouselSteps(1) | |
} else { | |
this.moveCarouselSteps(-1) | |
} | |
} | |
moveCarouselSteps (n) { | |
if (Math.abs(n) > this.childrenLength - 1) { | |
throw new Error('Number of steps should be less than the number of wheel items') | |
} | |
const lastItemIndex = this.childrenLength - 1 | |
if (n > 0) { | |
// if it goes before last item | |
if (this.currentItemIndex + n <= lastItemIndex) { | |
this.currentItemIndex += n | |
// if it goes beyond the last item, it should jump to the beginning | |
// and continue counting | |
// number of steps n, minus the last one | |
} else if (this.currentItemIndex + n > lastItemIndex) { | |
this.currentItemIndex = n - 1 | |
} | |
} else { | |
// n is negative in this case | |
// you still have to add + | |
// if it's still greater or equal zero | |
if (this.currentItemIndex + n >= 0) { | |
this.currentItemIndex = this.currentItemIndex + n | |
// if it's less than zero, it should jump to last position and | |
// and keep counting backwards | |
// n plus the first one | |
} else if (this.currentItemIndex + n < 0) { | |
this.currentItemIndex = lastItemIndex + (n + 1) | |
} | |
} | |
// 1 -> forward, -1 -> backwards | |
this.rotation += this.theta * n * -1 | |
this.rotateCarousel() | |
// callback to send the index to the parent component | |
this.wheelEndCallback({ | |
currentItemIndex: this.currentItemIndex, | |
id: this.id | |
}) | |
} | |
setWheel (index) { | |
this.currentItemIndex = index | |
this.rotation = this.theta * index * -1 | |
this.rotateCarousel() | |
} | |
// touchend or mouseup event | |
end () { | |
if (this.isDragging) { | |
// end dragging | |
this.isDragging = false | |
// make last position the next initial point | |
this.initialPointerY = this.pointerY | |
} | |
} | |
selectItem (wheelIndex) { | |
this.removeClass('selected') | |
this.element.children[wheelIndex].classList.add('selected') | |
} | |
// remove className from all elements with that class | |
removeClass (className) { | |
const matchedElements = this.element.getElementsByClassName(className) | |
for (const match of matchedElements) { | |
match.classList.remove(className) | |
} | |
} | |
// calculates radius and angle of each item in element to make it a 3D carousel | |
create3DCarousel () { | |
let panel, angle, i | |
this.theta = 360 / this.childrenLength | |
// do some trig to figure out how big the carousel is in 3D space | |
this.radius = Math.round((this.elementHeight / 2) / Math.tan(Math.PI / this.childrenLength)) | |
for (i = 0; i < this.childrenLength; i++) { | |
panel = this.element.children[i] | |
angle = this.theta * i | |
// rotate panel, then push it out in 3D space | |
panel.style['transform'] = 'rotateX(' + angle + 'deg) translateZ(' + this.radius + 'px)' | |
} | |
// adjust rotation so panels are always flat | |
this.rotation = Math.round(this.rotation / this.theta) * this.theta | |
this.rotateCarousel() | |
} | |
// rotates the whole element | |
rotateCarousel () { | |
// push the carousel back in 3D space and rotate it | |
this.element.style['transform'] = 'translateZ(-' + this.radius + 'px) ' + 'rotateX(' + this.rotation + 'deg)' | |
if (this.currentItemIndex > 0) { | |
this.selectItem(this.currentItemIndex) | |
} | |
} | |
// get the element and register all the events necessary for dragging | |
register (element) { | |
if (element) { | |
this.element = element | |
this.childrenLength = this.element.children.length | |
this.childrenHeight = this.element.firstElementChild.offsetHeight | |
this.elementHeight = this.element.offsetHeight | |
this.create3DCarousel() | |
element.addEventListener('mousedown', this.start.bind(this)) | |
element.addEventListener('touchstart', this.start.bind(this)) | |
element.addEventListener('mousemove', throttle(this.move.bind(this), 100, { leading: true })) | |
element.addEventListener('touchmove', throttle(this.move.bind(this), 100, { leading: true })) | |
element.addEventListener('wheel', this.move.bind(this)) | |
element.addEventListener('mouseup', this.end.bind(this)) | |
element.addEventListener('mouseleave', this.end.bind(this)) | |
element.addEventListener('touchend', this.end.bind(this)) | |
element.addEventListener('touchcancel', this.end.bind(this)) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment