diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-09-08 18:15:53 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-09-08 18:15:53 +0200 |
| commit | d919bdd91a540050e792a11e9837223388fd6aa7 (patch) | |
| tree | e626eae9f8a51ace1c62c9c6fdfdd3ed7cd97d34 /animism-align/frontend/app/utils/vendor | |
| parent | 9a27f55d7766629d71c2d93bb63609f6e890ae8b (diff) | |
forking keen-slider... adding scroll
Diffstat (limited to 'animism-align/frontend/app/utils/vendor')
3 files changed, 855 insertions, 0 deletions
diff --git a/animism-align/frontend/app/utils/vendor/keen-slider/keen-slider.js b/animism-align/frontend/app/utils/vendor/keen-slider/keen-slider.js new file mode 100644 index 0000000..8efea56 --- /dev/null +++ b/animism-align/frontend/app/utils/vendor/keen-slider/keen-slider.js @@ -0,0 +1,757 @@ +import './polyfills' + +function KeenSlider(initialContainer, initialOptions = {}) { + const attributeMoving = 'data-keen-slider-moves' + const attributeVertical = 'data-keen-slider-v' + + let container + let events = [] + let touchControls + let length + let origin + let slides + let width + let slidesPerView + let spacing + let resizeLastWidth + let breakpointCurrent = null + let optionsChanged = false + let sliderCreated = false + + let trackCurrentIdx + let trackPosition = 0 + let trackMeasurePoints = [] + let trackDirection + let trackMeasureTimeout + let trackSpeed + let trackSlidePositions + let trackProgress + + let options + + // touch/swipe helper + let touchIndexStart + let touchActive + let touchIdentifier + let touchLastX + let touchLastClientX + let touchLastClientY + let touchMultiplicator + let touchJustStarted + + // animation + let reqId + let startTime + let moveDistance + let moveDuration + let moveEasing + let moved + let moveForceFinish + let moveCallBack + + function eventAdd(element, event, handler, options = {}) { + element.addEventListener(event, handler, options) + events.push([element, event, handler, options]) + } + + function eventDrag(e) { + if ( + !touchActive || + touchIdentifier !== eventGetIdentifier(e) || + !isTouchable() + ) + return + const x = eventGetX(e).x + if (!eventIsSlide(e) && touchJustStarted) { + return eventDragStop(e) + } + if (touchJustStarted) { + trackMeasureReset() + touchLastX = x + container.setAttribute(attributeMoving, true) + touchJustStarted = false + } + if (e.cancelable) e.preventDefault() + const touchDistance = touchLastX - x + trackAdd(touchDistance, e.timeStamp) + touchLastX = x + } + + function eventDragStart(e) { + if (touchActive || !isTouchable() || eventIsIgnoreTarget(e.target)) return + touchActive = true + touchJustStarted = true + touchIdentifier = eventGetIdentifier(e) + eventIsSlide(e) + moveAnimateAbort() + touchIndexStart = trackCurrentIdx + touchLastX = eventGetX(e).x + trackAdd(0, e.timeStamp) + hook('dragStart') + } + + function eventDragStop(e) { + if ( + !touchActive || + touchIdentifier !== eventGetIdentifier(e, true) || + !isTouchable() + ) + return + container.removeAttribute(attributeMoving) + touchActive = false + moveWithSpeed() + + hook('dragEnd') + } + + function eventGetChangedTouches(e) { + return e.changedTouches + } + + function eventGetIdentifier(e, changedTouches = false) { + const touches = changedTouches + ? eventGetChangedTouches(e) + : eventGetTargetTouches(e) + return !touches ? 'default' : touches[0] ? touches[0].identifier : 'error' + } + + function eventGetTargetTouches(e) { + return e.targetTouches + } + + function eventGetX(e) { + const touches = eventGetTargetTouches(e) + return { + x: isVertialSlider() + ? !touches + ? e.pageY + : touches[0].screenY + : !touches + ? e.pageX + : touches[0].screenX, + timestamp: e.timeStamp, + } + } + + function eventIsIgnoreTarget(target) { + return target.hasAttribute(options.preventEvent) + } + + function eventIsSlide(e) { + const touches = eventGetTargetTouches(e) + if (!touches) return true + const touch = touches[0] + const x = isVertialSlider() ? touch.clientY : touch.clientX + const y = isVertialSlider() ? touch.clientX : touch.clientY + const isSlide = + touchLastClientX !== undefined && + touchLastClientY !== undefined && + Math.abs(touchLastClientY - y) <= Math.abs(touchLastClientX - x) + + touchLastClientX = x + touchLastClientY = y + return isSlide + } + + function eventWheel(e) { + if (!isTouchable()) return + if (touchActive) e.preventDefault() + const delta = Math.abs(e.deltaY) > Math.abs(e.deltaX) ? e.deltaY : e.deltaX + trackAdd(touchMultiplicator(delta, pubfuncs), e.timeStamp) + } + + function eventsAdd() { + eventAdd(window, 'orientationchange', sliderResizeFix) + eventAdd(window, 'resize', () => sliderResize()) + eventAdd(container, 'dragstart', function (e) { + if (!isTouchable()) return + e.preventDefault() + }) + eventAdd(container, 'mousedown', eventDragStart) + eventAdd(container, 'mousemove', eventDrag) + eventAdd(container, 'mouseleave', eventDragStop) + eventAdd(container, 'mouseup', eventDragStop) + eventAdd(container, 'touchstart', eventDragStart, { + passive: true, + }) + eventAdd(container, 'touchmove', eventDrag, { + passive: false, + }) + eventAdd(container, 'touchend', eventDragStop, { + passive: true, + }) + eventAdd(container, 'touchcancel', eventDragStop, { + passive: true, + }) + eventAdd(window, 'wheel', eventWheel, { + passive: false, + }) + } + + function eventsRemove() { + events.forEach(event => { + event[0].removeEventListener(event[1], event[2], event[3]) + }) + events = [] + } + + function hook(hook) { + if (options[hook]) options[hook](pubfuncs) + } + + function isCenterMode() { + return options.centered + } + + function isTouchable() { + return touchControls !== undefined ? touchControls : options.controls + } + + function isLoop() { + return options.loop + } + + function isRubberband() { + return !options.loop && options.rubberband + } + + function isVertialSlider() { + return !!options.vertical + } + + function moveAnimate() { + reqId = window.requestAnimationFrame(moveAnimateUpdate) + } + + function moveAnimateAbort() { + if (reqId) { + window.cancelAnimationFrame(reqId) + reqId = null + } + startTime = null + } + + function moveAnimateUpdate(timestamp) { + if (!startTime) startTime = timestamp + const duration = timestamp - startTime + let add = moveCalcValue(duration) + if (duration >= moveDuration) { + trackAdd(moveDistance - moved, false) + if (moveCallBack) return moveCallBack() + hook('afterChange') + return + } + + const offset = trackCalculateOffset(add) + if (offset !== 0 && !isLoop() && !isRubberband() && !moveForceFinish) { + trackAdd(add - offset, false) + return + } + if (offset !== 0 && isRubberband() && !moveForceFinish) { + return moveRubberband(Math.sign(offset)) + } + moved += add + trackAdd(add, false) + moveAnimate() + } + + function moveCalcValue(progress) { + const value = moveDistance * moveEasing(progress / moveDuration) - moved + return value + } + + function moveWithSpeed() { + hook('beforeChange') + switch (options.mode) { + case 'free': + moveFree() + break + case 'free-snap': + moveSnapFree() + break + case 'snap': + default: + moveSnapOne() + break + } + } + + function moveSnapOne() { + const startIndex = + slidesPerView === 1 && trackDirection !== 0 + ? touchIndexStart + : trackCurrentIdx + moveToIdx(startIndex + Math.sign(trackDirection)) + } + + function moveToIdx( + idx, + forceFinish, + duration = options.duration, + relative = false, + nearest = false + ) { + // forceFinish is used to ignore boundaries when rubberband movement is active + + idx = trackGetIdx(idx, relative, nearest) + const easing = t => 1 + --t * t * t * t * t + moveTo(trackGetIdxDistance(idx), duration, easing, forceFinish) + } + + function moveFree() { + // todo: refactor! + if (trackSpeed === 0) + return trackCalculateOffset(0) && !isLoop() + ? moveToIdx(trackCurrentIdx) + : false + const friction = options.friction / Math.pow(Math.abs(trackSpeed), -0.5) + const distance = + (Math.pow(trackSpeed, 2) / friction) * Math.sign(trackSpeed) + const duration = Math.abs(trackSpeed / friction) * 6 + const easing = function (t) { + return 1 - Math.pow(1 - t, 5) + } + moveTo(distance, duration, easing) + } + + function moveSnapFree() { + // todo: refactor! + if (trackSpeed === 0) return moveToIdx(trackCurrentIdx) + const friction = options.friction / Math.pow(Math.abs(trackSpeed), -0.5) + const distance = + (Math.pow(trackSpeed, 2) / friction) * Math.sign(trackSpeed) + const duration = Math.abs(trackSpeed / friction) * 6 + const easing = function (t) { + return 1 - Math.pow(1 - t, 5) + } + const idx_trend = (trackPosition + distance) / (width / slidesPerView) + const idx = + trackDirection === -1 ? Math.floor(idx_trend) : Math.ceil(idx_trend) + moveTo(idx * (width / slidesPerView) - trackPosition, duration, easing) + } + + function moveRubberband() { + moveAnimateAbort() + // todo: refactor! + if (trackSpeed === 0) return moveToIdx(trackCurrentIdx, true) + const friction = 0.04 / Math.pow(Math.abs(trackSpeed), -0.5) + const distance = + (Math.pow(trackSpeed, 2) / friction) * Math.sign(trackSpeed) + + const easing = function (t) { + return --t * t * t + 1 + } + + const speed = trackSpeed + const cb = () => { + moveTo( + trackGetIdxDistance(trackGetIdx(trackCurrentIdx)), + 500, + easing, + true + ) + } + moveTo(distance, Math.abs(speed / friction) * 3, easing, true, cb) + } + + function moveTo(distance, duration, easing, forceFinish, cb) { + moveAnimateAbort() + moveDistance = distance + moved = 0 + moveDuration = duration + moveEasing = easing + moveForceFinish = forceFinish + moveCallBack = cb + startTime = null + moveAnimate() + } + + function sliderBind(force_resize) { + let _container = getElements(initialContainer) + if (!_container.length) return + container = _container[0] + sliderResize(force_resize) + eventsAdd() + hook('mounted') + } + + function sliderCheckBreakpoint() { + const breakpoints = initialOptions.breakpoints || [] + let lastValid + for (let value in breakpoints) { + if (window.matchMedia(value).matches) lastValid = value + } + if (lastValid === breakpointCurrent) return true + breakpointCurrent = lastValid + const _options = breakpointCurrent + ? breakpoints[breakpointCurrent] + : initialOptions + if (_options.breakpoints && breakpointCurrent) delete _options.breakpoints + options = { ...defaultOptions, ...initialOptions, ..._options } + optionsChanged = true + resizeLastWidth = null + sliderRebind() + } + + function sliderGetSlidesPerView(option) { + return typeof option === 'function' + ? option() + : clampValue(option, 1, Math.max(isLoop() ? length - 1 : length, 1)) + } + + function sliderInit() { + sliderCheckBreakpoint() + sliderCreated = true + hook('created') + } + + function sliderRebind(new_options, force_resize) { + if (new_options) initialOptions = new_options + if (force_resize) breakpointCurrent = null + sliderUnbind() + sliderBind(force_resize) + } + + function sliderResize(force) { + const windowWidth = window.innerWidth + if (!sliderCheckBreakpoint() || (windowWidth === resizeLastWidth && !force)) + return + resizeLastWidth = windowWidth + const optionSlides = options.slides + if (typeof optionSlides === 'number') { + slides = null + length = optionSlides + } else { + slides = getElements(optionSlides, container) + length = slides ? slides.length : 0 + } + const dragSpeed = options.dragSpeed + touchMultiplicator = + typeof dragSpeed === 'function' ? dragSpeed : val => val * dragSpeed + width = isVertialSlider() ? container.offsetHeight : container.offsetWidth + slidesPerView = sliderGetSlidesPerView(options.slidesPerView) + spacing = clampValue(options.spacing, 0, width / (slidesPerView - 1) - 1) + width += spacing + origin = isCenterMode() + ? (width / 2 - width / slidesPerView / 2) / width + : 0 + slidesSetWidths() + + const currentIdx = + !sliderCreated || (optionsChanged && options.resetSlide) + ? options.initial + : trackCurrentIdx + trackSetPositionByIdx(isLoop() ? currentIdx : trackClampIndex(currentIdx)) + + if (isVertialSlider()) { + container.setAttribute(attributeVertical, true) + } + optionsChanged = false + } + + function sliderResizeFix(force) { + sliderResize() + setTimeout(sliderResize, 500) + setTimeout(sliderResize, 2000) + } + + function sliderUnbind() { + eventsRemove() + slidesRemoveStyles() + if (container && container.hasAttribute(attributeVertical)) + container.removeAttribute(attributeVertical) + hook('destroyed') + } + + function slidesSetPositions() { + if (!slides) return + slides.forEach((slide, idx) => { + const absoluteDistance = trackSlidePositions[idx].distance * width + const pos = + absoluteDistance - + idx * + (width / slidesPerView - + spacing / slidesPerView - + (spacing / slidesPerView) * (slidesPerView - 1)) + + const scale_size = 0.7 + const scale = 1 - (scale_size - scale_size * trackSlidePositions[idx].portion) + const x = isVertialSlider() ? 0 : pos + const y = isVertialSlider() ? pos : 0 + const transformString = `translate3d(${x}px, ${y}px, 0)` // scale(${scale})` + + slide.style.transform = transformString + slide.style['-webkit-transform'] = transformString + }) + } + + function slidesSetWidths() { + if (!slides) return + slides.forEach(slide => { + const style = `calc(${100 / slidesPerView}% - ${ + (spacing / slidesPerView) * (slidesPerView - 1) + }px)` + if (isVertialSlider()) { + slide.style['min-height'] = style + slide.style['max-height'] = style + } else { + slide.style['min-width'] = style + slide.style['max-width'] = style + } + }) + } + + function slidesRemoveStyles() { + if (!slides) return + let styles = ['transform', '-webkit-transform'] + styles = isVertialSlider + ? [...styles, 'min-height', 'max-height'] + : [...styles, 'min-width', 'max-width'] + slides.forEach(slide => { + styles.forEach(style => { + slide.style.removeProperty(style) + }) + }) + } + + function trackAdd(val, drag = true, timestamp = Date.now()) { + trackMeasure(val, timestamp) + if (drag) val = trackrubberband(val) + trackPosition += val + trackMove() + } + + function trackCalculateOffset(add) { + const trackLength = + (width * (length - 1 * (isCenterMode() ? 1 : slidesPerView))) / + slidesPerView + const position = trackPosition + add + return position > trackLength + ? position - trackLength + : position < 0 + ? position + : 0 + } + + function trackClampIndex(idx) { + return clampValue( + idx, + 0, + length - 1 - (isCenterMode() ? 0 : slidesPerView - 1) + ) + } + + function trackGetDetails() { + const trackProgressAbs = Math.abs(trackProgress) + const progress = trackPosition < 0 ? 1 - trackProgressAbs : trackProgressAbs + return { + direction: trackDirection, + progressTrack: progress, + progressSlides: (progress * length) / (length - 1), + positions: trackSlidePositions, + position: trackPosition, + speed: trackSpeed, + relativeSlide: ((trackCurrentIdx % length) + length) % length, + absoluteSlide: trackCurrentIdx, + size: length, + slidesPerView, + widthOrHeight: width, + } + } + + function trackGetIdx(idx, relative = false, nearest = false) { + return !isLoop() + ? trackClampIndex(idx) + : !relative + ? idx + : trackGetRelativeIdx(idx, nearest) + } + + function trackGetIdxDistance(idx) { + return -(-((width / slidesPerView) * idx) + trackPosition) + } + + function trackGetRelativeIdx(idx, nearest) { + idx = ((idx % length) + length) % length + const current = ((trackCurrentIdx % length) + length) % length + const left = current < idx ? -current - length + idx : -(current - idx) + const right = current > idx ? length - current + idx : idx - current + const add = nearest + ? Math.abs(left) <= right + ? left + : right + : idx < current + ? left + : right + return trackCurrentIdx + add + } + + function trackMeasure(val, timestamp) { + // todo - improve measurement - it could be better for ios + clearTimeout(trackMeasureTimeout) + const direction = Math.sign(val) + if (direction !== trackDirection) trackMeasureReset() + trackDirection = direction + trackMeasurePoints.push({ + distance: val, + time: timestamp, + }) + trackMeasureTimeout = setTimeout(() => { + trackMeasurePoints = [] + trackSpeed = 0 + }, 50) + trackMeasurePoints = trackMeasurePoints.slice(-6) + if (trackMeasurePoints.length <= 1 || trackDirection === 0) + return (trackSpeed = 0) + + const distance = trackMeasurePoints + .slice(0, -1) + .reduce((acc, next) => acc + next.distance, 0) + const end = trackMeasurePoints[trackMeasurePoints.length - 1].time + const start = trackMeasurePoints[0].time + trackSpeed = clampValue(distance / (end - start), -10, 10) + } + + function trackMeasureReset() { + trackMeasurePoints = [] + } + + // todo - option for not calculating slides that are not in sight + function trackMove() { + trackProgress = isLoop() + ? (trackPosition % ((width * length) / slidesPerView)) / + ((width * length) / slidesPerView) + : trackPosition / ((width * length) / slidesPerView) + + trackSetCurrentIdx() + const slidePositions = [] + for (let idx = 0; idx < length; idx++) { + let distance = + (((1 / length) * idx - + (trackProgress < 0 && isLoop() ? trackProgress + 1 : trackProgress)) * + length) / + slidesPerView + + origin + if (isLoop()) + distance += + distance > (length - 1) / slidesPerView + ? -(length / slidesPerView) + : distance < -(length / slidesPerView) + 1 + ? length / slidesPerView + : 0 + + const slideWidth = 1 / slidesPerView + const left = distance + slideWidth + const portion = + left < slideWidth + ? left / slideWidth + : left > 1 + ? 1 - ((left - 1) * slidesPerView) / 1 + : 1 + slidePositions.push({ + portion: portion < 0 || portion > 1 ? 0 : portion, + distance, + }) + } + trackSlidePositions = slidePositions + slidesSetPositions() + hook('move') + } + + function trackrubberband(add) { + if (isLoop()) return add + const offset = trackCalculateOffset(add) + if (!isRubberband()) return add - offset + if (offset === 0) return add + const easing = t => (1 - Math.abs(t)) * (1 - Math.abs(t)) + return add * easing(offset / width) + } + + function trackSetCurrentIdx() { + const new_idx = Math.round(trackPosition / (width / slidesPerView)) + if (new_idx === trackCurrentIdx) return + trackCurrentIdx = new_idx + hook('slideChanged') + } + + function trackSetPositionByIdx(idx) { + hook('beforeChange') + trackAdd(trackGetIdxDistance(idx), false) + hook('afterChange') + } + + const defaultOptions = { + centered: false, + breakpoints: null, + controls: true, + dragSpeed: 1, + friction: 0.0025, + loop: false, + initial: 0, + duration: 500, + preventEvent: 'data-keen-slider-pe', + slides: '.keen-slider__slide', + vertical: false, + resetSlide: false, + slidesPerView: 1, + spacing: 0, + mode: 'snap', + rubberband: true, + } + + const pubfuncs = { + controls: active => { + touchControls = active + }, + destroy: sliderUnbind, + refresh(options) { + sliderRebind(options, true) + }, + next() { + moveToIdx(trackCurrentIdx + 1, true) + }, + prev() { + moveToIdx(trackCurrentIdx - 1, true) + }, + moveToSlide(idx, duration) { + moveToIdx(idx, true, duration) + }, + moveToSlideRelative(idx, nearest = false, duration) { + moveToIdx(idx, true, duration, true, nearest) + }, + resize() { + sliderResize(true) + }, + details() { + return trackGetDetails() + }, + } + + sliderInit() + + return pubfuncs +} + +export default KeenSlider + +// helper functions + +function convertToArray(nodeList) { + return Array.prototype.slice.call(nodeList) +} + +function getElements(element, wrapper = document) { + return typeof element === 'function' + ? convertToArray(element()) + : typeof element === 'string' + ? convertToArray(wrapper.querySelectorAll(element)) + : element instanceof HTMLElement !== false + ? [element] + : element instanceof NodeList !== false + ? element + : [] +} + +function clampValue(value, min, max) { + return Math.min(Math.max(value, min), max) +} diff --git a/animism-align/frontend/app/utils/vendor/keen-slider/polyfills.js b/animism-align/frontend/app/utils/vendor/keen-slider/polyfills.js new file mode 100644 index 0000000..189223c --- /dev/null +++ b/animism-align/frontend/app/utils/vendor/keen-slider/polyfills.js @@ -0,0 +1,5 @@ +if (!Math.sign) { + Math.sign = function (x) { + return (x > 0) - (x < 0) || +x + } +}
\ No newline at end of file diff --git a/animism-align/frontend/app/utils/vendor/keen-slider/react.js b/animism-align/frontend/app/utils/vendor/keen-slider/react.js new file mode 100644 index 0000000..9da1b22 --- /dev/null +++ b/animism-align/frontend/app/utils/vendor/keen-slider/react.js @@ -0,0 +1,93 @@ +import KeenSlider from './keen-slider' +import { useEffect, useRef, useState } from 'react' + +export default KeenSlider + +export function useKeenSlider(options = {}) { + const ref = useRef() + const savedOptions = useRef() + + function checkOptions(options) { + if (!isEqual(savedOptions.current, options)) { + savedOptions.current = options + } + return savedOptions.current + } + + const [slider, setSlider] = useState(null) + + useEffect(() => { + const new_slider = new KeenSlider(ref.current, savedOptions.current) + setSlider(new_slider) + return () => { + new_slider.destroy() + } + }, [checkOptions(options)]) + return [ref, slider] +} + +/*! + * Check if two objects or arrays are equal + * (c) 2017 Chris Ferdinandi, MIT License, https://gomakethings.com + * @param {Object|Array} value The first object or array to compare + * @param {Object|Array} other The second object or array to compare + * @return {Boolean} Returns true if they're equal + */ +var isEqual = function (value, other) { + // Get the value type + var type = Object.prototype.toString.call(value) + + // If the two objects are not the same type, return false + if (type !== Object.prototype.toString.call(other)) return false + + // If items are not an object or array, return false + if (['[object Array]', '[object Object]'].indexOf(type) < 0) return false + + // Compare the length of the length of the two items + var valueLen = + type === '[object Array]' ? value.length : Object.keys(value).length + var otherLen = + type === '[object Array]' ? other.length : Object.keys(other).length + if (valueLen !== otherLen) return false + + // Compare two items + var compare = function (item1, item2) { + // Get the object type + var itemType = Object.prototype.toString.call(item1) + + // If an object or array, compare recursively + if (['[object Array]', '[object Object]'].indexOf(itemType) >= 0) { + if (!isEqual(item1, item2)) return false + } + + // Otherwise, do a simple comparison + else { + // If the two items are not the same type, return false + if (itemType !== Object.prototype.toString.call(item2)) return false + + // Else if it's a function, convert to a string and compare + // Otherwise, just compare + if (itemType === '[object Function]') { + if (item1.toString() !== item2.toString()) return false + } else { + if (item1 !== item2) return false + } + } + } + + // Compare properties + if (type === '[object Array]') { + for (var i = 0; i < valueLen; i++) { + if (compare(value[i], other[i]) === false) return false + } + } else { + for (var key in value) { + if (value.hasOwnProperty(key)) { + if (compare(value[key], other[key]) === false) return false + } + } + } + + // If nothing failed, return true + return true +} |
