Created
August 5, 2019 12:14
-
-
Save jesperlandberg/094a0a91b9619439dbe579ba7123be91 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| import store from '../store' | |
| import math from '../utils/math' | |
| import EventBus from '../utils/EventBus' | |
| import { Events as GlobalRAFEvents } from '../utils/GlobalRAF' | |
| import { Events as GlobalResizeEvents } from '../utils/GlobalResize' | |
| class Parallax { | |
| constructor(elems) { | |
| this.elems = elems | |
| this.cache = null | |
| this.state = { | |
| threshold: 100, | |
| isResizing: false, | |
| } | |
| this.init() | |
| } | |
| run = ({ current }) => { | |
| this.state.current = current | |
| this.animateElems() | |
| } | |
| animateElems() { | |
| this.cache.forEach((cache) => { | |
| const { height, top, bottom, tl, duration } = cache | |
| const { isVisible, start, end } = this.isVisible(top, bottom) | |
| if (isVisible || this.state.isResizing) { | |
| const { progress } = this.intersectRatio(height, start, end, duration) | |
| tl.progress(progress) | |
| } | |
| }) | |
| } | |
| isVisible(top, bottom) { | |
| const { current } = this.state | |
| const start = top - current | |
| const end = bottom - current | |
| const isVisible = start < store.height && end > 0 | |
| return { | |
| isVisible, | |
| start, | |
| end | |
| } | |
| } | |
| intersectRatio = (height, top, bottom, duration) => { | |
| let progress | |
| const start = top - store.height | |
| const end = (store.height + bottom + height) * duration | |
| progress = Math.abs(start / end) | |
| progress = math.norm(progress, 0, 1) | |
| return { | |
| progress | |
| } | |
| } | |
| getCache() { | |
| if (this.elems) { | |
| this.cache = [] | |
| this.elems.forEach(el => { | |
| if ((el.dataset.animateMobile === undefined && store.isDevice) || | |
| (el.dataset.animateFirefox === undefined && store.isFirefox)) return | |
| const tl = new TimelineLite({ paused: true }) | |
| const from = JSON.parse(el.dataset.from) | |
| const to = {...JSON.parse(el.dataset.to), ...{ ease: Linear.easeNone }} | |
| tl.fromTo(el, 1, from, to) | |
| tl.progress(1) | |
| const { top, bottom, height } = el.getBoundingClientRect() | |
| tl.progress(0) | |
| this.cache.push({ | |
| el: el, | |
| tl: tl, | |
| top: top > store.height ? top : store.height, | |
| bottom: bottom + (store.height / 2), | |
| height: height, | |
| duration: el.dataset.duration || 1, | |
| }) | |
| }) | |
| } | |
| } | |
| updateCache() { | |
| this.elems.forEach(elem => { | |
| const { top, bottom } = elem.getBoundingClientRect() | |
| Object.assign(elem, { | |
| top: top > store.height ? top : store.height, | |
| bottom: bottom, | |
| height: bottom - top, | |
| }) | |
| }) | |
| } | |
| 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) | |
| } | |
| onResize = () => { | |
| this.state.isResizing = true | |
| this.updateCache() | |
| this.state.isResizing = false | |
| } | |
| destroy() { | |
| this.removeListeners() | |
| this.cache = null | |
| this.elems = null | |
| this.state = null | |
| } | |
| init() { | |
| this.getCache() | |
| this.addListeners() | |
| } | |
| } | |
| export default Parallax |
This file contains hidden or 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
| import store from '../store' | |
| import math from '../utils/math' | |
| import EventBus from '../utils/EventBus' | |
| import { Events as GlobalRAFEvents } from '../utils/GlobalRAF' | |
| import { Events as GlobalResizeEvents } from '../utils/GlobalResize' | |
| class P { | |
| constructor(elems) { | |
| this.elems = elems || document.querySelectorAll('[data-parallax]') | |
| this.cache = null | |
| this.state = { | |
| total: this.elems.length, | |
| isResizing: false, | |
| } | |
| this.init() | |
| } | |
| run = ({ current }) => { | |
| this.state.current = current | |
| this.loopElems() | |
| } | |
| loopElems() { | |
| this.cache.forEach(this.animateElem) | |
| } | |
| animateElem = ({ | |
| el, | |
| y, scale, opacity, rotation, | |
| isTop, isBottom, | |
| top, bottom, height, | |
| ... cache | |
| }) => { | |
| const { | |
| isVisible, | |
| start, | |
| end | |
| } = this.isVisible(top, bottom) | |
| if (isVisible || !cache.out) { | |
| const progress = this.intersectRatio(height, start, end, isTop, isBottom) | |
| let yTransform = y ? `translateY(${(y[1] * progress) - y[0]}px)` : '' | |
| let scaleTransform = scale ? `scale(${(scale[1] * progress) + scale[0]})` : '' | |
| let rotationTransform = rotation ? `rotate(${(rotation[1] * progress)}deg)` : '' | |
| el.style.transform = ` | |
| ${yTransform} | |
| ${scaleTransform} | |
| ${rotationTransform} | |
| translate3d(0, 0, 0) | |
| ` | |
| if (opacity) { | |
| el.style.opacity = opacity - progress | |
| } | |
| if (!cache.out) { | |
| cache.out = false | |
| } | |
| } else if (cache.out) { | |
| cache.out = true | |
| } | |
| } | |
| isVisible(top, bottom) { | |
| const { current } = this.state | |
| const start = top - current | |
| const end = bottom - current | |
| const isVisible = start < store.height && end > 0 | |
| return { | |
| isVisible, | |
| start, | |
| end | |
| } | |
| } | |
| intersectRatio = (height, start, end, isTop, isBottom) => { | |
| const proogressStart = start - store.height | |
| let progress | |
| let progressEnd | |
| if (isTop) { | |
| progressEnd = store.height - (store.height - height) + end | |
| } else if (isBottom) { | |
| progressEnd = end + store.height - start | |
| } else { | |
| progressEnd = end + store.height + height | |
| } | |
| progress = Math.abs(proogressStart / progressEnd) | |
| progress = math.norm(progress, 0, 1) | |
| return progress | |
| } | |
| getCache() { | |
| if (this.elems) { | |
| this.cache = [] | |
| this.elems.forEach((el, index) => { | |
| if ((el.dataset.parallaxMobile === undefined && store.isDevice) || | |
| (el.dataset.parallaxNoFirefox != undefined && store.isFirefox)) return | |
| const rect = el.getBoundingClientRect() | |
| const cache = {} | |
| cache.el = el | |
| cache.index = index | |
| cache.out = true | |
| let offsetTop = 0 | |
| let offsetBottom = 0 | |
| const object = JSON.parse(el.dataset.parallax) | |
| const objectEntries = Object.entries(object) | |
| // Set keys and props | |
| for (const [key, value] of objectEntries) { | |
| const props = JSON.parse(value) | |
| if (key === 'yPercent') { | |
| offsetTop = Math.abs(rect.height * props[0]) | |
| offsetBottom = rect.height * props[1] | |
| const start = offsetTop | |
| const end = rect.height * props[1] + Math.abs(start) | |
| cache.y = [start, end] | |
| } else if (key === 'scale') { | |
| const start = props[0] | |
| const end = props[1] - start | |
| cache.scale = [start, end] | |
| } else if (key === 'opacity') { | |
| const start = props[0] | |
| const end = props[1] | |
| cache.opacity = [start, end] | |
| } else if (key === 'rotation') { | |
| const start = props[0] | |
| const end = props[1] | |
| cache.rotation = [start, end] | |
| } | |
| } | |
| cache.isBottom = rect.bottom >= (store.scrollHeight) | |
| cache.isTop = rect.top < store.height | |
| // Top | |
| if (cache.isTop) { | |
| cache.top = store.height + offsetTop | |
| } else { | |
| cache.top = rect.top - offsetTop | |
| } | |
| // Bottom | |
| if (cache.isTop) { | |
| cache.bottom = rect.bottom | |
| } else if (cache.isBottom && !cache.isTop) { | |
| cache.bottom = rect.bottom - store.height | |
| } else { | |
| cache.bottom = rect.bottom + offsetBottom | |
| } | |
| // Height | |
| if (cache.isBottom) { | |
| cache.height = 0 | |
| } else { | |
| cache.height = rect.height | |
| } | |
| el.style.willChange = 'transform' | |
| this.cache.push(cache) | |
| }) | |
| } | |
| } | |
| 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) | |
| } | |
| onResize = () => { | |
| this.state.isResizing = true | |
| this.state.isResizing = false | |
| } | |
| destroy() { | |
| this.removeListeners() | |
| this.cache = null | |
| this.elems = null | |
| this.state = null | |
| } | |
| init() { | |
| this.getCache() | |
| if (this.cache.length != 0) this.addListeners() | |
| } | |
| } | |
| export default P |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment