Created
January 13, 2020 08:04
-
-
Save codenamezjames/e7e6acf6d5135a571aedcc59ff02ba58 to your computer and use it in GitHub Desktop.
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
<template> | |
<div> | |
<div class="row items-center"> | |
<div class="col-auto" style="align-self: stretch;"> | |
<q-btn flat class="full-height q-py-lg" size="sm" icon="fas fa-chevron-left" @click="move(-col)" aria-label="previous slide"/> | |
</div> | |
<div class="col"> | |
<div :class="{'slider-loading': loading, slider: !loading}" ref="slideWrapper" :style="currentSlideHeightStyle"> | |
<div | |
:class="{ | |
one:col===1, | |
two:col===2, | |
three:col===3, | |
four:col===4, | |
five:col===5, | |
six:col===6, | |
seven:col===7, | |
eight:col===8, | |
nine:col===9, | |
ten:col===10, | |
eleven:col===11, | |
twelve:col===12 | |
}" | |
class="flex items-center" | |
style="cursor: grab;" | |
ref="slides" | |
:style="`transform:translateX(${xPosition}px);`" | |
v-for="(slide,index) in slides" | |
:key="index" | |
v-touch-pan.horizontal.prevent.mouse="interaction"> | |
<component :is="slide.name" :options="slide.options" class="slide-inner full-width full-height"/> | |
</div> | |
</div> | |
</div> | |
<div class="col-auto" style="align-self: stretch;"> | |
<q-btn flat class="full-height q-py-lg" size="sm" icon="fas fa-chevron-right" @click="move(col)" aria-label="next slide"/> | |
</div> | |
</div> | |
<div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import { dom } from 'quasar' | |
const { width, height } = dom | |
const { exp, round, random } = Math | |
import take from 'lodash/take' | |
import takeRight from 'lodash/takeRight' | |
import max from 'lodash/max' | |
import isUndefined from 'lodash/isUndefined' | |
import throttle from 'lodash/throttle' | |
import { tween } from 'shifty' | |
const defined = (...args) => !isUndefined(...args) | |
export default { | |
computed: { | |
col () { | |
if (process.env.CLIENT) { | |
if (defined(this.options.xlCol) && this.windowWidth > 1919) return this.options.xl | |
if (defined(this.options.lgCol) && this.windowWidth > 1439) return this.options.lgCol | |
if (defined(this.options.mdCol) && this.windowWidth > 1023) return this.options.mdCol | |
if (defined(this.options.smCol) && this.windowWidth > 599) return this.options.smCol | |
if (defined(this.options.xsCol) && this.windowWidth < 600) return this.options.xsCol | |
} | |
return this.options.col || 3 | |
} | |
}, | |
data () { | |
return { | |
// Settings | |
scrollDecay: 100, | |
snappiness: 2, | |
throwWeight: 0.2, // lower is heavier | |
// Data | |
windowWidth: 1000, | |
slides: this.options.slides, | |
currentSlideHeightStyle: 'height: auto', | |
currentSlideHeight: 0, | |
slideIndex: 0, | |
delta: 0, | |
target: 0, | |
amplitude: 0, | |
now: 0, | |
elapsed: 0, | |
timestamp: 0, | |
velocity: 0, | |
slidesWidth: 0, | |
xPosition: 0, | |
loading: true | |
} | |
}, | |
destroyed () { | |
window.removeEventListener('resize', this.calcColStuff) | |
window.removeEventListener('resize', this.resizeSlider) | |
window.removeEventListener('resize', this.setWindowWidth) | |
}, | |
mounted () { | |
this.setSlideHeight = throttle(this.setSlideHeight, 100) | |
this.calcColStuff = throttle(this.calcColStuff, 100) | |
this.resizeSlider = throttle(this.resizeSlider, 100) | |
this.setWindowWidth = throttle(this.setWindowWidth, 100) | |
window.addEventListener('resize', this.calcColStuff) | |
window.addEventListener('resize', this.resizeSlider) | |
window.addEventListener('resize', this.setWindowWidth) | |
this.setSlideHeight(true) | |
this.setWindowWidth() | |
this.$nextTick(() => { this.calcColStuff() }) | |
this.loading = false | |
}, | |
methods: { | |
round, | |
random, | |
calcColStuff () { | |
this.snap = width(this.$refs.slides[0]) | |
this.slidesWidth = this.$refs.slides.reduce((l, c) => l + width(c), 0) | |
this.xPosition = this.snap * -this.col | |
this.prepareSlides() | |
}, | |
prepareSlides () { | |
this.slides = [ ...takeRight(this.options.slides, this.col), ...this.options.slides, ...take(this.options.slides, this.col) ] | |
}, | |
setWindowWidth () { | |
this.windowWidth = width(window) | |
}, | |
setSlideHeight (noTween) { | |
let els = this.$refs.slides.slice(this.slideIndex + this.col, (this.col * 2) + this.slideIndex) | |
const heights = els.map(el => { | |
if (el && el.childNodes) { | |
el = el.$el ? el.$el : el | |
return Array.from(el.childNodes).reduce((l, e) => e.nodeType === 8 ? l : l + height(e), 0) | |
} | |
return 0 | |
}) | |
const newSlideHeight = max(heights) | |
if (newSlideHeight === this.currentSlideHeight || this.currentSlideHeight === 0) return | |
if (noTween) { | |
this.currentSlideHeightStyle = `height: ${newSlideHeight}px` | |
} else { | |
tween({ | |
from: { y: this.currentSlideHeight }, | |
to: { y: newSlideHeight }, | |
duration: 300, | |
easing: 'easeOutQuad', | |
step: state => { this.currentSlideHeightStyle = `height: ${state.y}px` } | |
}) | |
} | |
this.currentSlideHeight = newSlideHeight | |
}, | |
resizeSlider () { | |
this.snap = width(this.$refs.slides[0]) | |
this.slidesWidth = this.$refs.slides.reduce((l, c) => l + width(c), 0) - this.snap * (this.col * 2) | |
this.xPosition = this.snap * ((-this.col) - this.slideIndex) | |
}, | |
move (slides) { | |
this.amplitude = -(this.snap * slides) | |
this.target = this.xPosition - (this.snap * slides) | |
this.target = round(this.target / this.snap) * this.snap | |
this.timestamp = Date.now() | |
requestAnimationFrame(this.autoScroll) | |
}, | |
interactStart () { | |
this.velocity = 0 | |
this.amplitude = 0 | |
this.timestamp = Date.now() | |
}, | |
interaction ({ isFirst, isFinal, delta }) { | |
this.delta = delta | |
if (isFirst) return this.interactStart() | |
if (isFinal) return this.interactEnd() | |
this.updatePos(this.xPosition + this.delta.x) | |
const now = Date.now() | |
this.elapsed = now - this.timestamp | |
this.timestamp = now | |
const v = 1000 * delta.x / (1 + this.elapsed) | |
this.velocity = 0.8 * v + 0.2 * this.velocity | |
}, | |
interactEnd () { | |
// I don't know what this does | |
// if (this.velocity > 0.1 || this.velocity < -0.1) { | |
this.amplitude = this.throwWeight * this.velocity | |
this.target = round(this.xPosition + this.amplitude) | |
// } | |
this.target = round(this.target / this.snap) * this.snap | |
this.amplitude = this.target - this.xPosition | |
this.timestamp = Date.now() | |
requestAnimationFrame(this.autoScroll) | |
}, | |
autoScroll () { | |
if (this.amplitude) { | |
this.elapsed = Date.now() - this.timestamp | |
const deltaX = -this.amplitude * exp(-this.elapsed / this.scrollDecay) | |
if (deltaX > this.snappiness || deltaX < -this.snappiness) { | |
this.updatePos(+this.target + deltaX) | |
requestAnimationFrame(this.autoScroll) | |
} else { | |
this.updatePos(+this.target) | |
} | |
} | |
}, | |
updatePos (x) { | |
this.xPosition = x | |
this.slideIndex = round(this.xPosition / (-this.snap)) - this.col | |
this.setSlideHeight() | |
if (this.slidesWidth + x <= 0) { | |
this.xPosition = 0 | |
this.amplitude = this.amplitude - this.slidesWidth | |
this.target = this.target + this.slidesWidth | |
} | |
if (x >= 0) { | |
this.xPosition = -this.slidesWidth | |
this.amplitude = this.amplitude + this.slidesWidth | |
this.target = this.target - this.slidesWidth | |
} | |
} | |
}, | |
props: { | |
options: Object | |
}, | |
watch: { | |
col () { | |
this.$nextTick(() => { this.prepareSlides() }) | |
} | |
} | |
} | |
</script> | |
<style lang="stylus" scoped> | |
.slider-loading | |
display flex | |
overflow auto | |
overflow-scrolling touch | |
.slider | |
display flex | |
overflow hidden | |
overflow-scrolling hidden | |
.slider-loading > div, .slider > div | |
flex-shrink 0 | |
min-height 100px | |
&.one | |
width 100% | |
&.two | |
width 50% | |
&.three | |
width 33.33334% | |
&.four | |
width 25% | |
&.five | |
width 20% | |
&.six | |
width 16.66667% | |
&.seven | |
width 14.28571% | |
&.eight | |
width 12.5% | |
&.nine | |
width 11.11111% | |
&.ten | |
width 10% | |
&.eleven | |
width 9.09090% | |
&.twelve | |
width 8.33334% | |
.slider-loading .slide-inner, .slider .slide-inner | |
background black | |
color white | |
margin-left 1px | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment