1174 lines
30 KiB
JavaScript
1174 lines
30 KiB
JavaScript
/*! @license ScrollReveal v4.0.1
|
||
|
||
Copyright 2018 Fisssion LLC.
|
||
|
||
Licensed under the GNU General Public License 3.0 for
|
||
compatible open source projects and non-commercial use.
|
||
|
||
For commercial sites, themes, projects, and applications,
|
||
keep your source code private/proprietary by purchasing
|
||
a commercial license from https://scrollrevealjs.org/
|
||
*/
|
||
import $ from 'tealight';
|
||
import { parse, multiply, rotateX, rotateY, rotateZ, scale, translateX, translateY } from 'rematrix';
|
||
import raf from 'miniraf';
|
||
|
||
var defaults = {
|
||
delay: 0,
|
||
distance: '0',
|
||
duration: 600,
|
||
easing: 'cubic-bezier(0.5, 0, 0, 1)',
|
||
interval: 0,
|
||
opacity: 0,
|
||
origin: 'bottom',
|
||
rotate: {
|
||
x: 0,
|
||
y: 0,
|
||
z: 0
|
||
},
|
||
scale: 1,
|
||
cleanup: true,
|
||
container: document.documentElement,
|
||
desktop: true,
|
||
mobile: true,
|
||
reset: false,
|
||
useDelay: 'always',
|
||
viewFactor: 0.0,
|
||
viewOffset: {
|
||
top: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
left: 0
|
||
},
|
||
afterReset: function afterReset() {},
|
||
afterReveal: function afterReveal() {},
|
||
beforeReset: function beforeReset() {},
|
||
beforeReveal: function beforeReveal() {}
|
||
}
|
||
|
||
function failure() {
|
||
var root = document.documentElement;
|
||
|
||
root.classList.remove('sr');
|
||
|
||
return {
|
||
clean: function clean() {},
|
||
destroy: function destroy() {},
|
||
reveal: function reveal() {},
|
||
sync: function sync() {},
|
||
get noop() {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
|
||
function success() {
|
||
var html = document.documentElement;
|
||
var body = document.body;
|
||
|
||
html.classList.add('sr');
|
||
|
||
if (body) {
|
||
body.style.height = '100%';
|
||
} else {
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
body.style.height = '100%';
|
||
});
|
||
}
|
||
}
|
||
|
||
var mount = { success: success, failure: failure }
|
||
|
||
function isObject(x) {
|
||
return (
|
||
x !== null &&
|
||
x instanceof Object &&
|
||
(x.constructor === Object ||
|
||
Object.prototype.toString.call(x) === '[object Object]')
|
||
)
|
||
}
|
||
|
||
function each(collection, callback) {
|
||
if (isObject(collection)) {
|
||
var keys = Object.keys(collection);
|
||
return keys.forEach(function (key) { return callback(collection[key], key, collection); })
|
||
}
|
||
if (collection instanceof Array) {
|
||
return collection.forEach(function (item, i) { return callback(item, i, collection); })
|
||
}
|
||
throw new TypeError('Expected either an array or object literal.')
|
||
}
|
||
|
||
function logger(message) {
|
||
var details = [], len = arguments.length - 1;
|
||
while ( len-- > 0 ) details[ len ] = arguments[ len + 1 ];
|
||
|
||
if (this.constructor.debug && console) {
|
||
var report = "%cScrollReveal: " + message;
|
||
details.forEach(function (detail) { return (report += "\n — " + detail); });
|
||
console.log(report, 'color: #ea654b;'); // eslint-disable-line no-console
|
||
}
|
||
}
|
||
|
||
function rinse() {
|
||
var this$1 = this;
|
||
|
||
var struct = function () { return ({
|
||
active: [],
|
||
stale: []
|
||
}); };
|
||
|
||
var elementIds = struct();
|
||
var sequenceIds = struct();
|
||
var containerIds = struct();
|
||
|
||
/**
|
||
* Take stock of active element IDs.
|
||
*/
|
||
try {
|
||
each($('[data-sr-id]'), function (node) {
|
||
var id = parseInt(node.getAttribute('data-sr-id'));
|
||
elementIds.active.push(id);
|
||
});
|
||
} catch (e) {
|
||
throw e
|
||
}
|
||
/**
|
||
* Destroy stale elements.
|
||
*/
|
||
each(this.store.elements, function (element) {
|
||
if (elementIds.active.indexOf(element.id) === -1) {
|
||
elementIds.stale.push(element.id);
|
||
}
|
||
});
|
||
|
||
each(elementIds.stale, function (staleId) { return delete this$1.store.elements[staleId]; });
|
||
|
||
/**
|
||
* Take stock of active container and sequence IDs.
|
||
*/
|
||
each(this.store.elements, function (element) {
|
||
if (containerIds.active.indexOf(element.containerId) === -1) {
|
||
containerIds.active.push(element.containerId);
|
||
}
|
||
if (element.hasOwnProperty('sequence')) {
|
||
if (sequenceIds.active.indexOf(element.sequence.id) === -1) {
|
||
sequenceIds.active.push(element.sequence.id);
|
||
}
|
||
}
|
||
});
|
||
|
||
/**
|
||
* Destroy stale containers.
|
||
*/
|
||
each(this.store.containers, function (container) {
|
||
if (containerIds.active.indexOf(container.id) === -1) {
|
||
containerIds.stale.push(container.id);
|
||
}
|
||
});
|
||
|
||
each(containerIds.stale, function (staleId) {
|
||
var stale = this$1.store.containers[staleId].node;
|
||
stale.removeEventListener('scroll', this$1.delegate);
|
||
stale.removeEventListener('resize', this$1.delegate);
|
||
delete this$1.store.containers[staleId];
|
||
});
|
||
|
||
/**
|
||
* Destroy stale sequences.
|
||
*/
|
||
each(this.store.sequences, function (sequence) {
|
||
if (sequenceIds.active.indexOf(sequence.id) === -1) {
|
||
sequenceIds.stale.push(sequence.id);
|
||
}
|
||
});
|
||
|
||
each(sequenceIds.stale, function (staleId) { return delete this$1.store.sequences[staleId]; });
|
||
}
|
||
|
||
function clean(target) {
|
||
var this$1 = this;
|
||
|
||
var dirty;
|
||
try {
|
||
each($(target), function (node) {
|
||
var id = node.getAttribute('data-sr-id');
|
||
if (id !== null) {
|
||
dirty = true;
|
||
var element = this$1.store.elements[id];
|
||
if (element.callbackTimer) {
|
||
window.clearTimeout(element.callbackTimer.clock);
|
||
}
|
||
node.setAttribute('style', element.styles.inline.generated);
|
||
node.removeAttribute('data-sr-id');
|
||
delete this$1.store.elements[id];
|
||
}
|
||
});
|
||
} catch (e) {
|
||
return logger.call(this, 'Clean failed.', e.message)
|
||
}
|
||
|
||
if (dirty) {
|
||
try {
|
||
rinse.call(this);
|
||
} catch (e) {
|
||
return logger.call(this, 'Clean failed.', e.message)
|
||
}
|
||
}
|
||
}
|
||
|
||
function destroy() {
|
||
var this$1 = this;
|
||
|
||
/**
|
||
* Remove all generated styles and element ids
|
||
*/
|
||
each(this.store.elements, function (element) {
|
||
element.node.setAttribute('style', element.styles.inline.generated);
|
||
element.node.removeAttribute('data-sr-id');
|
||
});
|
||
|
||
/**
|
||
* Remove all event listeners.
|
||
*/
|
||
each(this.store.containers, function (container) {
|
||
var target =
|
||
container.node === document.documentElement ? window : container.node;
|
||
target.removeEventListener('scroll', this$1.delegate);
|
||
target.removeEventListener('resize', this$1.delegate);
|
||
});
|
||
|
||
/**
|
||
* Clear all data from the store
|
||
*/
|
||
this.store = {
|
||
containers: {},
|
||
elements: {},
|
||
history: [],
|
||
sequences: {}
|
||
};
|
||
}
|
||
|
||
var getPrefixedCssProp = (function () {
|
||
var properties = {};
|
||
var style = document.documentElement.style;
|
||
|
||
function getPrefixedCssProperty(name, source) {
|
||
if ( source === void 0 ) source = style;
|
||
|
||
if (name && typeof name === 'string') {
|
||
if (properties[name]) {
|
||
return properties[name]
|
||
}
|
||
if (typeof source[name] === 'string') {
|
||
return (properties[name] = name)
|
||
}
|
||
if (typeof source[("-webkit-" + name)] === 'string') {
|
||
return (properties[name] = "-webkit-" + name)
|
||
}
|
||
throw new RangeError(("Unable to find \"" + name + "\" style property."))
|
||
}
|
||
throw new TypeError('Expected a string.')
|
||
}
|
||
|
||
getPrefixedCssProperty.clearCache = function () { return (properties = {}); };
|
||
|
||
return getPrefixedCssProperty
|
||
})();
|
||
|
||
function style(element) {
|
||
var computed = window.getComputedStyle(element.node);
|
||
var position = computed.position;
|
||
var config = element.config;
|
||
|
||
/**
|
||
* Generate inline styles
|
||
*/
|
||
var inline = {};
|
||
var inlineStyle = element.node.getAttribute('style') || '';
|
||
var inlineMatch = inlineStyle.match(/[\w-]+\s*:\s*[^;]+\s*/gi) || [];
|
||
|
||
inline.computed = inlineMatch ? inlineMatch.map(function (m) { return m.trim(); }).join('; ') + ';' : '';
|
||
|
||
inline.generated = inlineMatch.some(function (m) { return m.match(/visibility\s?:\s?visible/i); })
|
||
? inline.computed
|
||
: inlineMatch.concat( ['visibility: visible']).map(function (m) { return m.trim(); }).join('; ') + ';';
|
||
|
||
/**
|
||
* Generate opacity styles
|
||
*/
|
||
var computedOpacity = parseFloat(computed.opacity);
|
||
var configOpacity = !isNaN(parseFloat(config.opacity))
|
||
? parseFloat(config.opacity)
|
||
: parseFloat(computed.opacity);
|
||
|
||
var opacity = {
|
||
computed: computedOpacity !== configOpacity ? ("opacity: " + computedOpacity + ";") : '',
|
||
generated: computedOpacity !== configOpacity ? ("opacity: " + configOpacity + ";") : ''
|
||
};
|
||
|
||
/**
|
||
* Generate transformation styles
|
||
*/
|
||
var transformations = [];
|
||
|
||
if (parseFloat(config.distance)) {
|
||
var axis = config.origin === 'top' || config.origin === 'bottom' ? 'Y' : 'X';
|
||
|
||
/**
|
||
* Let’s make sure our our pixel distances are negative for top and left.
|
||
* e.g. { origin: 'top', distance: '25px' } starts at `top: -25px` in CSS.
|
||
*/
|
||
var distance = config.distance;
|
||
if (config.origin === 'top' || config.origin === 'left') {
|
||
distance = /^-/.test(distance) ? distance.substr(1) : ("-" + distance);
|
||
}
|
||
|
||
var ref = distance.match(/(^-?\d+\.?\d?)|(em$|px$|%$)/g);
|
||
var value = ref[0];
|
||
var unit = ref[1];
|
||
|
||
switch (unit) {
|
||
case 'em':
|
||
distance = parseInt(computed.fontSize) * value;
|
||
break
|
||
case 'px':
|
||
distance = value;
|
||
break
|
||
case '%':
|
||
/**
|
||
* Here we use `getBoundingClientRect` instead of
|
||
* the existing data attached to `element.geometry`
|
||
* because only the former includes any transformations
|
||
* current applied to the element.
|
||
*
|
||
* If that behavior ends up being unintuitive, this
|
||
* logic could instead utilize `element.geometry.height`
|
||
* and `element.geoemetry.width` for the distaince calculation
|
||
*/
|
||
distance =
|
||
axis === 'Y'
|
||
? element.node.getBoundingClientRect().height * value / 100
|
||
: element.node.getBoundingClientRect().width * value / 100;
|
||
break
|
||
default:
|
||
throw new RangeError('Unrecognized or missing distance unit.')
|
||
}
|
||
|
||
if (axis === 'Y') {
|
||
transformations.push(translateY(distance));
|
||
} else {
|
||
transformations.push(translateX(distance));
|
||
}
|
||
}
|
||
|
||
if (config.rotate.x) { transformations.push(rotateX(config.rotate.x)); }
|
||
if (config.rotate.y) { transformations.push(rotateY(config.rotate.y)); }
|
||
if (config.rotate.z) { transformations.push(rotateZ(config.rotate.z)); }
|
||
if (config.scale !== 1) {
|
||
if (config.scale === 0) {
|
||
/**
|
||
* The CSS Transforms matrix interpolation specification
|
||
* basically disallows transitions of non-invertible
|
||
* matrixes, which means browsers won't transition
|
||
* elements with zero scale.
|
||
*
|
||
* That’s inconvenient for the API and developer
|
||
* experience, so we simply nudge their value
|
||
* slightly above zero; this allows browsers
|
||
* to transition our element as expected.
|
||
*
|
||
* `0.0002` was the smallest number
|
||
* that performed across browsers.
|
||
*/
|
||
transformations.push(scale(0.0002));
|
||
} else {
|
||
transformations.push(scale(config.scale));
|
||
}
|
||
}
|
||
|
||
var transform = {};
|
||
if (transformations.length) {
|
||
transform.property = getPrefixedCssProp('transform');
|
||
/**
|
||
* The default computed transform value should be one of:
|
||
* undefined || 'none' || 'matrix()' || 'matrix3d()'
|
||
*/
|
||
transform.computed = {
|
||
raw: computed[transform.property],
|
||
matrix: parse(computed[transform.property])
|
||
};
|
||
|
||
transformations.unshift(transform.computed.matrix);
|
||
var product = transformations.reduce(multiply);
|
||
|
||
transform.generated = {
|
||
initial: ((transform.property) + ": matrix3d(" + (product.join(', ')) + ");"),
|
||
final: ((transform.property) + ": matrix3d(" + (transform.computed.matrix.join(
|
||
', '
|
||
)) + ");")
|
||
};
|
||
} else {
|
||
transform.generated = {
|
||
initial: '',
|
||
final: ''
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Generate transition styles
|
||
*/
|
||
var transition = {};
|
||
if (opacity.generated || transform.generated.initial) {
|
||
transition.property = getPrefixedCssProp('transition');
|
||
transition.computed = computed[transition.property];
|
||
transition.fragments = [];
|
||
|
||
var delay = config.delay;
|
||
var duration = config.duration;
|
||
var easing = config.easing;
|
||
|
||
if (opacity.generated) {
|
||
transition.fragments.push({
|
||
delayed: ("opacity " + (duration / 1000) + "s " + easing + " " + (delay / 1000) + "s"),
|
||
instant: ("opacity " + (duration / 1000) + "s " + easing + " 0s")
|
||
});
|
||
}
|
||
|
||
if (transform.generated.initial) {
|
||
transition.fragments.push({
|
||
delayed: ((transform.property) + " " + (duration / 1000) + "s " + easing + " " + (delay /
|
||
1000) + "s"),
|
||
instant: ((transform.property) + " " + (duration / 1000) + "s " + easing + " 0s")
|
||
});
|
||
}
|
||
|
||
/**
|
||
* The default computed transition property should be one of:
|
||
* undefined || '' || 'all 0s ease 0s' || 'all 0s 0s cubic-bezier()'
|
||
*/
|
||
if (transition.computed && !transition.computed.match(/all 0s/)) {
|
||
transition.fragments.unshift({
|
||
delayed: transition.computed,
|
||
instant: transition.computed
|
||
});
|
||
}
|
||
|
||
var composed = transition.fragments.reduce(
|
||
function (composition, fragment, i) {
|
||
composition.delayed +=
|
||
i === 0 ? fragment.delayed : (", " + (fragment.delayed));
|
||
composition.instant +=
|
||
i === 0 ? fragment.instant : (", " + (fragment.instant));
|
||
return composition
|
||
},
|
||
{
|
||
delayed: '',
|
||
instant: ''
|
||
}
|
||
);
|
||
|
||
transition.generated = {
|
||
delayed: ((transition.property) + ": " + (composed.delayed) + ";"),
|
||
instant: ((transition.property) + ": " + (composed.instant) + ";")
|
||
};
|
||
} else {
|
||
transition.generated = {
|
||
delayed: '',
|
||
instant: ''
|
||
};
|
||
}
|
||
|
||
return {
|
||
inline: inline,
|
||
opacity: opacity,
|
||
position: position,
|
||
transform: transform,
|
||
transition: transition
|
||
}
|
||
}
|
||
|
||
function animate(element, force) {
|
||
if ( force === void 0 ) force = {};
|
||
|
||
var pristine = force.pristine || this.pristine;
|
||
var delayed =
|
||
element.config.useDelay === 'always' ||
|
||
(element.config.useDelay === 'onload' && pristine) ||
|
||
(element.config.useDelay === 'once' && !element.seen);
|
||
|
||
var shouldReveal = element.visible && !element.revealed;
|
||
var shouldReset = !element.visible && element.revealed && element.config.reset;
|
||
|
||
if (force.reveal || shouldReveal) {
|
||
return triggerReveal.call(this, element, delayed)
|
||
}
|
||
|
||
if (force.reset || shouldReset) {
|
||
return triggerReset.call(this, element)
|
||
}
|
||
}
|
||
|
||
function triggerReveal(element, delayed) {
|
||
var styles = [
|
||
element.styles.inline.generated,
|
||
element.styles.opacity.computed,
|
||
element.styles.transform.generated.final
|
||
];
|
||
if (delayed) {
|
||
styles.push(element.styles.transition.generated.delayed);
|
||
} else {
|
||
styles.push(element.styles.transition.generated.instant);
|
||
}
|
||
element.revealed = element.seen = true;
|
||
element.node.setAttribute('style', styles.filter(function (s) { return s !== ''; }).join(' '));
|
||
registerCallbacks.call(this, element, delayed);
|
||
}
|
||
|
||
function triggerReset(element) {
|
||
var styles = [
|
||
element.styles.inline.generated,
|
||
element.styles.opacity.generated,
|
||
element.styles.transform.generated.initial,
|
||
element.styles.transition.generated.instant
|
||
];
|
||
element.revealed = false;
|
||
element.node.setAttribute('style', styles.filter(function (s) { return s !== ''; }).join(' '));
|
||
registerCallbacks.call(this, element);
|
||
}
|
||
|
||
function registerCallbacks(element, isDelayed) {
|
||
var this$1 = this;
|
||
|
||
var duration = isDelayed
|
||
? element.config.duration + element.config.delay
|
||
: element.config.duration;
|
||
|
||
var beforeCallback = element.revealed
|
||
? element.config.beforeReveal
|
||
: element.config.beforeReset;
|
||
|
||
var afterCallback = element.revealed
|
||
? element.config.afterReveal
|
||
: element.config.afterReset;
|
||
|
||
var elapsed = 0;
|
||
if (element.callbackTimer) {
|
||
elapsed = Date.now() - element.callbackTimer.start;
|
||
window.clearTimeout(element.callbackTimer.clock);
|
||
}
|
||
|
||
beforeCallback(element.node);
|
||
|
||
element.callbackTimer = {
|
||
start: Date.now(),
|
||
clock: window.setTimeout(function () {
|
||
afterCallback(element.node);
|
||
element.callbackTimer = null;
|
||
if (element.revealed && !element.config.reset && element.config.cleanup) {
|
||
clean.call(this$1, element.node);
|
||
}
|
||
}, duration - elapsed)
|
||
};
|
||
}
|
||
|
||
var nextUniqueId = (function () {
|
||
var uid = 0;
|
||
return function () { return uid++; }
|
||
})();
|
||
|
||
function sequence(element, pristine) {
|
||
if ( pristine === void 0 ) pristine = this.pristine;
|
||
|
||
/**
|
||
* We first check if the element should reset.
|
||
*/
|
||
if (!element.visible && element.revealed && element.config.reset) {
|
||
return animate.call(this, element, { reset: true })
|
||
}
|
||
|
||
var seq = this.store.sequences[element.sequence.id];
|
||
var i = element.sequence.index;
|
||
|
||
if (seq) {
|
||
var visible = new SequenceModel(seq, 'visible', this.store);
|
||
var revealed = new SequenceModel(seq, 'revealed', this.store);
|
||
|
||
seq.models = { visible: visible, revealed: revealed };
|
||
|
||
/**
|
||
* If the sequence has no revealed members,
|
||
* then we reveal the first visible element
|
||
* within that sequence.
|
||
*
|
||
* The sequence then cues a recursive call
|
||
* in both directions.
|
||
*/
|
||
if (!revealed.body.length) {
|
||
var nextId = seq.members[visible.body[0]];
|
||
var nextElement = this.store.elements[nextId];
|
||
|
||
if (nextElement) {
|
||
cue.call(this, seq, visible.body[0], -1, pristine);
|
||
cue.call(this, seq, visible.body[0], +1, pristine);
|
||
return animate.call(this, nextElement, { reveal: true, pristine: pristine })
|
||
}
|
||
}
|
||
|
||
/**
|
||
* If our element isn’t resetting, we check the
|
||
* element sequence index against the head, and
|
||
* then the foot of the sequence.
|
||
*/
|
||
if (
|
||
!seq.blocked.head &&
|
||
i === [].concat( revealed.head ).pop() &&
|
||
i >= [].concat( visible.body ).shift()
|
||
) {
|
||
cue.call(this, seq, i, -1, pristine);
|
||
return animate.call(this, element, { reveal: true, pristine: pristine })
|
||
}
|
||
|
||
if (
|
||
!seq.blocked.foot &&
|
||
i === [].concat( revealed.foot ).shift() &&
|
||
i <= [].concat( visible.body ).pop()
|
||
) {
|
||
cue.call(this, seq, i, +1, pristine);
|
||
return animate.call(this, element, { reveal: true, pristine: pristine })
|
||
}
|
||
}
|
||
}
|
||
|
||
function Sequence(interval) {
|
||
var i = Math.abs(interval);
|
||
if (!isNaN(i)) {
|
||
this.id = nextUniqueId();
|
||
this.interval = Math.max(i, 16);
|
||
this.members = [];
|
||
this.models = {};
|
||
this.blocked = {
|
||
head: false,
|
||
foot: false
|
||
};
|
||
} else {
|
||
throw new RangeError('Invalid sequence interval.')
|
||
}
|
||
}
|
||
|
||
function SequenceModel(seq, prop, store) {
|
||
var this$1 = this;
|
||
|
||
this.head = [];
|
||
this.body = [];
|
||
this.foot = [];
|
||
|
||
each(seq.members, function (id, index) {
|
||
var element = store.elements[id];
|
||
if (element && element[prop]) {
|
||
this$1.body.push(index);
|
||
}
|
||
});
|
||
|
||
if (this.body.length) {
|
||
each(seq.members, function (id, index) {
|
||
var element = store.elements[id];
|
||
if (element && !element[prop]) {
|
||
if (index < this$1.body[0]) {
|
||
this$1.head.push(index);
|
||
} else {
|
||
this$1.foot.push(index);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
function cue(seq, i, direction, pristine) {
|
||
var this$1 = this;
|
||
|
||
var blocked = ['head', null, 'foot'][1 + direction];
|
||
var nextId = seq.members[i + direction];
|
||
var nextElement = this.store.elements[nextId];
|
||
|
||
seq.blocked[blocked] = true;
|
||
|
||
setTimeout(function () {
|
||
seq.blocked[blocked] = false;
|
||
if (nextElement) {
|
||
sequence.call(this$1, nextElement, pristine);
|
||
}
|
||
}, seq.interval);
|
||
}
|
||
|
||
function initialize() {
|
||
var this$1 = this;
|
||
|
||
rinse.call(this);
|
||
|
||
each(this.store.elements, function (element) {
|
||
var styles = [element.styles.inline.generated];
|
||
|
||
if (element.visible) {
|
||
styles.push(element.styles.opacity.computed);
|
||
styles.push(element.styles.transform.generated.final);
|
||
} else {
|
||
styles.push(element.styles.opacity.generated);
|
||
styles.push(element.styles.transform.generated.initial);
|
||
}
|
||
|
||
element.node.setAttribute('style', styles.filter(function (s) { return s !== ''; }).join(' '));
|
||
});
|
||
|
||
each(this.store.containers, function (container) {
|
||
var target =
|
||
container.node === document.documentElement ? window : container.node;
|
||
target.addEventListener('scroll', this$1.delegate);
|
||
target.addEventListener('resize', this$1.delegate);
|
||
});
|
||
|
||
/**
|
||
* Manually invoke delegate once to capture
|
||
* element and container dimensions, container
|
||
* scroll position, and trigger any valid reveals
|
||
*/
|
||
this.delegate();
|
||
|
||
/**
|
||
* Wipe any existing `setTimeout` now
|
||
* that initialization has completed.
|
||
*/
|
||
this.initTimeout = null;
|
||
}
|
||
|
||
function isMobile(agent) {
|
||
if ( agent === void 0 ) agent = navigator.userAgent;
|
||
|
||
return /Android|iPhone|iPad|iPod/i.test(agent)
|
||
}
|
||
|
||
function deepAssign(target) {
|
||
var sources = [], len = arguments.length - 1;
|
||
while ( len-- > 0 ) sources[ len ] = arguments[ len + 1 ];
|
||
|
||
if (isObject(target)) {
|
||
each(sources, function (source) {
|
||
each(source, function (data, key) {
|
||
if (isObject(data)) {
|
||
if (!target[key] || !isObject(target[key])) {
|
||
target[key] = {};
|
||
}
|
||
deepAssign(target[key], data);
|
||
} else {
|
||
target[key] = data;
|
||
}
|
||
});
|
||
});
|
||
return target
|
||
} else {
|
||
throw new TypeError('Target must be an object literal.')
|
||
}
|
||
}
|
||
|
||
function reveal(target, options, syncing) {
|
||
var this$1 = this;
|
||
if ( options === void 0 ) options = {};
|
||
if ( syncing === void 0 ) syncing = false;
|
||
|
||
var containerBuffer = [];
|
||
var sequence$$1;
|
||
var interval = options.interval || defaults.interval;
|
||
|
||
try {
|
||
if (interval) {
|
||
sequence$$1 = new Sequence(interval);
|
||
}
|
||
|
||
var nodes = $(target);
|
||
if (!nodes.length) {
|
||
throw new Error('Invalid reveal target.')
|
||
}
|
||
|
||
var elements = nodes.reduce(function (elementBuffer, elementNode) {
|
||
var element = {};
|
||
var existingId = elementNode.getAttribute('data-sr-id');
|
||
|
||
if (existingId) {
|
||
deepAssign(element, this$1.store.elements[existingId]);
|
||
|
||
/**
|
||
* In order to prevent previously generated styles
|
||
* from throwing off the new styles, the style tag
|
||
* has to be reverted to its pre-reveal state.
|
||
*/
|
||
element.node.setAttribute('style', element.styles.inline.computed);
|
||
} else {
|
||
element.id = nextUniqueId();
|
||
element.node = elementNode;
|
||
element.seen = false;
|
||
element.revealed = false;
|
||
element.visible = false;
|
||
}
|
||
|
||
var config = deepAssign({}, element.config || this$1.defaults, options);
|
||
|
||
if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) {
|
||
if (existingId) {
|
||
clean.call(this$1, element);
|
||
}
|
||
return elementBuffer // skip elements that are disabled
|
||
}
|
||
|
||
var containerNode = $(config.container)[0];
|
||
if (!containerNode) {
|
||
throw new Error('Invalid container.')
|
||
}
|
||
if (!containerNode.contains(elementNode)) {
|
||
return elementBuffer // skip elements found outside the container
|
||
}
|
||
|
||
var containerId;
|
||
{
|
||
containerId = getContainerId(
|
||
containerNode,
|
||
containerBuffer,
|
||
this$1.store.containers
|
||
);
|
||
if (containerId === null) {
|
||
containerId = nextUniqueId();
|
||
containerBuffer.push({ id: containerId, node: containerNode });
|
||
}
|
||
}
|
||
|
||
element.config = config;
|
||
element.containerId = containerId;
|
||
element.styles = style(element);
|
||
|
||
if (sequence$$1) {
|
||
element.sequence = {
|
||
id: sequence$$1.id,
|
||
index: sequence$$1.members.length
|
||
};
|
||
sequence$$1.members.push(element.id);
|
||
}
|
||
|
||
elementBuffer.push(element);
|
||
return elementBuffer
|
||
}, []);
|
||
|
||
/**
|
||
* Modifying the DOM via setAttribute needs to be handled
|
||
* separately from reading computed styles in the map above
|
||
* for the browser to batch DOM changes (limiting reflows)
|
||
*/
|
||
each(elements, function (element) {
|
||
this$1.store.elements[element.id] = element;
|
||
element.node.setAttribute('data-sr-id', element.id);
|
||
});
|
||
} catch (e) {
|
||
return logger.call(this, 'Reveal failed.', e.message)
|
||
}
|
||
|
||
/**
|
||
* Now that element set-up is complete...
|
||
* Let’s commit any container and sequence data we have to the store.
|
||
*/
|
||
each(containerBuffer, function (container) {
|
||
this$1.store.containers[container.id] = {
|
||
id: container.id,
|
||
node: container.node
|
||
};
|
||
});
|
||
if (sequence$$1) {
|
||
this.store.sequences[sequence$$1.id] = sequence$$1;
|
||
}
|
||
|
||
/**
|
||
* If reveal wasn't invoked by sync, we want to
|
||
* make sure to add this call to the history.
|
||
*/
|
||
if (syncing !== true) {
|
||
this.store.history.push({ target: target, options: options });
|
||
|
||
/**
|
||
* Push initialization to the event queue, giving
|
||
* multiple reveal calls time to be interpreted.
|
||
*/
|
||
if (this.initTimeout) {
|
||
window.clearTimeout(this.initTimeout);
|
||
}
|
||
this.initTimeout = window.setTimeout(initialize.bind(this), 0);
|
||
}
|
||
}
|
||
|
||
function getContainerId(node) {
|
||
var collections = [], len = arguments.length - 1;
|
||
while ( len-- > 0 ) collections[ len ] = arguments[ len + 1 ];
|
||
|
||
var id = null;
|
||
each(collections, function (collection) {
|
||
each(collection, function (container) {
|
||
if (id === null && container.node === node) {
|
||
id = container.id;
|
||
}
|
||
});
|
||
});
|
||
return id
|
||
}
|
||
|
||
/**
|
||
* Re-runs the reveal method for each record stored in history,
|
||
* for capturing new content asynchronously loaded into the DOM.
|
||
*/
|
||
function sync() {
|
||
var this$1 = this;
|
||
|
||
each(this.store.history, function (record) {
|
||
reveal.call(this$1, record.target, record.options, true);
|
||
});
|
||
|
||
initialize.call(this);
|
||
}
|
||
|
||
var polyfill = function (x) { return (x > 0) - (x < 0) || +x; };
|
||
var mathSign = Math.sign || polyfill
|
||
|
||
function getGeometry(target, isContainer) {
|
||
/**
|
||
* We want to ignore padding and scrollbars for container elements.
|
||
* More information here: https://goo.gl/vOZpbz
|
||
*/
|
||
var height = isContainer ? target.node.clientHeight : target.node.offsetHeight;
|
||
var width = isContainer ? target.node.clientWidth : target.node.offsetWidth;
|
||
|
||
var offsetTop = 0;
|
||
var offsetLeft = 0;
|
||
var node = target.node;
|
||
|
||
do {
|
||
if (!isNaN(node.offsetTop)) {
|
||
offsetTop += node.offsetTop;
|
||
}
|
||
if (!isNaN(node.offsetLeft)) {
|
||
offsetLeft += node.offsetLeft;
|
||
}
|
||
node = node.offsetParent;
|
||
} while (node)
|
||
|
||
return {
|
||
bounds: {
|
||
top: offsetTop,
|
||
right: offsetLeft + width,
|
||
bottom: offsetTop + height,
|
||
left: offsetLeft
|
||
},
|
||
height: height,
|
||
width: width
|
||
}
|
||
}
|
||
|
||
function getScrolled(container) {
|
||
var top, left;
|
||
if (container.node === document.documentElement) {
|
||
top = window.pageYOffset;
|
||
left = window.pageXOffset;
|
||
} else {
|
||
top = container.node.scrollTop;
|
||
left = container.node.scrollLeft;
|
||
}
|
||
return { top: top, left: left }
|
||
}
|
||
|
||
function isElementVisible(element) {
|
||
if ( element === void 0 ) element = {};
|
||
|
||
var container = this.store.containers[element.containerId];
|
||
if (!container) { return }
|
||
|
||
var viewFactor = Math.max(0, Math.min(1, element.config.viewFactor));
|
||
var viewOffset = element.config.viewOffset;
|
||
|
||
var elementBounds = {
|
||
top: element.geometry.bounds.top + element.geometry.height * viewFactor,
|
||
right: element.geometry.bounds.right - element.geometry.width * viewFactor,
|
||
bottom: element.geometry.bounds.bottom - element.geometry.height * viewFactor,
|
||
left: element.geometry.bounds.left + element.geometry.width * viewFactor
|
||
};
|
||
|
||
var containerBounds = {
|
||
top: container.geometry.bounds.top + container.scroll.top + viewOffset.top,
|
||
right: container.geometry.bounds.right + container.scroll.left - viewOffset.right,
|
||
bottom:
|
||
container.geometry.bounds.bottom + container.scroll.top - viewOffset.bottom,
|
||
left: container.geometry.bounds.left + container.scroll.left + viewOffset.left
|
||
};
|
||
|
||
return (
|
||
(elementBounds.top < containerBounds.bottom &&
|
||
elementBounds.right > containerBounds.left &&
|
||
elementBounds.bottom > containerBounds.top &&
|
||
elementBounds.left < containerBounds.right) ||
|
||
element.styles.position === 'fixed'
|
||
)
|
||
}
|
||
|
||
function delegate(
|
||
event,
|
||
elements
|
||
) {
|
||
var this$1 = this;
|
||
if ( event === void 0 ) event = { type: 'init' };
|
||
if ( elements === void 0 ) elements = this.store.elements;
|
||
|
||
raf(function () {
|
||
var stale = event.type === 'init' || event.type === 'resize';
|
||
|
||
each(this$1.store.containers, function (container) {
|
||
if (stale) {
|
||
container.geometry = getGeometry.call(this$1, container, true);
|
||
}
|
||
var scroll = getScrolled.call(this$1, container);
|
||
if (container.scroll) {
|
||
container.direction = {
|
||
x: mathSign(scroll.left - container.scroll.left),
|
||
y: mathSign(scroll.top - container.scroll.top)
|
||
};
|
||
}
|
||
container.scroll = scroll;
|
||
});
|
||
|
||
/**
|
||
* Due to how the sequencer is implemented, it’s
|
||
* important that we update the state of all
|
||
* elements, before any animation logic is
|
||
* evaluated (in the second loop below).
|
||
*/
|
||
each(elements, function (element) {
|
||
if (stale) {
|
||
element.geometry = getGeometry.call(this$1, element);
|
||
}
|
||
element.visible = isElementVisible.call(this$1, element);
|
||
});
|
||
|
||
each(elements, function (element) {
|
||
if (element.sequence) {
|
||
sequence.call(this$1, element);
|
||
} else {
|
||
animate.call(this$1, element);
|
||
}
|
||
});
|
||
|
||
this$1.pristine = false;
|
||
});
|
||
}
|
||
|
||
function transformSupported() {
|
||
var style = document.documentElement.style;
|
||
return 'transform' in style || 'WebkitTransform' in style
|
||
}
|
||
|
||
function transitionSupported() {
|
||
var style = document.documentElement.style;
|
||
return 'transition' in style || 'WebkitTransition' in style
|
||
}
|
||
|
||
var version = "4.0.1";
|
||
|
||
var boundDelegate;
|
||
var boundDestroy;
|
||
var boundReveal;
|
||
var boundClean;
|
||
var boundSync;
|
||
var config;
|
||
var debug;
|
||
var instance;
|
||
|
||
function ScrollReveal(options) {
|
||
if ( options === void 0 ) options = {};
|
||
|
||
var invokedWithoutNew =
|
||
typeof this === 'undefined' ||
|
||
Object.getPrototypeOf(this) !== ScrollReveal.prototype;
|
||
|
||
if (invokedWithoutNew) {
|
||
return new ScrollReveal(options)
|
||
}
|
||
|
||
if (!ScrollReveal.isSupported()) {
|
||
logger.call(this, 'Instantiation failed.', 'This browser is not supported.');
|
||
return mount.failure()
|
||
}
|
||
|
||
var buffer;
|
||
try {
|
||
buffer = config
|
||
? deepAssign({}, config, options)
|
||
: deepAssign({}, defaults, options);
|
||
} catch (e) {
|
||
logger.call(this, 'Invalid configuration.', e.message);
|
||
return mount.failure()
|
||
}
|
||
|
||
try {
|
||
var container = $(buffer.container)[0];
|
||
if (!container) {
|
||
throw new Error('Invalid container.')
|
||
}
|
||
} catch (e) {
|
||
logger.call(this, e.message);
|
||
return mount.failure()
|
||
}
|
||
|
||
config = buffer;
|
||
|
||
if ((!config.mobile && isMobile()) || (!config.desktop && !isMobile())) {
|
||
logger.call(
|
||
this,
|
||
'This device is disabled.',
|
||
("desktop: " + (config.desktop)),
|
||
("mobile: " + (config.mobile))
|
||
);
|
||
return mount.failure()
|
||
}
|
||
|
||
mount.success();
|
||
|
||
this.store = {
|
||
containers: {},
|
||
elements: {},
|
||
history: [],
|
||
sequences: {}
|
||
};
|
||
|
||
this.pristine = true;
|
||
|
||
boundDelegate = boundDelegate || delegate.bind(this);
|
||
boundDestroy = boundDestroy || destroy.bind(this);
|
||
boundReveal = boundReveal || reveal.bind(this);
|
||
boundClean = boundClean || clean.bind(this);
|
||
boundSync = boundSync || sync.bind(this);
|
||
|
||
Object.defineProperty(this, 'delegate', { get: function () { return boundDelegate; } });
|
||
Object.defineProperty(this, 'destroy', { get: function () { return boundDestroy; } });
|
||
Object.defineProperty(this, 'reveal', { get: function () { return boundReveal; } });
|
||
Object.defineProperty(this, 'clean', { get: function () { return boundClean; } });
|
||
Object.defineProperty(this, 'sync', { get: function () { return boundSync; } });
|
||
|
||
Object.defineProperty(this, 'defaults', { get: function () { return config; } });
|
||
Object.defineProperty(this, 'version', { get: function () { return version; } });
|
||
Object.defineProperty(this, 'noop', { get: function () { return false; } });
|
||
|
||
return instance ? instance : (instance = this)
|
||
}
|
||
|
||
ScrollReveal.isSupported = function () { return transformSupported() && transitionSupported(); };
|
||
|
||
Object.defineProperty(ScrollReveal, 'debug', {
|
||
get: function () { return debug || false; },
|
||
set: function (value) { return (debug = typeof value === 'boolean' ? value : debug); }
|
||
});
|
||
|
||
ScrollReveal();
|
||
|
||
export default ScrollReveal;
|