Skip to content

Instantly share code, notes, and snippets.

@codenamezjames
Created January 13, 2020 08:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save codenamezjames/e7e6acf6d5135a571aedcc59ff02ba58 to your computer and use it in GitHub Desktop.
Save codenamezjames/e7e6acf6d5135a571aedcc59ff02ba58 to your computer and use it in GitHub Desktop.
<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