vue-slick-carousel
Version:
Vue Slick Carousel with True SSR Written for Faster Luxstay
872 lines (828 loc) • 23.4 kB
JavaScript
export const canUseDOM = () =>
!!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
)
export const getPreClones = spec => {
if (spec.unslick || !spec.infinite) {
return 0
}
if (spec.variableWidth) {
return spec.slideCount
}
return spec.slidesToShow + (spec.centerMode ? 1 : 0)
}
export const getPostClones = spec => {
if (spec.unslick || !spec.infinite) {
return 0
}
return spec.slideCount
}
export const keyHandler = (e, accessibility, rtl) => {
if (e.target.tagName.match('TEXTAREA|INPUT|SELECT') || !accessibility)
return ''
if (e.keyCode === 37) return rtl ? 'next' : 'previous'
if (e.keyCode === 39) return rtl ? 'previous' : 'next'
return ''
}
export const siblingDirection = spec => {
if (spec.targetSlide > spec.currentSlide) {
if (spec.targetSlide > spec.currentSlide + slidesOnRight(spec)) {
return 'left'
}
return 'right'
} else {
if (spec.targetSlide < spec.currentSlide - slidesOnLeft(spec)) {
return 'right'
}
return 'left'
}
}
export const slidesOnRight = ({
slidesToShow,
centerMode,
rtl,
centerPadding,
}) => {
// returns no of slides on the right of active slide
if (centerMode) {
let right = (slidesToShow - 1) / 2 + 1
if (parseInt(centerPadding) > 0) right += 1
if (rtl && slidesToShow % 2 === 0) right += 1
return right
}
if (rtl) {
return 0
}
return slidesToShow - 1
}
export const slidesOnLeft = ({
slidesToShow,
centerMode,
rtl,
centerPadding,
}) => {
// returns no of slides on the left of active slide
if (centerMode) {
let left = (slidesToShow - 1) / 2 + 1
if (parseInt(centerPadding) > 0) left += 1
if (!rtl && slidesToShow % 2 === 0) left += 1
return left
}
if (rtl) {
return slidesToShow - 1
}
return 0
}
// startIndex that needs to be present
export const lazyStartIndex = spec => spec.currentSlide - lazySlidesOnLeft(spec)
export const lazyEndIndex = spec => spec.currentSlide + lazySlidesOnRight(spec)
export const lazySlidesOnLeft = spec =>
spec.centerMode
? Math.floor(spec.slidesToShow / 2) +
(parseInt(spec.centerPadding) > 0 ? 1 : 0)
: 0
export const lazySlidesOnRight = spec =>
spec.centerMode
? Math.floor((spec.slidesToShow - 1) / 2) +
1 +
(parseInt(spec.centerPadding) > 0 ? 1 : 0)
: spec.slidesToShow
export const getOnDemandLazySlides = spec => {
let onDemandSlides = []
let startIndex = lazyStartIndex(spec)
let endIndex = lazyEndIndex(spec)
for (let slideIndex = startIndex; slideIndex < endIndex; slideIndex++) {
if (spec.lazyLoadedList.indexOf(slideIndex) < 0) {
onDemandSlides.push(slideIndex)
}
}
return onDemandSlides
}
export const changeSlide = (spec, options) => {
var indexOffset, previousInt, slideOffset, unevenOffset, targetSlide
const {
slidesToScroll,
slidesToShow,
slideCount,
currentSlide,
lazyLoad,
infinite,
} = spec
unevenOffset = slideCount % slidesToScroll !== 0
indexOffset = unevenOffset ? 0 : (slideCount - currentSlide) % slidesToScroll
if (options.message === 'previous') {
slideOffset =
indexOffset === 0 ? slidesToScroll : slidesToShow - indexOffset
targetSlide = currentSlide - slideOffset
if (lazyLoad && !infinite) {
previousInt = currentSlide - slideOffset
targetSlide = previousInt === -1 ? slideCount - 1 : previousInt
}
} else if (options.message === 'next') {
slideOffset = indexOffset === 0 ? slidesToScroll : indexOffset
targetSlide = currentSlide + slideOffset
if (lazyLoad && !infinite) {
targetSlide = ((currentSlide + slidesToScroll) % slideCount) + indexOffset
}
} else if (options.message === 'dots') {
// Click on dots
targetSlide = options.index * options.slidesToScroll
if (targetSlide === options.currentSlide) {
return null
}
} else if (options.message === 'children') {
// Click on the slides
targetSlide = options.index
if (targetSlide === options.currentSlide) {
return null
}
if (infinite) {
let direction = siblingDirection({ ...spec, targetSlide })
if (targetSlide > options.currentSlide && direction === 'left') {
targetSlide = targetSlide - slideCount
} else if (targetSlide < options.currentSlide && direction === 'right') {
targetSlide = targetSlide + slideCount
}
}
} else if (options.message === 'index') {
targetSlide = Number(options.index)
if (targetSlide === options.currentSlide) {
return null
}
}
return targetSlide
}
export const filterUndefined = props =>
Object.keys(props)
.filter(key => props[key] !== undefined)
.reduce((obj, key) => {
obj[key] = props[key]
return obj
}, {})
export const filterUndefinedOrNull = props =>
Object.keys(props)
.filter(key => props[key] !== undefined && props[key] !== null)
.reduce((obj, key) => {
obj[key] = props[key]
return obj
}, {})
export const swipeStart = (e, swipe, draggable) => {
if (!swipe || (!draggable && e.type.indexOf('mouse') !== -1)) return ''
return {
dragging: true,
touchObject: {
startX: e.touches ? e.touches[0].pageX : e.clientX,
startY: e.touches ? e.touches[0].pageY : e.clientY,
curX: e.touches ? e.touches[0].pageX : e.clientX,
curY: e.touches ? e.touches[0].pageY : e.clientY,
},
}
}
export const swipeMove = (e, spec) => {
// spec also contains, trackRef and slideIndex
const {
scrolling,
animating,
vertical,
swipeToSlide,
verticalSwiping,
rtl,
currentSlide,
edgeFriction,
edgeDragged,
onEdge,
swiped,
swiping,
slideCount,
slidesToScroll,
infinite,
touchObject,
swipeEvent,
listHeight,
listWidth,
} = spec
if (scrolling) return
if (animating) return e.preventDefault()
if (vertical && swipeToSlide && verticalSwiping) e.preventDefault()
let swipeLeft,
state = {}
let curLeft = getTrackLeft(spec)
touchObject.curX = e.touches ? e.touches[0].pageX : e.clientX
touchObject.curY = e.touches ? e.touches[0].pageY : e.clientY
touchObject.swipeLength = Math.round(
Math.sqrt(Math.pow(touchObject.curX - touchObject.startX, 2)),
)
let verticalSwipeLength = Math.round(
Math.sqrt(Math.pow(touchObject.curY - touchObject.startY, 2)),
)
if (!verticalSwiping && !swiping && verticalSwipeLength > 10) {
return { scrolling: true }
}
if (verticalSwiping) touchObject.swipeLength = verticalSwipeLength
let positionOffset =
(!rtl ? 1 : -1) * (touchObject.curX > touchObject.startX ? 1 : -1)
if (verticalSwiping)
positionOffset = touchObject.curY > touchObject.startY ? 1 : -1
let dotCount = Math.ceil(slideCount / slidesToScroll)
let swipeDirection = getSwipeDirection(spec.touchObject, verticalSwiping)
let touchSwipeLength = touchObject.swipeLength
if (!infinite) {
if (
(currentSlide === 0 && swipeDirection === 'right') ||
(currentSlide + 1 >= dotCount && swipeDirection === 'left') ||
(!canGoNext(spec) && swipeDirection === 'left')
) {
touchSwipeLength = touchObject.swipeLength * edgeFriction
if (edgeDragged === false && onEdge) {
onEdge(swipeDirection)
state['edgeDragged'] = true
}
}
}
if (!swiped && swipeEvent) {
swipeEvent(swipeDirection)
state['swiped'] = true
}
if (!vertical) {
if (!rtl) {
swipeLeft = curLeft + touchSwipeLength * positionOffset
} else {
swipeLeft = curLeft - touchSwipeLength * positionOffset
}
} else {
swipeLeft =
curLeft + touchSwipeLength * (listHeight / listWidth) * positionOffset
}
if (verticalSwiping) {
swipeLeft = curLeft + touchSwipeLength * positionOffset
}
state = {
...state,
touchObject,
swipeLeft,
trackStyle: getTrackCSS({ ...spec, left: swipeLeft }),
}
if (
Math.abs(touchObject.curX - touchObject.startX) <
Math.abs(touchObject.curY - touchObject.startY) * 0.8
) {
return state
}
if (touchObject.swipeLength > 10) {
state['swiping'] = true
e.preventDefault()
}
return state
}
export const swipeEnd = (e, spec) => {
const {
dragging,
swipe,
touchObject,
listWidth,
touchThreshold,
verticalSwiping,
listHeight,
currentSlide,
swipeToSlide,
scrolling,
onSwipe,
} = spec
if (!dragging) {
if (swipe) e.preventDefault()
return {}
}
let minSwipe = verticalSwiping
? listHeight / touchThreshold
: listWidth / touchThreshold
let swipeDirection = getSwipeDirection(touchObject, verticalSwiping)
// reset the state of touch related state variables.
let state = {
dragging: false,
edgeDragged: false,
scrolling: false,
swiping: false,
swiped: false,
swipeLeft: null,
touchObject: {},
}
if (scrolling) {
return state
}
if (!touchObject.swipeLength) {
return state
}
if (touchObject.swipeLength > minSwipe) {
e.preventDefault()
if (onSwipe) {
onSwipe(swipeDirection)
}
let slideCount, newSlide
switch (swipeDirection) {
case 'left':
case 'up':
newSlide = currentSlide + getSlideCount(spec)
slideCount = swipeToSlide ? checkNavigable(spec, newSlide) : newSlide
state['currentDirection'] = 0
break
case 'right':
case 'down':
newSlide = currentSlide - getSlideCount(spec)
slideCount = swipeToSlide ? checkNavigable(spec, newSlide) : newSlide
state['currentDirection'] = 1
break
default:
slideCount = currentSlide
}
state['triggerSlideHandler'] = slideCount
} else {
// Adjust the track back to it's original position.
let currentLeft = getTrackLeft(spec)
state['trackStyle'] = getTrackAnimateCSS({ ...spec, left: currentLeft })
}
return state
}
export const getNavigableIndexes = spec => {
let max = spec.infinite ? spec.slideCount * 2 : spec.slideCount
let breakpoint = spec.infinite ? spec.slidesToShow * -1 : 0
let counter = spec.infinite ? spec.slidesToShow * -1 : 0
let indexes = []
while (breakpoint < max) {
indexes.push(breakpoint)
breakpoint = counter + spec.slidesToScroll
counter += Math.min(spec.slidesToScroll, spec.slidesToShow)
}
return indexes
}
export const checkNavigable = (spec, index) => {
const navigables = getNavigableIndexes(spec)
let prevNavigable = 0
if (index > navigables[navigables.length - 1]) {
index = navigables[navigables.length - 1]
} else {
for (let n in navigables) {
if (index < navigables[n]) {
index = prevNavigable
break
}
prevNavigable = navigables[n]
}
}
return index
}
export const getSlideCount = spec => {
const centerOffset = spec.centerMode
? spec.slideWidth * Math.floor(spec.slidesToShow / 2)
: 0
if (spec.swipeToSlide) {
let swipedSlide
const slickList = spec.listRef
const slides = slickList.querySelectorAll('.slick-slide')
Array.from(slides).every(slide => {
if (!spec.vertical) {
if (
slide.offsetLeft - centerOffset + getWidth(slide) / 2 >
spec.swipeLeft * -1
) {
swipedSlide = slide
return false
}
} else {
if (slide.offsetTop + getHeight(slide) / 2 > spec.swipeLeft * -1) {
swipedSlide = slide
return false
}
}
return true
})
if (!swipedSlide) {
return 0
}
const currentIndex =
spec.rtl === true
? spec.slideCount - spec.currentSlide
: spec.currentSlide
const slidesTraversed =
Math.abs(swipedSlide.dataset.index - currentIndex) || 1
return slidesTraversed
} else {
return spec.slidesToScroll
}
}
// given an object and a list of keys, return new object with given keys
export const extractObject = (spec, keys) => {
let newObject = {}
keys.forEach(key => (newObject[key] = spec[key]))
return newObject
}
export const PROP_KEYS = {
TRACK: [
'fade',
'cssEase',
'speed',
'infinite',
'centerMode',
'currentSlide',
'lazyLoad',
'lazyLoadedList',
'rtl',
'slideWidth',
'slideHeight',
'listHeight',
'vertical',
'slidesToShow',
'slidesToScroll',
'slideCount',
'trackStyle',
'variableWidth',
'unslick',
'centerPadding',
],
DOT: [
'dotsClass',
'slideCount',
'slidesToShow',
'currentSlide',
'slidesToScroll',
'children',
'customPaging',
'infinite',
],
ARROW: [
'infinite',
'centerMode',
'currentSlide',
'slideCount',
'slidesToShow',
'prevArrow',
'nextArrow',
],
}
// whether or not we can go next
export const canGoNext = spec => {
let canGo = true
if (!spec.infinite) {
if (spec.centerMode && spec.currentSlide >= spec.slideCount - 1) {
canGo = false
} else if (
spec.slideCount <= spec.slidesToShow ||
spec.currentSlide >= spec.slideCount - spec.slidesToShow
) {
canGo = false
}
}
return canGo
}
export const slideHandler = spec => {
const {
waitForAnimate,
animating,
fade,
infinite,
index,
slideCount,
lazyLoadedList,
lazyLoad,
currentSlide,
centerMode,
slidesToScroll,
slidesToShow,
useCSS,
} = spec
if (waitForAnimate && animating) return {}
let animationSlide = index,
finalSlide,
animationLeft,
finalLeft
let state = {},
nextState = {}
if (fade) {
if (!infinite && (index < 0 || index >= slideCount)) return {}
if (index < 0) {
animationSlide = index + slideCount
} else if (index >= slideCount) {
animationSlide = index - slideCount
}
if (lazyLoad && lazyLoadedList.indexOf(animationSlide) < 0) {
lazyLoadedList.push(animationSlide)
}
state = {
animating: true,
currentSlide: animationSlide,
lazyLoadedList,
}
nextState = { animating: false }
} else {
finalSlide = animationSlide
if (animationSlide < 0) {
finalSlide = animationSlide + slideCount
if (!infinite) finalSlide = 0
else if (slideCount % slidesToScroll !== 0)
finalSlide = slideCount - (slideCount % slidesToScroll)
} else if (!canGoNext(spec) && animationSlide > currentSlide) {
animationSlide = finalSlide = currentSlide
} else if (centerMode && animationSlide >= slideCount) {
animationSlide = infinite ? slideCount : slideCount - 1
finalSlide = infinite ? 0 : slideCount - 1
} else if (animationSlide >= slideCount) {
finalSlide = animationSlide - slideCount
if (!infinite) finalSlide = slideCount - slidesToShow
else if (slideCount % slidesToScroll !== 0) finalSlide = 0
}
animationLeft = getTrackLeft({ ...spec, slideIndex: animationSlide })
finalLeft = getTrackLeft({ ...spec, slideIndex: finalSlide })
if (!infinite) {
if (animationLeft === finalLeft) animationSlide = finalSlide
animationLeft = finalLeft
}
lazyLoad &&
lazyLoadedList.concat(
getOnDemandLazySlides({ ...spec, currentSlide: animationSlide }),
)
if (!useCSS) {
state = {
currentSlide: finalSlide,
trackStyle: getTrackCSS({ ...spec, left: finalLeft }),
lazyLoadedList,
}
} else {
state = {
animating: true,
currentSlide: finalSlide,
trackStyle: getTrackAnimateCSS({ ...spec, left: animationLeft }),
lazyLoadedList,
}
nextState = {
animating: false,
currentSlide: finalSlide,
trackStyle: getTrackCSS({ ...spec, left: finalLeft }),
swipeLeft: null,
}
}
}
return { state, nextState }
}
// get width of an element
export const getWidth = elem => (elem && elem.offsetWidth) || 0
export const getHeight = elem => (elem && elem.offsetHeight) || 0
export const getSwipeDirection = (touchObject, verticalSwiping = false) => {
var xDist, yDist, r, swipeAngle
xDist = touchObject.startX - touchObject.curX
yDist = touchObject.startY - touchObject.curY
r = Math.atan2(yDist, xDist)
swipeAngle = Math.round((r * 180) / Math.PI)
if (swipeAngle < 0) {
swipeAngle = 360 - Math.abs(swipeAngle)
}
if (
(swipeAngle <= 45 && swipeAngle >= 0) ||
(swipeAngle <= 360 && swipeAngle >= 315)
) {
return 'left'
}
if (swipeAngle >= 135 && swipeAngle <= 225) {
return 'right'
}
if (verticalSwiping === true) {
if (swipeAngle >= 35 && swipeAngle <= 135) {
return 'up'
} else {
return 'down'
}
}
return 'vertical'
}
// get initialized state
export const initializedState = spec => {
// spec also contains listRef, trackRef
let slideCount = spec.children.length
let listWidth = Math.ceil(getWidth(spec.listRef))
let trackWidth = Math.ceil(getWidth(spec.trackRef))
let slideWidth
if (!spec.vertical) {
let centerPaddingAdj = spec.centerMode && parseInt(spec.centerPadding) * 2
if (
typeof spec.centerPadding === 'string' &&
spec.centerPadding.slice(-1) === '%'
) {
centerPaddingAdj *= listWidth / 100
}
slideWidth = Math.ceil((listWidth - centerPaddingAdj) / spec.slidesToShow)
} else {
slideWidth = listWidth
}
let slideHeight =
spec.listRef && getHeight(spec.listRef.querySelector('[data-index="0"]'))
let listHeight = slideHeight * spec.slidesToShow
let currentSlide =
spec.currentSlide === undefined ? spec.initialSlide : spec.currentSlide
if (spec.rtl && spec.currentSlide === undefined) {
currentSlide = slideCount - 1 - spec.initialSlide
}
let lazyLoadedList = spec.lazyLoadedList || []
let slidesToLoad = getOnDemandLazySlides(
{ currentSlide, lazyLoadedList },
spec,
)
lazyLoadedList.concat(slidesToLoad)
let state = {
slideCount,
slideWidth,
listWidth,
trackWidth,
currentSlide,
slideHeight,
listHeight,
lazyLoadedList,
}
if (spec.autoplaying === null && spec.autoplay) {
state['autoplaying'] = 'playing'
}
return state
}
export const getTrackLeft = spec => {
if (spec.unslick) {
return 0
}
checkSpecKeys(spec, [
'slideIndex',
'trackRef',
'infinite',
'centerMode',
'slideCount',
'slidesToShow',
'slidesToScroll',
'slideWidth',
'listWidth',
'variableWidth',
'slideHeight',
])
const {
slideIndex,
trackRef,
infinite,
centerMode,
slideCount,
slidesToShow,
slidesToScroll,
slideWidth,
listWidth,
variableWidth,
slideHeight,
fade,
vertical,
} = spec
var slideOffset = 0
var targetLeft
var targetSlide
var verticalOffset = 0
if (fade || spec.slideCount === 1) {
return 0
}
let slidesToOffset = 0
if (infinite) {
slidesToOffset = -getPreClones(spec) // bring active slide to the beginning of visual area
// if next scroll doesn't have enough children, just reach till the end of original slides instead of shifting slidesToScroll children
if (
slideCount % slidesToScroll !== 0 &&
slideIndex + slidesToScroll > slideCount
) {
slidesToOffset = -(slideIndex > slideCount
? slidesToShow - (slideIndex - slideCount)
: slideCount % slidesToScroll)
}
// shift current slide to center of the frame
if (centerMode) {
slidesToOffset += parseInt(slidesToShow / 2)
}
} else {
if (
slideCount % slidesToScroll !== 0 &&
slideIndex + slidesToScroll > slideCount
) {
slidesToOffset = slidesToShow - (slideCount % slidesToScroll)
}
if (centerMode) {
slidesToOffset = parseInt(slidesToShow / 2)
}
}
slideOffset = slidesToOffset * slideWidth
verticalOffset = slidesToOffset * slideHeight
if (!vertical) {
targetLeft = slideIndex * slideWidth * -1 + slideOffset
} else {
targetLeft = slideIndex * slideHeight * -1 + verticalOffset
}
if (variableWidth === true) {
var targetSlideIndex
let trackElem = trackRef.$el
targetSlideIndex = slideIndex + getPreClones(spec)
targetSlide = trackElem && trackElem.childNodes[targetSlideIndex]
targetLeft = targetSlide ? targetSlide.offsetLeft * -1 : 0
if (centerMode === true) {
targetSlideIndex = infinite ? slideIndex + getPreClones(spec) : slideIndex
targetSlide = trackElem && trackElem.children[targetSlideIndex]
targetLeft = 0
for (let slide = 0; slide < targetSlideIndex; slide++) {
targetLeft -=
trackElem &&
trackElem.children[slide] &&
trackElem.children[slide].offsetWidth
}
targetLeft -= parseInt(spec.centerPadding)
targetLeft += targetSlide && (listWidth - targetSlide.offsetWidth) / 2
}
}
return targetLeft
}
export const getTotalSlides = spec =>
spec.slideCount === 1
? 1
: getPreClones(spec) + spec.slideCount + getPostClones(spec)
export const checkSpecKeys = (spec, keysArray) =>
keysArray.reduce((value, key) => value && spec.hasOwnProperty(key), true)
? null
: console.error('Keys Missing:', spec) // eslint-disable-line no-console
export const getTrackCSS = spec => {
checkSpecKeys(spec, [
'left',
'variableWidth',
'slideCount',
'slidesToShow',
'slideWidth',
])
let trackWidth, trackHeight
const trackChildren = spec.slideCount + 2 * spec.slidesToShow
if (!spec.vertical) {
trackWidth = getTotalSlides(spec) * spec.slideWidth
} else {
trackHeight = trackChildren * spec.slideHeight
}
let style = {
opacity: 1,
transition: '',
WebkitTransition: '',
}
if (spec.useTransform) {
let WebkitTransform = !spec.vertical
? 'translate3d(' + spec.left + 'px, 0px, 0px)'
: 'translate3d(0px, ' + spec.left + 'px, 0px)'
let transform = !spec.vertical
? 'translate3d(' + spec.left + 'px, 0px, 0px)'
: 'translate3d(0px, ' + spec.left + 'px, 0px)'
let msTransform = !spec.vertical
? 'translateX(' + spec.left + 'px)'
: 'translateY(' + spec.left + 'px)'
style = {
...style,
WebkitTransform,
transform,
msTransform,
}
} else {
if (spec.vertical) {
style['top'] = spec.left
} else {
style['left'] = spec.left
}
}
if (spec.fade) style = { opacity: 1 }
if (trackWidth) style.width = trackWidth + 'px'
if (trackHeight) style.height = trackHeight + 'px'
// Fallback for IE8
if (window && !window.addEventListener && window.attachEvent) {
if (!spec.vertical) {
style.marginLeft = spec.left + 'px'
} else {
style.marginTop = spec.left + 'px'
}
}
return style
}
export const getTrackAnimateCSS = spec => {
checkSpecKeys(spec, [
'left',
'variableWidth',
'slideCount',
'slidesToShow',
'slideWidth',
'speed',
'cssEase',
])
let style = getTrackCSS(spec)
// useCSS is true by default so it can be undefined
if (spec.useTransform) {
style.WebkitTransition =
'-webkit-transform ' + spec.speed + 'ms ' + spec.cssEase
style.transition = 'transform ' + spec.speed + 'ms ' + spec.cssEase
} else {
if (spec.vertical) {
style.transition = 'top ' + spec.speed + 'ms ' + spec.cssEase
} else {
style.transition = 'left ' + spec.speed + 'ms ' + spec.cssEase
}
}
return style
}