Skip to content

Instantly share code, notes, and snippets.

@jesperlandberg
Last active August 10, 2019 22:36
Show Gist options
  • Select an option

  • Save jesperlandberg/e636e4d2cf4412b7eadce9059f4650fe to your computer and use it in GitHub Desktop.

Select an option

Save jesperlandberg/e636e4d2cf4412b7eadce9059f4650fe to your computer and use it in GitHub Desktop.
import TweenMax from 'gsap'
import store from '../store'
import preload from './preload'
import math from './math'
import Pointer from './Pointer'
import VS from './VS'
import EventBus from './EventBus'
import { Events as GlobalResizeEvents } from './GlobalResize'
class GlobalRAF {
constructor() {
TweenMax.ticker.addEventListener('tick', this.tick)
this.state = {
target: 0,
current: 0,
ease: 0.1
}
this.addListeners()
}
tick = () => {
const state = this.state
if (store.isSmooth) {
state.current = math.lerp(state.current, state.target, state.ease)
if (state.current < .1) {
state.current = 0
}
} else {
state.current = state.target
}
EventBus.emit(GlobalRAF.events.TICK, {
target: state.target,
current: state.current,
})
}
clampTarget() {
this.state.target = Math.min(Math.max(this.state.target, 0), store.scrollHeight)
}
onEvent = (e) => {
Pointer.run()
this.state.target += e.deltaY * -1
this.clampTarget()
}
onScroll = () => {
this.state.target = window.scrollY
}
addListeners() {
if (store.isSmooth) {
this.vs = new VS()
this.vs.on(this.onEvent)
} else {
window.addEventListener('scroll', this.onScroll, { passive: true })
}
EventBus.on(GlobalResizeEvents.RESIZE, this.onResize)
}
}
GlobalRAF.events = {
TICK: 'TICK',
}
export default new GlobalRAF()
export const Events = GlobalRAF.events
import store from '../store'
import preload from '../utils/preload'
import EventBus from '../utils/EventBus'
import { Events as GlobalRAFEvents } from '../utils/GlobalRAF'
import { Events as GlobalResizeEvents } from '../utils/GlobalResize'
class Smooth {
constructor() {
this.el = store.scrollEl
this.ui = {
sections: document.querySelectorAll('.js-smooth-section')
}
this.state = {
total: this.ui.sections.length,
current: 0,
target: 0,
threshold: 100,
isResizing: false
}
this.init()
}
init() {
this.on()
}
on() {
this.setStyles()
this.getCache()
this.addListeners()
preload(this.onResize)
}
setStyles() {
store.body.classList.add('is-virtual-scroll')
}
run = ({ current }) => {
this.state.current = current
this.transformSections()
}
transformSections() {
const { total, current, isResizing } = this.state
for (let i = 0; i < total; i++) {
const data = this.sections[i]
const { el, bounds, speed } = data
const { isVisible, transform } = this.isVisible(bounds, speed)
if (isVisible || isResizing) {
Object.assign(data, { out: false })
el.style.transform = `translate3d(0, ${-transform}px, 0)`
} else if (!data.out) {
Object.assign(data, { out: true })
el.style.transform = `translate3d(0, ${-transform}px, 0)`
}
}
}
isVisible({ top, bottom, offset, parallaxOffset }, speed) {
const { current, threshold } = this.state
const translate = current * speed
const transform = translate - parallaxOffset
const start = (top + offset) - translate
const end = (bottom + offset) - translate
const isVisible = start < (threshold + store.height) && end > -threshold
return {
isVisible,
transform
}
}
getCache() {
this.getSections()
}
getSections() {
if (!this.ui.sections) return
this.sections = []
this.ui.sections.forEach((el) => {
el.style.transform = 'translate3d(0, 0, 0)'
const speed = el.dataset.speed || 1
const { top, bottom, height } = el.getBoundingClientRect()
const centering = ((store.height / 2) - (height / 2))
const parallaxOffset = top < store.height ? 0 : ((top - centering) * speed) - (top - centering)
const offset = (this.state.current * speed) + parallaxOffset
const state = {
el,
bounds: {
top,
bottom,
offset,
parallaxOffset
},
speed,
out: true,
}
this.sections.push(state)
})
}
onResize = () => {
this.state.isResizing = true
if (this.sections) {
this.sections.forEach(({ el, bounds, speed }) => {
el.style.transform = 'translate3d(0, 0, 0)'
const { top, bottom, height } = el.getBoundingClientRect()
const centering = ((store.height / 2) - (height / 2))
const parallaxOffset = top < store.height ? 0 : ((top - centering) * speed) - (top - centering)
const offset = (this.state.current * speed) + parallaxOffset
Object.assign(bounds, {
top,
bottom,
parallaxOffset,
offset
})
})
this.transformSections()
}
this.state.isResizing = false
}
addListeners() {
EventBus.on(GlobalRAFEvents.TICK, this.run)
EventBus.on(GlobalResizeEvents.RESIZE, this.onResize)
}
removeListeners() {
EventBus.off(GlobalRAFEvents.TICK, this.run)
EventBus.off(GlobalResizeEvents.RESIZE, this.onResize)
}
destroy() {
this.removeListeners()
this.ui = null
this.state = null
this.sections = null
}
}
export default Smooth
import store from '../store'
import EventBus from './EventBus'
const event_id = 'vs'
class VS {
constructor(opts = {}) {
this.el = window
this.opts = Object.assign({
mouseMultiplier: (["Win32", "Win64", "Windows", "WinCE"].indexOf(window.navigator.platform) !== -1) ? 0.7 : 0.45,
touchMultiplier: 3,
firefoxMultiplier: 90
}, opts)
this.event = {
y: 0,
deltaY: 0
}
this.touchY = null
}
emit(e) {
const event = this.event
event.y += event.deltaY
EventBus.emit(event_id, {
y: event.y,
deltaY: event.deltaY,
originalEvent: e
})
}
onWheel = (e) => {
const { firefoxMultiplier, mouseMultiplier } = this.opts
const event = this.event
event.deltaY = e.wheelDeltaY || e.deltaY * -1
if (store.isFirefox && e.deltaMode == 1) {
event.deltaY *= firefoxMultiplier
}
event.deltaY *= mouseMultiplier
this.emit(e)
}
onTouchStart = (e) => {
const touch = (e.targetTouches) ? e.targetTouches[0] : e
this.touchY = touch.pageY
}
onTouchMove = (e) => {
const { touchMultiplier } = this.options
const event = this.event
const touch = (e.targetTouches) ? e.targetTouches[0] : e
event.deltaY = (touch.pageY - this.touchY) * touchMultiplier
this.touchY = touch.pageY
this.emit(e)
}
addListeners() {
if (store.hasWheelEvent) {
this.el.addEventListener('wheel', this.onWheel, { passive: true })
}
}
removeListeners() {
if (store.hasWheelEvent) {
this.el.removeEventListener('wheel', this.onWheel, { passive: true })
}
}
on(callback) {
EventBus.on(event_id, callback)
this.addListeners()
}
off(callback) {
EventBus.off(event_id, callback)
this.removeListeners()
}
}
export default VS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment