UNPKG

react-spark-scroll

Version:
423 lines (357 loc) 12.4 kB
'use strict'; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var AnimationFrame = require('animation-frame'); var EventEmitter = require('events').EventEmitter; var _isArray = function _isArray(x) { return x instanceof Array; }; function _isObject(value) { var type = typeof value; return value != null && (type == 'object' || type == 'function'); } function sparkFactory(_ref4) { var animator = _ref4.animator; var formulas = _ref4.formulas; var actionProps = _ref4.actionProps; var setup = _ref4.setup; var invalidateAutomatically = _ref4.invalidateAutomatically; var allowAnimation = !!animator; var sparkFormulas = formulas ? _extends({}, _sparkFormulas, formulas) : _sparkFormulas; var sparkActionProps = actionProps ? _extends({}, _sparkActionProps, actionProps) : _sparkActionProps; var sparkSetup = setup ? _extends({}, _sparkSetup, setup) : _sparkSetup; var eventEmitter = new EventEmitter(); eventEmitter.setMaxListeners(0); var spark = function spark(element, proxyElementFn, timeline, options) { var callback = options.callback; var prevRatio = 0; var minScrollY = 0; var maxScrollY = 0; var sparkAnimator = allowAnimation && animator.instance(); var actor = sparkAnimator && sparkAnimator.addActor({ context: element }); var isAnimated = true; var actionFrames = []; var actionFrameIdx = -1; var y = 0; var prevy = 0; var scrollY = 0; var animationFrame = new AnimationFrame(); var updating = false; var data = null; var sparkData = null; var container = document.documentElement; var actionsUpdate = function actionsUpdate() { var a, actionProp, c, d, idx, o, prop, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3; d = y - prevy; if (d < 0 && actionFrameIdx >= 0) { idx = actionFrameIdx >= actionFrames.length ? actionFrameIdx - 1 : actionFrameIdx; while (idx >= 0 && y < actionFrames[idx]) { c = sparkData[actionFrames[idx]]; _ref = c.actions; for (a in _ref) { o = _ref[a]; _ref1 = o.props; for (_i = 0, _len = _ref1.length; _i < _len; _i++) { prop = _ref1[_i]; actionProp = sparkActionProps[prop]; if (actionProp.up) { actionProp.up.call(c, o); } } } actionFrameIdx = --idx; } } if (d >= 0 && actionFrameIdx < actionFrames.length) { idx = actionFrameIdx < 0 ? 0 : actionFrameIdx; while (idx < actionFrames.length && y > actionFrames[idx]) { c = sparkData[actionFrames[idx]]; _ref2 = c.actions; for (a in _ref2) { o = _ref2[a]; _ref3 = o.props; for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) { prop = _ref3[_j]; actionProp = sparkActionProps[prop]; if (actionProp.down) { actionProp.down.call(c, o); } } } actionFrameIdx = ++idx; } } prevy = y; updating = false; }; var update = function update() { y = scrollY; sparkAnimator.update(y); callback && doCallback(); actionsUpdate(); // sets updating = false }; var recalcMinMax = function recalcMinMax() { var scrY, idx = 0; for (scrY in sparkData) { scrY = ~ ~scrY; if (idx++) { if (scrY > maxScrollY) maxScrollY = scrY;else if (scrY < minScrollY) minScrollY = scrY; } else { maxScrollY = minScrollY = scrY; } } }; var doCallback = function doCallback() { var ratio = Math.max(0, Math.min((y - minScrollY) / (maxScrollY - minScrollY), 1)); if (ratio !== prevRatio) { callback(ratio); } prevRatio = ratio; }; var recalcFormulas = function recalcFormulas() { var changed, containerRect, keyFrame, kf, newScrY, rect, scrY; if (!sparkData) { parseData(); if (!sparkData) return; } changed = false; rect = proxyElementFn().getBoundingClientRect(); containerRect = container.getBoundingClientRect(); for (scrY in sparkData) { keyFrame = sparkData[scrY]; if (!keyFrame.formula) continue; newScrY = keyFrame.formula.fn(proxyElementFn(), container, rect, containerRect, keyFrame.formula.offset); if (newScrY !== ~ ~scrY) { changed = true; if (keyFrame.anims && allowAnimation) { actor.moveKeyframe(~ ~scrY, newScrY); } sparkData[newScrY] = keyFrame; delete sparkData[scrY]; } } if (changed) { if (callback) { recalcMinMax(); } actionFrames = []; for (scrY in sparkData) { kf = sparkData[scrY]; if (kf.actionCount) { actionFrames.push(~ ~scrY); } } actionFrames.sort(function (a, b) { return a > b; }); onScroll(); } }; var parseData = function parseData(data) { var c, ease; var k, o, parts, rect, scrY, formula; if (!data) { return; } data = _extends({}, data); if (allowAnimation) { actor.removeAllKeyframes(); } var elmEase = data.ease || 'linear'; delete data.ease; var animCount = 0; sparkData = {}; actionFrames = []; rect = proxyElementFn().getBoundingClientRect(); var containerRect = container.getBoundingClientRect(); for (scrY in data) { var keyFrame = data[scrY] || {}; keyFrame = _extends({}, keyFrame); var actionCount = 0; c = scrY.charCodeAt(0); if (c < 48 || c > 57) { parts = scrY.match(/^(\w+)(.*)$/); formula = { fn: sparkFormulas[parts[1]], offset: ~ ~parts[2] }; scrY = formula.fn(proxyElementFn(), container, rect, containerRect, formula.offset); if (sparkData[scrY]) { if (sparkSetup.debug) { console.log("warning: spark-scroll failed to calculate formulas", data); } sparkData = null; return; } } ease = {}; var kfEase = elmEase; if (keyFrame.ease != null) { if (_isObject(keyFrame.ease)) { ease = keyFrame.ease; } else { kfEase = keyFrame.ease; } delete keyFrame.ease; } for (k in keyFrame) { var v = keyFrame[k]; var ksplit = k.split(','); if (sparkActionProps[ksplit[0]]) { keyFrame.actions || (keyFrame.actions = {}); keyFrame.actions[k] = { props: ksplit, val: v }; delete keyFrame[k]; actionCount++; } else { keyFrame.anims || (keyFrame.anims = {}); if (!_isArray(v)) { v = [v, kfEase]; } o = {}; o[k] = v[1]; Object.assign(ease, o); keyFrame.anims[k] = v[0]; delete keyFrame[k]; } } if (keyFrame.anims && allowAnimation) { actor.keyframe(scrY, keyFrame.anims, ease); animCount++; } keyFrame.formula = formula; keyFrame.element = element; keyFrame.actionCount = actionCount; sparkData[scrY] = keyFrame; if (actionCount) { actionFrames.push(~ ~scrY); } } isAnimated = !!animCount; if (isAnimated) { actor.finishedAddingKeyframes && actor.finishedAddingKeyframes(); } actionFrames.sort(function (a, b) { return a > b; }); if (callback) { recalcMinMax(); } y = prevy = scrollY = window.pageYOffset; if (isAnimated) { update(); } return actionsUpdate(); }; var nonAnimatedUpdate = function nonAnimatedUpdate() { if (callback) { doCallback(); } return actionsUpdate(); }; var onScroll = function onScroll() { scrollY = window.pageYOffset; if (!updating) { updating = true; if (isAnimated) { return animationFrame.request(update); } else { y = scrollY; return animationFrame.request(nonAnimatedUpdate); } } }; var onInvalidate = debounce(recalcFormulas, 100); window.addEventListener('scroll', onScroll, false); window.addEventListener('resize', onInvalidate, false); eventEmitter.on('invalidate', onInvalidate); spark.cleanup = function () { window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onInvalidate); eventEmitter.removeListener('invalidate', onInvalidate); }; // delay parse a frame to allow proxy to render animationFrame.request(parseData.bind(null, timeline)); }; spark.invalidate = function () { return eventEmitter.emit('invalidate'); }; var invalidationInterval; spark.enableInvalidationInterval = function (delay) { delay = delay || 1000; clearInterval(invalidationInterval); invalidationInterval = setInterval(spark.invalidate, delay); }; spark.disableInvalidationInterval = function (delay) { clearInterval(invalidationInterval); }; if (invalidateAutomatically) { spark.enableInvalidationInterval(invalidateAutomatically > 1 ? invalidateAutomatically : null); } return spark; } function debounce(fn, wait) { var t; return function () { clearTimeout(t); t = setTimeout(fn, wait); }; } var _sparkSetup = { debug: true }; var _sparkFormulas = { // top of the element hits the top of the viewport topTop: function topTop(element, container, rect, containerRect, offset) { return ~ ~(rect.top - containerRect.top + offset); }, // top of the element hits the center of the viewport topCenter: function topCenter(element, container, rect, containerRect, offset) { return ~ ~(rect.top - containerRect.top - container.clientHeight / 2 + offset); }, // top of the element hits the bottom of the viewport topBottom: function topBottom(element, container, rect, containerRect, offset) { return ~ ~(rect.top - containerRect.top - container.clientHeight + offset); }, // center of the element hits the top of the viewport centerTop: function centerTop(element, container, rect, containerRect, offset) { return ~ ~(rect.top + rect.height / 2 - containerRect.top + offset); }, // center of the element hits the center of the viewport centerCenter: function centerCenter(element, container, rect, containerRect, offset) { return ~ ~(rect.top + rect.height / 2 - containerRect.top - container.clientHeight / 2 + offset); }, // center of the element hits the bottom of the viewport centerBottom: function centerBottom(element, container, rect, containerRect, offset) { return ~ ~(rect.top + rect.height / 2 - containerRect.top - container.clientHeight + offset); }, // bottom of the element hits the top of the viewport bottomTop: function bottomTop(element, container, rect, containerRect, offset) { return ~ ~(rect.bottom - containerRect.top + offset); }, // bottom of the element hits the bottom of the viewport bottomBottom: function bottomBottom(element, container, rect, containerRect, offset) { return ~ ~(rect.bottom - containerRect.top - container.clientHeight + offset); }, // bottom of the element hits the center of the viewport bottomCenter: function bottomCenter(element, container, rect, containerRect, offset) { return ~ ~(rect.bottom - containerRect.top - container.clientHeight / 2 + offset); } }; var _sparkActionProps = { onDown: { down: function down(o) { return o.val('onDown', this, o); } }, onUp: { up: function up(o) { return o.val('onUp', this, o); } } }; module.exports = sparkFactory;