Skip to content

Instantly share code, notes, and snippets.

@jesperlandberg
Created July 1, 2020 13:04
Show Gist options
  • Save jesperlandberg/1c47ccb9a0dd0fded1b4bb78aed7b87d to your computer and use it in GitHub Desktop.
Save jesperlandberg/1c47ccb9a0dd0fded1b4bb78aed7b87d to your computer and use it in GitHub Desktop.
import gsap from 'gsap'
import DrawSVGPlugin from '../lib/DrawSVGPlugin'
import store from '../store'
import { bindAll, qsa } from '../utils'
import { Events } from '../events'
gsap.registerPlugin(DrawSVGPlugin)
export default class {
constructor(el, opts = {}) {
bindAll(this, 'run', 'onDown', 'onMove', 'onUp')
this.el = el
this.opts = Object.assign({
speed: 1.5,
touchMultiplier: store.isDevice ? 2 : 1,
threshold: 50,
ease: 0.09
}, opts)
this.ui = {
area: this.el.closest('.js-draggable-area'),
items: qsa('.js-draggable-item', this.el)
}
this.state = {
target: 0,
current: {
a: 0,
aRounded: 0,
},
ease: {
a: this.opts.ease,
},
y: 0,
on: {
x: 0,
y: 0
},
off: 0,
progress: 0,
max: 0,
min: 0,
snap: {
points: null
},
flags: {
dragging: false
}
}
this.items = null
this.init()
}
init() {
return gsap.utils.pipe(
this.setup(),
this.on()
)
}
destroy() {
this.off()
this.state = null
this.items = null
this.opts = null
this.ui = null
}
on() {
Events.on('mousedown', this.onDown)
Events.on('mousemove', this.onMove)
Events.on('mouseup', this.onUp)
Events.on('tick', this.run)
Events.on('resize', this.resize)
}
off() {
Events.off('mousedown', this.onDown)
Events.off('mousemove', this.onMove)
Events.off('mouseup', this.onUp)
Events.off('tick', this.run)
Events.off('resize', this.resize)
}
setup() {
this.setBounds()
this.ui.area.style.touchAction = 'pan-y'
}
setBounds() {
const state = this.state
const { items } = this.ui
const {
width: wrapWidth,
left: wrapDiff
} = this.el.getBoundingClientRect()
state.snap.points = null
state.snap.points = []
this.items = null
this.items = []
for (let i = 0; i < items.length; i++) {
const el = items[i]
el.style.transform = 'translate3d(0, 0, 0)'
const ease = el.dataset.ease
const { left, right, width } = el.getBoundingClientRect()
// Push to snap array
state.snap.points.push(-(left - wrapDiff))
// Push to cache
this.items.push({
el, left, right, width,
ease: store.isPhone ? 'a' : (ease ? ease : 'a'),
out: false
})
}
// Set bounding
state.max = -(this.items[this.items.length - 1].right - wrapWidth - wrapDiff)
state.min = 0
}
calc() {
const state = this.state
state.current.a += (state.target - state.current.a) * state.ease.a
state.current.aRounded = Math.round(state.current.a * 100) / 100
state.progress = state.current.aRounded / state.max
this.tl && this.tl.progress(state.progress)
// Extended calcs here
}
run() {
this.calc()
this.transformItems()
}
transformItems() {
const { current: scroll, y } = this.state
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i]
const current = scroll[item.ease + 'Rounded']
const { isVisible, progress } = this.isVisible(item, current)
if (!item.out) {
const transform = `
translate3d(${current}px, ${i % 2 ? y : -y}px, 0)
rotate(0.001deg)
`
item.el.style.transform = transform
item.tl && item.tl.progress(progress)
}
if (isVisible || store.flags.resize) {
item.out = false
} else if (!item.out) {
item.out = true
}
}
}
isVisible({ left, right, width }, current) {
const { ww } = store.bounds
const threshold = this.opts.threshold
const start = left + current
const end = right + current
const isVisible = start < (threshold + ww) && end > -threshold
const progress = gsap.utils.clamp(0, 1, 1 - (current + left + width) / (ww + width))
return {
isVisible,
progress
}
}
clampTarget() {
const state = this.state
state.target = gsap.utils.clamp(state.max, 0, state.target)
}
onDown({ x, y, target }) {
if (!this.ui.area.contains(target)) return
const { flags, on } = this.state
flags.dragging = true
this.el.style.pointerEvents = 'none'
on.x = x
on.y = y
}
onUp() {
const state = this.state
state.flags.dragging = false
this.el.style.pointerEvents = 'auto'
this.opts.snap && this.snap()
state.off = state.target
}
snap() {
this.opts.snap && (this.state.target = gsap.utils.snap(this.state.snap.points, this.state.target))
}
onMove({ x, y, e }) {
const state = this.state
if (!state.flags.dragging) return
const { off, on } = state
const moveX = x - on.x
const moveY = y - on.y
if ((Math.abs(moveX) > Math.abs(moveY)) && e.cancelable) {
e.preventDefault()
e.stopPropagation()
}
state.target = off + (moveX * (this.opts.speed * this.opts.touchMultiplier))
this.clampTarget()
}
resize = () => {
this.setBounds()
this.childResize()
}
childResize() {
// extends here
}
}
import gsap from 'gsap'
import { qs } from '../utils'
import Draggable from './Draggable'
export default class extends Draggable {
setup() {
super.setup()
this.createTl()
}
createTl() {
this.tl && this.tl.kill()
const pagination = qs('.js-img-slider-pag')
if (pagination) {
this.tl = gsap.timeline({ paused: true })
.fromTo('.js-img-slider-pag', {
yPercent: 0
}, {
yPercent: -(100 - (100 / this.items.length)),
duration: 1,
ease: 'linear'
})
}
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i]
const content = qs('.js-slide-content', item.el)
const img = qs('.js-slide-img', content)
item.tl && item.tl.kill()
item.tl = gsap.timeline({
paused: true,
defaults: {
duration: 1,
ease: 'linear'
}
})
.fromTo(content, {
scale: 0.25,
alpha: 0.1,
xPercent: -20,
yPercent: 37.5
}, {
scale: 1,
alpha: .999,
xPercent: 0,
yPercent: 0
})
.to(content, {
scale: 0.25,
alpha: 0.1,
xPercent: 20,
yPercent: -37.5
})
.fromTo(img, {
xPercent: -20
}, {
xPercent: 20,
duration: 2
}, 0)
}
}
childResize() {
super.childResize()
this.createTl()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment