frpjs
Version:
Functional Reactive Programming JavaScript Library
134 lines (107 loc) • 4.52 kB
JavaScript
"use strict"
import frp from "frpjs"
import dom from "frpjs/dom"
export default function(selector, slideWidth, slideHeight) {
let stream$ = frp.compose(
dom.onTouchStart(selector),
frp.merge(dom.onTouchMove(selector)),
frp.merge(dom.onTouchEnd(selector)),
frp.map(event => ({
type: event.type,
pageX: getPageX(event),
time: event.timeStamp
})),
frp.fold((prev, curr) => {
curr.startX = (curr.type == "touchstart") ? curr.pageX : prev.startX
curr.startTime = (curr.type == "touchstart") ? curr.time : prev.startTime
curr.displacement = curr.pageX - curr.startX
curr.slideIndex = prev.slideIndex
return curr
}, { slideIndex: 0 })
)
const view = new SwipeView(selector, slideWidth, slideHeight)
stream$(event => activateEventStream(event, view))
}
function activateEventStream(event, view) {
if (event.type == "touchmove")
handleTouchMove(event, view)
else if (event.type == "touchend")
handleTouchEnd(event, view)
}
function handleTouchMove(event, view) {
if (view.canSlideLeft(event) || view.canSlideRight(event)) {
let distance = -(event.slideIndex * view.slideWidth) + event.displacement
view.move(distance)
} else if (view.isPullingEdge(event)) {
let distance = -(event.slideIndex * view.slideWidth) + (view.edgePadding / view.slideWidth) * event.displacement
view.move(distance)
}
}
function handleTouchEnd(event, view) {
if (view.hasCrossedMidPoint(event) || view.isFlicked(event)) {
if (view.canSlideRight(event))
event.slideIndex++
else if (view.canSlideLeft(event))
event.slideIndex--
}
let distance = -(event.slideIndex * view.slideWidth)
let time = view.isFlicked(event) ? 150 : 300
view.animate(distance, time)
}
function SwipeView(selector, slideWidth, slideHeight) {
this.slideWidth = slideWidth || window.innerWidth
this.slideHeight = slideHeight || window.innerHeight
this.container = dom.select(selector)
this.slider = this.container.firstElementChild
this.slides = this.slider.children
this.numSlides = this.slides.length
this.edgePadding = this.slideWidth / 10
this.setupStyles()
}
SwipeView.prototype.setupStyles = function() {
this.container.style["width"] = this.slideWidth + "px"
this.container.style["height"] = this.slideHeight + "px"
this.container.style["overflow"] = "hidden"
this.slider.style["width"] = this.numSlides * 100 + "%"
this.slider.style["height"] = "100%"
this.slider.style["transform"] = "translate3d(0, 0, 0)"
let slideWidth = this.slideWidth, slideHeight = this.slideHeight
Array.prototype.forEach.call(this.slides, function(slide) {
slide.style["width"] = slideWidth + "px"
slide.style["height"] = slideHeight + "px"
slide.style["float"] = "left"
})
}
SwipeView.prototype.canSlideLeft = function(event) {
return (event.displacement > 0 && event.slideIndex > 0)
}
SwipeView.prototype.canSlideRight = function(event) {
return (event.displacement < 0 && event.slideIndex < this.numSlides - 1)
}
SwipeView.prototype.isPullingEdge = function(event) {
let sliderPosition = this.slider.getBoundingClientRect().left
let nMinusOneSlides = (this.numSlides - 1) * this.slideWidth // width of (n - 1) slides
return ((0 <= sliderPosition && sliderPosition < this.edgePadding) ||
(-nMinusOneSlides - this.edgePadding < sliderPosition && sliderPosition <= -nMinusOneSlides))
}
SwipeView.prototype.hasCrossedMidPoint = function(event) {
return Math.abs(event.displacement) > this.slideWidth/2
}
SwipeView.prototype.isFlicked = function(event) {
return getSpeed(event) > 1
}
SwipeView.prototype.animate = function(translateX, ms) {
this.slider.style["transition"] = "transform " + ms +"ms ease-out"
this.slider.style["transform"] = "translate3d(" + translateX + "px, 0, 0)"
}
SwipeView.prototype.move = function(translateX) {
this.slider.style["transition"] = "none"
this.slider.style["transform"] = "translate3d(" + translateX + "px, 0, 0)"
}
function getSpeed(event) {
return Math.abs(event.displacement) / (event.time - event.startTime)
}
function getPageX(event) {
return (event.type == "touchend") ?
event.changedTouches[0].pageX : event.targetTouches[0].pageX
}