-
-
Save zurie/f816c1d6569d28b2eafa7fea71b7ed95 to your computer and use it in GitHub Desktop.
Vue plugin for scrollwizardy
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
/* | |
eslint no-underscore-dangle: 0, max-len: 0, prefer-destructuring: 0, no-nested-ternary: 0 | |
*/ | |
import TweenMax from 'gsap/TweenMax'; | |
import TimelineMax from 'gsap/TimelineMax'; | |
import * as ScrollWizardry from 'scrollwizardry'; | |
// import * as ScrollWizardry from '../../scrollwizardry/src'; | |
const { Controller, Scene } = ScrollWizardry; | |
const defaultOptions = { | |
addIndicators: false, | |
}; | |
let sceneManager; | |
class SceneManager { | |
constructor(options) { | |
this.options = options; | |
this._scenes = {}; | |
this._sceneObservers = {}; | |
if (typeof window === 'undefined') { | |
return; | |
} | |
this.controller = new Controller(options); | |
} | |
static getTargetElement(element, targetElement) { | |
if (targetElement) { | |
if (typeof targetElement === 'string') { | |
if (/^(parent)$/i.test(targetElement)) { | |
return element.parentNode; | |
} | |
return document.querySelector(targetElement); | |
} | |
return targetElement; | |
} | |
return element; | |
} | |
static parseString(string, triggerElement) { | |
let number; | |
const vh = /([0-9]+)vh$/.exec(string); | |
if (vh) { | |
number = () => window.innerHeight * (parseFloat(vh[1]) / 100); | |
} | |
const percent = /([0-9]+)%$/.exec(string); | |
if (percent) { | |
number = () => triggerElement.clientHeight * (parseFloat(percent[1]) / 100); | |
} | |
return number; | |
} | |
static mounted(element) { | |
return new Promise((resolve) => { | |
const observer = new MutationObserver(resolve); | |
observer.observe(element.parentNode, { childList: true }); | |
}); | |
} | |
get addIndicators() { | |
return this.options.addIndicators; | |
} | |
_notifySceneObservers(sceneId, ...args) { | |
args.push(sceneId); | |
this._sceneObservers[sceneId].forEach((fn) => { | |
fn(...args); | |
}); | |
} | |
addSceneObserver(sceneId, observer) { | |
if (this._scenes[sceneId]) { | |
observer(this._scenes[sceneId], sceneId); | |
return; | |
} | |
if (!this._sceneObservers[sceneId]) { | |
this._sceneObservers[sceneId] = []; | |
} | |
this._sceneObservers[sceneId].push(observer); | |
} | |
getSceneIds(sceneId) { | |
const defaultSceneId = Object.keys(this._scenes)[0] || 0; | |
if (Object.prototype.toString.call(sceneId) === '[object Array]') { | |
return sceneId; | |
} | |
return sceneId && sceneId.length ? [sceneId] : [defaultSceneId]; | |
} | |
getScene(sceneId) { | |
sceneId = sceneId || this.getSceneIds(sceneId)[0]; | |
return this._scenes[sceneId]; | |
} | |
setScene(sceneId, scene) { | |
this._scenes[sceneId] = scene; | |
if (this._sceneObservers[sceneId]) { | |
this._notifySceneObservers(sceneId, scene); | |
} | |
} | |
destroyScene(sceneId) { | |
const scene = this._scenes[sceneId]; | |
this.controller.removeScene(scene); | |
scene.destroy(true); | |
delete this._scenes[sceneId]; | |
} | |
removeSceneObservers(sceneId, observers = []) { | |
const observerIds = observers.map(observer => observer.id); | |
if (this._sceneObservers[sceneId]) { | |
this._sceneObservers[sceneId] = this._sceneObservers[sceneId].filter(({ id }) => { | |
if (observerIds.includes(id)) { | |
return false; | |
} | |
return true; | |
}); | |
} | |
} | |
} | |
const sceneDirective = { | |
inserted(el, binding) { | |
const options = binding.value; | |
const sceneId = sceneManager.getSceneIds(options.sceneId)[0]; | |
if (sceneManager.getScene(sceneId)) { | |
sceneManager.destroyScene(sceneId); | |
} | |
let scene; | |
let offset = options.offset !== undefined ? options.offset : 0; | |
let duration = options.duration !== undefined ? options.duration : 0; | |
const triggerElement = options.triggerElement ? options.triggerElement : el; | |
const triggerHook = options.triggerHook !== undefined ? options.triggerHook : 0.5; | |
if (typeof offset === 'string') { | |
offset = SceneManager.parseString(offset, triggerElement); | |
} | |
if (typeof offset === 'function') { | |
offset = offset(); | |
} | |
if (typeof duration === 'function') { | |
duration = duration.bind({ | |
scene, | |
triggerElement, | |
offset, | |
triggerHook: options.triggerHook, | |
}); | |
} | |
if (typeof duration === 'string') { | |
duration = SceneManager.parseString(duration, triggerElement); | |
} | |
scene = new Scene({ | |
offset, | |
duration, | |
triggerElement, | |
triggerHook, | |
}); | |
if (options.onEnter) { | |
scene.on('enter', options.onEnter.bind({ | |
scene, | |
triggerElement, | |
props: options.props, | |
})); | |
} | |
if (options.onLeave) { | |
scene.on('leave', options.onLeave.bind({ | |
scene, | |
triggerElement, | |
props: options.props, | |
})); | |
} | |
if (sceneManager.addIndicators) { | |
scene.addIndicators({ | |
name: sceneId, | |
}); | |
} | |
scene.addTo(sceneManager.controller); | |
sceneManager.setScene(sceneId, scene); | |
if (options.onInit) { | |
options.onInit({ | |
scene, | |
triggerElement, | |
props: options.props, | |
}); | |
} | |
}, | |
unbind(el, binding) { | |
const options = binding.value; | |
const sceneId = sceneManager.getSceneIds(options.sceneId)[0]; | |
sceneManager.destroyScene(sceneId); | |
}, | |
}; | |
const pinDirective = { | |
bind(el, binding) { | |
const options = binding.value; | |
el.$observers = []; | |
sceneManager.getSceneIds(options.sceneId).forEach((sceneId) => { | |
const observer = (scene) => { | |
scene.setPin(SceneManager.getTargetElement(el, options.targetElement)); | |
}; | |
observer.id = +new Date(); | |
el.$observers.push(observer); | |
sceneManager.addSceneObserver(sceneId, observer); | |
}); | |
}, | |
unbind(el, binding) { | |
const options = binding.value; | |
sceneManager.removeSceneObservers(options.sceneId, el.$observers); | |
delete el.$observers; | |
}, | |
}; | |
const classToggleDirective = { | |
bind(el, binding) { | |
const options = binding.value; | |
el.$observers = []; | |
sceneManager.getSceneIds(options.sceneId).forEach((sceneId, i) => { | |
let className = options.class || options.classes; | |
if (Object.prototype.toString.call(className) === '[object Array]') { | |
className = className[i]; | |
} | |
const observer = (scene) => { | |
scene.setClassToggle(SceneManager.getTargetElement(el, options.targetElement), className); | |
}; | |
observer.id = +new Date(); | |
el.$observers.push(observer); | |
sceneManager.addSceneObserver(sceneId, observer); | |
}); | |
}, | |
unbind(el, binding) { | |
const options = binding.value; | |
sceneManager.removeSceneObservers(options.sceneId, el.$observers); | |
delete el.$observers; | |
}, | |
}; | |
const tweenDirective = { | |
bind(el, binding) { | |
const options = binding.value; | |
el.$observers = []; | |
sceneManager.getSceneIds(options.sceneId).forEach((sceneId) => { | |
const duration = options.duration; | |
const fromVars = options.fromVars; | |
const toVars = options.toVars || options.vars; | |
const method = fromVars && toVars ? 'fromTo' : fromVars ? 'from' : 'to'; | |
const observer = (scene) => { | |
if (!scene.timeline) { | |
scene.timeline = new TimelineMax(); | |
} | |
const tween = TweenMax[method](SceneManager.getTargetElement(el, options.targetElement), duration || 1, fromVars || toVars, toVars); | |
scene.timeline.add([tween], 0, 'normal'); | |
scene.setTween(scene.timeline); | |
}; | |
observer.id = +new Date(); | |
el.$observers.push(observer); | |
sceneManager.addSceneObserver(sceneId, observer); | |
}); | |
}, | |
unbind(el, binding) { | |
const options = binding.value; | |
sceneManager.removeSceneObservers(options.sceneId, el.$observers); | |
delete el.$observers; | |
}, | |
}; | |
const VueScrollWizardryPlugin = (_Vue, options = defaultOptions) => { | |
sceneManager = new SceneManager(options); | |
_Vue.directive('sw-scene', sceneDirective); | |
_Vue.directive('sw-pin', pinDirective); | |
_Vue.directive('sw-class-toggle', classToggleDirective); | |
_Vue.directive('sw-tween', tweenDirective); | |
}; | |
export default VueScrollWizardryPlugin; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment