|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;(function () { |
|
'use strict' |
|
|
|
var sr |
|
var _requestAnimationFrame |
|
|
|
function ScrollReveal (config) { |
|
|
|
if (typeof this === 'undefined' || Object.getPrototypeOf(this) !== ScrollReveal.prototype) { |
|
return new ScrollReveal(config) |
|
} |
|
|
|
sr = this |
|
sr.version = '3.3.6' |
|
sr.tools = new Tools() |
|
|
|
if (sr.isSupported()) { |
|
sr.tools.extend(sr.defaults, config || {}) |
|
|
|
sr.defaults.container = _resolveContainer(sr.defaults) |
|
|
|
sr.store = { |
|
elements: {}, |
|
containers: [] |
|
} |
|
|
|
sr.sequences = {} |
|
sr.history = [] |
|
sr.uid = 0 |
|
sr.initialized = false |
|
} else if (typeof console !== 'undefined' && console !== null) { |
|
|
|
console.log('ScrollReveal is not supported in this browser.') |
|
} |
|
|
|
return sr |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ScrollReveal.prototype.defaults = { |
|
|
|
origin: 'bottom', |
|
|
|
|
|
distance: '20px', |
|
|
|
|
|
duration: 500, |
|
delay: 0, |
|
|
|
|
|
rotate: { x: 0, y: 0, z: 0 }, |
|
|
|
|
|
opacity: 0, |
|
|
|
|
|
scale: 0.9, |
|
|
|
|
|
easing: 'cubic-bezier(0.6, 0.2, 0.1, 1)', |
|
|
|
|
|
|
|
|
|
container: window.document.documentElement, |
|
|
|
|
|
mobile: true, |
|
|
|
|
|
|
|
reset: false, |
|
|
|
|
|
|
|
|
|
useDelay: 'always', |
|
|
|
|
|
|
|
viewFactor: 0.2, |
|
|
|
|
|
|
|
|
|
|
|
viewOffset: { top: 0, right: 0, bottom: 0, left: 0 }, |
|
|
|
|
|
beforeReveal: function (domEl) {}, |
|
beforeReset: function (domEl) {}, |
|
|
|
|
|
afterReveal: function (domEl) {}, |
|
afterReset: function (domEl) {} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
ScrollReveal.prototype.isSupported = function () { |
|
var style = document.documentElement.style |
|
return 'WebkitTransition' in style && 'WebkitTransform' in style || |
|
'transition' in style && 'transform' in style |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ScrollReveal.prototype.reveal = function (target, config, interval, sync) { |
|
var container |
|
var elements |
|
var elem |
|
var elemId |
|
var sequence |
|
var sequenceId |
|
|
|
|
|
|
|
if (config !== undefined && typeof config === 'number') { |
|
interval = config |
|
config = {} |
|
} else if (config === undefined || config === null) { |
|
config = {} |
|
} |
|
|
|
container = _resolveContainer(config) |
|
elements = _getRevealElements(target, container) |
|
|
|
if (!elements.length) { |
|
console.log('ScrollReveal: reveal on "' + target + '" failed, no elements found.') |
|
return sr |
|
} |
|
|
|
|
|
if (interval && typeof interval === 'number') { |
|
sequenceId = _nextUid() |
|
|
|
sequence = sr.sequences[sequenceId] = { |
|
id: sequenceId, |
|
interval: interval, |
|
elemIds: [], |
|
active: false |
|
} |
|
} |
|
|
|
|
|
for (var i = 0; i < elements.length; i++) { |
|
|
|
elemId = elements[i].getAttribute('data-sr-id') |
|
if (elemId) { |
|
elem = sr.store.elements[elemId] |
|
} else { |
|
|
|
elem = { |
|
id: _nextUid(), |
|
domEl: elements[i], |
|
seen: false, |
|
revealing: false |
|
} |
|
elem.domEl.setAttribute('data-sr-id', elem.id) |
|
} |
|
|
|
|
|
if (sequence) { |
|
elem.sequence = { |
|
id: sequence.id, |
|
index: sequence.elemIds.length |
|
} |
|
|
|
sequence.elemIds.push(elem.id) |
|
} |
|
|
|
|
|
|
|
_configure(elem, config, container) |
|
_style(elem) |
|
_updateStore(elem) |
|
|
|
|
|
|
|
if (sr.tools.isMobile() && !elem.config.mobile || !sr.isSupported()) { |
|
elem.domEl.setAttribute('style', elem.styles.inline) |
|
elem.disabled = true |
|
} else if (!elem.revealing) { |
|
|
|
elem.domEl.setAttribute('style', |
|
elem.styles.inline + |
|
elem.styles.transform.initial |
|
) |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!sync && sr.isSupported()) { |
|
_record(target, config, interval) |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (sr.initTimeout) { |
|
window.clearTimeout(sr.initTimeout) |
|
} |
|
sr.initTimeout = window.setTimeout(_init, 0) |
|
} |
|
|
|
return sr |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
ScrollReveal.prototype.sync = function () { |
|
if (sr.history.length && sr.isSupported()) { |
|
for (var i = 0; i < sr.history.length; i++) { |
|
var record = sr.history[i] |
|
sr.reveal(record.target, record.config, record.interval, true) |
|
} |
|
_init() |
|
} else { |
|
console.log('ScrollReveal: sync failed, no reveals found.') |
|
} |
|
return sr |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function _resolveContainer (config) { |
|
if (config && config.container) { |
|
if (typeof config.container === 'string') { |
|
return window.document.documentElement.querySelector(config.container) |
|
} else if (sr.tools.isNode(config.container)) { |
|
return config.container |
|
} else { |
|
console.log('ScrollReveal: invalid container "' + config.container + '" provided.') |
|
console.log('ScrollReveal: falling back to default container.') |
|
} |
|
} |
|
return sr.defaults.container |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function _getRevealElements (target, container) { |
|
if (typeof target === 'string') { |
|
return Array.prototype.slice.call(container.querySelectorAll(target)) |
|
} else if (sr.tools.isNode(target)) { |
|
return [target] |
|
} else if (sr.tools.isNodeList(target)) { |
|
return Array.prototype.slice.call(target) |
|
} |
|
return [] |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function _nextUid () { |
|
return ++sr.uid |
|
} |
|
|
|
function _configure (elem, config, container) { |
|
|
|
|
|
if (config.container) config.container = container |
|
|
|
|
|
if (!elem.config) { |
|
elem.config = sr.tools.extendClone(sr.defaults, config) |
|
} else { |
|
|
|
|
|
elem.config = sr.tools.extendClone(elem.config, config) |
|
} |
|
|
|
|
|
if (elem.config.origin === 'top' || elem.config.origin === 'bottom') { |
|
elem.config.axis = 'Y' |
|
} else { |
|
elem.config.axis = 'X' |
|
} |
|
} |
|
|
|
function _style (elem) { |
|
var computed = window.getComputedStyle(elem.domEl) |
|
|
|
if (!elem.styles) { |
|
elem.styles = { |
|
transition: {}, |
|
transform: {}, |
|
computed: {} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
elem.styles.inline = elem.domEl.getAttribute('style') || '' |
|
elem.styles.inline += '; visibility: visible; ' |
|
|
|
|
|
elem.styles.computed.opacity = computed.opacity |
|
|
|
|
|
if (!computed.transition || computed.transition === 'all 0s ease 0s') { |
|
elem.styles.computed.transition = '' |
|
} else { |
|
elem.styles.computed.transition = computed.transition + ', ' |
|
} |
|
} |
|
|
|
|
|
elem.styles.transition.instant = _generateTransition(elem, 0) |
|
elem.styles.transition.delayed = _generateTransition(elem, elem.config.delay) |
|
|
|
|
|
elem.styles.transform.initial = ' -webkit-transform:' |
|
elem.styles.transform.target = ' -webkit-transform:' |
|
_generateTransform(elem) |
|
|
|
|
|
elem.styles.transform.initial += 'transform:' |
|
elem.styles.transform.target += 'transform:' |
|
_generateTransform(elem) |
|
} |
|
|
|
function _generateTransition (elem, delay) { |
|
var config = elem.config |
|
|
|
return '-webkit-transition: ' + elem.styles.computed.transition + |
|
'-webkit-transform ' + config.duration / 1000 + 's ' + |
|
config.easing + ' ' + |
|
delay / 1000 + 's, opacity ' + |
|
config.duration / 1000 + 's ' + |
|
config.easing + ' ' + |
|
delay / 1000 + 's; ' + |
|
|
|
'transition: ' + elem.styles.computed.transition + |
|
'transform ' + config.duration / 1000 + 's ' + |
|
config.easing + ' ' + |
|
delay / 1000 + 's, opacity ' + |
|
config.duration / 1000 + 's ' + |
|
config.easing + ' ' + |
|
delay / 1000 + 's; ' |
|
} |
|
|
|
function _generateTransform (elem) { |
|
var config = elem.config |
|
var cssDistance |
|
var transform = elem.styles.transform |
|
|
|
|
|
|
|
if (config.origin === 'top' || config.origin === 'left') { |
|
cssDistance = /^-/.test(config.distance) |
|
? config.distance.substr(1) |
|
: '-' + config.distance |
|
} else { |
|
cssDistance = config.distance |
|
} |
|
|
|
if (parseInt(config.distance)) { |
|
transform.initial += ' translate' + config.axis + '(' + cssDistance + ')' |
|
transform.target += ' translate' + config.axis + '(0)' |
|
} |
|
if (config.scale) { |
|
transform.initial += ' scale(' + config.scale + ')' |
|
transform.target += ' scale(1)' |
|
} |
|
if (config.rotate.x) { |
|
transform.initial += ' rotateX(' + config.rotate.x + 'deg)' |
|
transform.target += ' rotateX(0)' |
|
} |
|
if (config.rotate.y) { |
|
transform.initial += ' rotateY(' + config.rotate.y + 'deg)' |
|
transform.target += ' rotateY(0)' |
|
} |
|
if (config.rotate.z) { |
|
transform.initial += ' rotateZ(' + config.rotate.z + 'deg)' |
|
transform.target += ' rotateZ(0)' |
|
} |
|
transform.initial += '; opacity: ' + config.opacity + ';' |
|
transform.target += '; opacity: ' + elem.styles.computed.opacity + ';' |
|
} |
|
|
|
function _updateStore (elem) { |
|
var container = elem.config.container |
|
|
|
|
|
if (container && sr.store.containers.indexOf(container) === -1) { |
|
sr.store.containers.push(elem.config.container) |
|
} |
|
|
|
|
|
sr.store.elements[elem.id] = elem |
|
} |
|
|
|
function _record (target, config, interval) { |
|
|
|
|
|
var record = { |
|
target: target, |
|
config: config, |
|
interval: interval |
|
} |
|
sr.history.push(record) |
|
} |
|
|
|
function _init () { |
|
if (sr.isSupported()) { |
|
|
|
|
|
_animate() |
|
|
|
|
|
|
|
for (var i = 0; i < sr.store.containers.length; i++) { |
|
sr.store.containers[i].addEventListener('scroll', _handler) |
|
sr.store.containers[i].addEventListener('resize', _handler) |
|
} |
|
|
|
|
|
if (!sr.initialized) { |
|
window.addEventListener('scroll', _handler) |
|
window.addEventListener('resize', _handler) |
|
sr.initialized = true |
|
} |
|
} |
|
return sr |
|
} |
|
|
|
function _handler () { |
|
_requestAnimationFrame(_animate) |
|
} |
|
|
|
function _setActiveSequences () { |
|
var active |
|
var elem |
|
var elemId |
|
var sequence |
|
|
|
|
|
sr.tools.forOwn(sr.sequences, function (sequenceId) { |
|
sequence = sr.sequences[sequenceId] |
|
active = false |
|
|
|
|
|
|
|
for (var i = 0; i < sequence.elemIds.length; i++) { |
|
elemId = sequence.elemIds[i] |
|
elem = sr.store.elements[elemId] |
|
if (_isElemVisible(elem) && !active) { |
|
active = true |
|
} |
|
} |
|
|
|
sequence.active = active |
|
}) |
|
} |
|
|
|
function _animate () { |
|
var delayed |
|
var elem |
|
|
|
_setActiveSequences() |
|
|
|
|
|
sr.tools.forOwn(sr.store.elements, function (elemId) { |
|
elem = sr.store.elements[elemId] |
|
delayed = _shouldUseDelay(elem) |
|
|
|
|
|
|
|
|
|
if (_shouldReveal(elem)) { |
|
elem.config.beforeReveal(elem.domEl) |
|
if (delayed) { |
|
elem.domEl.setAttribute('style', |
|
elem.styles.inline + |
|
elem.styles.transform.target + |
|
elem.styles.transition.delayed |
|
) |
|
} else { |
|
elem.domEl.setAttribute('style', |
|
elem.styles.inline + |
|
elem.styles.transform.target + |
|
elem.styles.transition.instant |
|
) |
|
} |
|
|
|
|
|
|
|
_queueCallback('reveal', elem, delayed) |
|
elem.revealing = true |
|
elem.seen = true |
|
|
|
if (elem.sequence) { |
|
_queueNextInSequence(elem, delayed) |
|
} |
|
} else if (_shouldReset(elem)) { |
|
|
|
|
|
elem.config.beforeReset(elem.domEl) |
|
elem.domEl.setAttribute('style', |
|
elem.styles.inline + |
|
elem.styles.transform.initial + |
|
elem.styles.transition.instant |
|
) |
|
|
|
_queueCallback('reset', elem) |
|
elem.revealing = false |
|
} |
|
}) |
|
} |
|
|
|
function _queueNextInSequence (elem, delayed) { |
|
var elapsed = 0 |
|
var delay = 0 |
|
var sequence = sr.sequences[elem.sequence.id] |
|
|
|
|
|
sequence.blocked = true |
|
|
|
|
|
|
|
if (delayed && elem.config.useDelay === 'onload') { |
|
delay = elem.config.delay |
|
} |
|
|
|
|
|
if (elem.sequence.timer) { |
|
elapsed = Math.abs(elem.sequence.timer.started - new Date()) |
|
window.clearTimeout(elem.sequence.timer) |
|
} |
|
|
|
|
|
elem.sequence.timer = { started: new Date() } |
|
elem.sequence.timer.clock = window.setTimeout(function () { |
|
|
|
sequence.blocked = false |
|
elem.sequence.timer = null |
|
_handler() |
|
}, Math.abs(sequence.interval) + delay - elapsed) |
|
} |
|
|
|
function _queueCallback (type, elem, delayed) { |
|
var elapsed = 0 |
|
var duration = 0 |
|
var callback = 'after' |
|
|
|
|
|
switch (type) { |
|
case 'reveal': |
|
duration = elem.config.duration |
|
if (delayed) { |
|
duration += elem.config.delay |
|
} |
|
callback += 'Reveal' |
|
break |
|
|
|
case 'reset': |
|
duration = elem.config.duration |
|
callback += 'Reset' |
|
break |
|
} |
|
|
|
|
|
if (elem.timer) { |
|
elapsed = Math.abs(elem.timer.started - new Date()) |
|
window.clearTimeout(elem.timer.clock) |
|
} |
|
|
|
|
|
elem.timer = { started: new Date() } |
|
elem.timer.clock = window.setTimeout(function () { |
|
|
|
elem.config[callback](elem.domEl) |
|
elem.timer = null |
|
}, duration - elapsed) |
|
} |
|
|
|
function _shouldReveal (elem) { |
|
if (elem.sequence) { |
|
var sequence = sr.sequences[elem.sequence.id] |
|
return sequence.active && |
|
!sequence.blocked && |
|
!elem.revealing && |
|
!elem.disabled |
|
} |
|
return _isElemVisible(elem) && |
|
!elem.revealing && |
|
!elem.disabled |
|
} |
|
|
|
function _shouldUseDelay (elem) { |
|
var config = elem.config.useDelay |
|
return config === 'always' || |
|
(config === 'onload' && !sr.initialized) || |
|
(config === 'once' && !elem.seen) |
|
} |
|
|
|
function _shouldReset (elem) { |
|
if (elem.sequence) { |
|
var sequence = sr.sequences[elem.sequence.id] |
|
return !sequence.active && |
|
elem.config.reset && |
|
elem.revealing && |
|
!elem.disabled |
|
} |
|
return !_isElemVisible(elem) && |
|
elem.config.reset && |
|
elem.revealing && |
|
!elem.disabled |
|
} |
|
|
|
function _getContainer (container) { |
|
return { |
|
width: container.clientWidth, |
|
height: container.clientHeight |
|
} |
|
} |
|
|
|
function _getScrolled (container) { |
|
|
|
if (container && container !== window.document.documentElement) { |
|
var offset = _getOffset(container) |
|
return { |
|
x: container.scrollLeft + offset.left, |
|
y: container.scrollTop + offset.top |
|
} |
|
} else { |
|
|
|
return { |
|
x: window.pageXOffset, |
|
y: window.pageYOffset |
|
} |
|
} |
|
} |
|
|
|
function _getOffset (domEl) { |
|
var offsetTop = 0 |
|
var offsetLeft = 0 |
|
|
|
|
|
var offsetHeight = domEl.offsetHeight |
|
var offsetWidth = domEl.offsetWidth |
|
|
|
|
|
|
|
|
|
do { |
|
if (!isNaN(domEl.offsetTop)) { |
|
offsetTop += domEl.offsetTop |
|
} |
|
if (!isNaN(domEl.offsetLeft)) { |
|
offsetLeft += domEl.offsetLeft |
|
} |
|
domEl = domEl.offsetParent |
|
} while (domEl) |
|
|
|
return { |
|
top: offsetTop, |
|
left: offsetLeft, |
|
height: offsetHeight, |
|
width: offsetWidth |
|
} |
|
} |
|
|
|
function _isElemVisible (elem) { |
|
var offset = _getOffset(elem.domEl) |
|
var container = _getContainer(elem.config.container) |
|
var scrolled = _getScrolled(elem.config.container) |
|
var vF = elem.config.viewFactor |
|
|
|
|
|
var elemHeight = offset.height |
|
var elemWidth = offset.width |
|
var elemTop = offset.top |
|
var elemLeft = offset.left |
|
var elemBottom = elemTop + elemHeight |
|
var elemRight = elemLeft + elemWidth |
|
|
|
return confirmBounds() || isPositionFixed() |
|
|
|
function confirmBounds () { |
|
|
|
var top = elemTop + elemHeight * vF |
|
var left = elemLeft + elemWidth * vF |
|
var bottom = elemBottom - elemHeight * vF |
|
var right = elemRight - elemWidth * vF |
|
|
|
|
|
var viewTop = scrolled.y + elem.config.viewOffset.top |
|
var viewLeft = scrolled.x + elem.config.viewOffset.left |
|
var viewBottom = scrolled.y - elem.config.viewOffset.bottom + container.height |
|
var viewRight = scrolled.x - elem.config.viewOffset.right + container.width |
|
|
|
return top < viewBottom && |
|
bottom > viewTop && |
|
left < viewRight && |
|
right > viewLeft |
|
} |
|
|
|
function isPositionFixed () { |
|
return (window.getComputedStyle(elem.domEl).position === 'fixed') |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function Tools () {} |
|
|
|
Tools.prototype.isObject = function (object) { |
|
return object !== null && typeof object === 'object' && object.constructor === Object |
|
} |
|
|
|
Tools.prototype.isNode = function (object) { |
|
return typeof window.Node === 'object' |
|
? object instanceof window.Node |
|
: object && typeof object === 'object' && |
|
typeof object.nodeType === 'number' && |
|
typeof object.nodeName === 'string' |
|
} |
|
|
|
Tools.prototype.isNodeList = function (object) { |
|
var prototypeToString = Object.prototype.toString.call(object) |
|
var regex = /^\[object (HTMLCollection|NodeList|Object)\]$/ |
|
|
|
return typeof window.NodeList === 'object' |
|
? object instanceof window.NodeList |
|
: object && typeof object === 'object' && |
|
regex.test(prototypeToString) && |
|
typeof object.length === 'number' && |
|
(object.length === 0 || this.isNode(object[0])) |
|
} |
|
|
|
Tools.prototype.forOwn = function (object, callback) { |
|
if (!this.isObject(object)) { |
|
throw new TypeError('Expected "object", but received "' + typeof object + '".') |
|
} else { |
|
for (var property in object) { |
|
if (object.hasOwnProperty(property)) { |
|
callback(property) |
|
} |
|
} |
|
} |
|
} |
|
|
|
Tools.prototype.extend = function (target, source) { |
|
this.forOwn(source, function (property) { |
|
if (this.isObject(source[property])) { |
|
if (!target[property] || !this.isObject(target[property])) { |
|
target[property] = {} |
|
} |
|
this.extend(target[property], source[property]) |
|
} else { |
|
target[property] = source[property] |
|
} |
|
}.bind(this)) |
|
return target |
|
} |
|
|
|
Tools.prototype.extendClone = function (target, source) { |
|
return this.extend(this.extend({}, target), source) |
|
} |
|
|
|
Tools.prototype.isMobile = function () { |
|
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
_requestAnimationFrame = window.requestAnimationFrame || |
|
window.webkitRequestAnimationFrame || |
|
window.mozRequestAnimationFrame || |
|
function (callback) { |
|
window.setTimeout(callback, 1000 / 60) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) { |
|
define(function () { |
|
return ScrollReveal |
|
}) |
|
} else if (typeof module !== 'undefined' && module.exports) { |
|
module.exports = ScrollReveal |
|
} else { |
|
window.ScrollReveal = ScrollReveal |
|
} |
|
})(); |
|
|