You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

scrollreveal.js 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. ///// ///// ///// /////
  2. ///// ///// ///// /////
  3. ///// ///// ///// /////
  4. ///// ///// ///// /////
  5. ///// ///// /////
  6. ///// ///// /////
  7. ///// ///// ///// /////
  8. ///// ///// ///// /////
  9. ///// /////
  10. ///// /////
  11. ///// ///// ///// /////
  12. ///// ///// ///// /////
  13. ///// ///// ///// /////
  14. ///// ///// ///// /////
  15. /**
  16. * ScrollReveal
  17. * ------------
  18. * Version : 3.3.6
  19. * Website : scrollrevealjs.org
  20. * Repo : github.com/jlmakes/scrollreveal.js
  21. * Author : Julian Lloyd (@jlmakes)
  22. */
  23. ;(function () {
  24. 'use strict'
  25. var sr
  26. var _requestAnimationFrame
  27. function ScrollReveal (config) {
  28. // Support instantiation without the `new` keyword.
  29. if (typeof this === 'undefined' || Object.getPrototypeOf(this) !== ScrollReveal.prototype) {
  30. return new ScrollReveal(config)
  31. }
  32. sr = this // Save reference to instance.
  33. sr.version = '3.3.6'
  34. sr.tools = new Tools() // *required utilities
  35. if (sr.isSupported()) {
  36. sr.tools.extend(sr.defaults, config || {})
  37. sr.defaults.container = _resolveContainer(sr.defaults)
  38. sr.store = {
  39. elements: {},
  40. containers: []
  41. }
  42. sr.sequences = {}
  43. sr.history = []
  44. sr.uid = 0
  45. sr.initialized = false
  46. } else if (typeof console !== 'undefined' && console !== null) {
  47. // Note: IE9 only supports console if devtools are open.
  48. console.log('ScrollReveal is not supported in this browser.')
  49. }
  50. return sr
  51. }
  52. /**
  53. * Configuration
  54. * -------------
  55. * This object signature can be passed directly to the ScrollReveal constructor,
  56. * or as the second argument of the `reveal()` method.
  57. */
  58. ScrollReveal.prototype.defaults = {
  59. // 'bottom', 'left', 'top', 'right'
  60. origin: 'bottom',
  61. // Can be any valid CSS distance, e.g. '5rem', '10%', '20vw', etc.
  62. distance: '20px',
  63. // Time in milliseconds.
  64. duration: 500,
  65. delay: 0,
  66. // Starting angles in degrees, will transition from these values to 0 in all axes.
  67. rotate: { x: 0, y: 0, z: 0 },
  68. // Starting opacity value, before transitioning to the computed opacity.
  69. opacity: 0,
  70. // Starting scale value, will transition from this value to 1
  71. scale: 0.9,
  72. // Accepts any valid CSS easing, e.g. 'ease', 'ease-in-out', 'linear', etc.
  73. easing: 'cubic-bezier(0.6, 0.2, 0.1, 1)',
  74. // `<html>` is the default reveal container. You can pass either:
  75. // DOM Node, e.g. document.querySelector('.fooContainer')
  76. // Selector, e.g. '.fooContainer'
  77. container: window.document.documentElement,
  78. // true/false to control reveal animations on mobile.
  79. mobile: true,
  80. // true: reveals occur every time elements become visible
  81. // false: reveals occur once as elements become visible
  82. reset: false,
  83. // 'always' — delay for all reveal animations
  84. // 'once' — delay only the first time reveals occur
  85. // 'onload' - delay only for animations triggered by first load
  86. useDelay: 'always',
  87. // Change when an element is considered in the viewport. The default value
  88. // of 0.20 means 20% of an element must be visible for its reveal to occur.
  89. viewFactor: 0.2,
  90. // Pixel values that alter the container boundaries.
  91. // e.g. Set `{ top: 48 }`, if you have a 48px tall fixed toolbar.
  92. // --
  93. // Visual Aid: https://scrollrevealjs.org/assets/viewoffset.png
  94. viewOffset: { top: 0, right: 0, bottom: 0, left: 0 },
  95. // Callbacks that fire for each triggered element reveal, and reset.
  96. beforeReveal: function (domEl) {},
  97. beforeReset: function (domEl) {},
  98. // Callbacks that fire for each completed element reveal, and reset.
  99. afterReveal: function (domEl) {},
  100. afterReset: function (domEl) {}
  101. }
  102. /**
  103. * Check if client supports CSS Transform and CSS Transition.
  104. * @return {boolean}
  105. */
  106. ScrollReveal.prototype.isSupported = function () {
  107. var style = document.documentElement.style
  108. return 'WebkitTransition' in style && 'WebkitTransform' in style ||
  109. 'transition' in style && 'transform' in style
  110. }
  111. /**
  112. * Creates a reveal set, a group of elements that will animate when they
  113. * become visible. If [interval] is provided, a new sequence is created
  114. * that will ensure elements reveal in the order they appear in the DOM.
  115. *
  116. * @param {Node|NodeList|string} [target] The node, node list or selector to use for animation.
  117. * @param {Object} [config] Override the defaults for this reveal set.
  118. * @param {number} [interval] Time between sequenced element animations (milliseconds).
  119. * @param {boolean} [sync] Used internally when updating reveals for async content.
  120. *
  121. * @return {Object} The current ScrollReveal instance.
  122. */
  123. ScrollReveal.prototype.reveal = function (target, config, interval, sync) {
  124. var container
  125. var elements
  126. var elem
  127. var elemId
  128. var sequence
  129. var sequenceId
  130. // No custom configuration was passed, but a sequence interval instead.
  131. // let’s shuffle things around to make sure everything works.
  132. if (config !== undefined && typeof config === 'number') {
  133. interval = config
  134. config = {}
  135. } else if (config === undefined || config === null) {
  136. config = {}
  137. }
  138. container = _resolveContainer(config)
  139. elements = _getRevealElements(target, container)
  140. if (!elements.length) {
  141. console.log('ScrollReveal: reveal on "' + target + '" failed, no elements found.')
  142. return sr
  143. }
  144. // Prepare a new sequence if an interval is passed.
  145. if (interval && typeof interval === 'number') {
  146. sequenceId = _nextUid()
  147. sequence = sr.sequences[sequenceId] = {
  148. id: sequenceId,
  149. interval: interval,
  150. elemIds: [],
  151. active: false
  152. }
  153. }
  154. // Begin main loop to configure ScrollReveal elements.
  155. for (var i = 0; i < elements.length; i++) {
  156. // Check if the element has already been configured and grab it from the store.
  157. elemId = elements[i].getAttribute('data-sr-id')
  158. if (elemId) {
  159. elem = sr.store.elements[elemId]
  160. } else {
  161. // Otherwise, let’s do some basic setup.
  162. elem = {
  163. id: _nextUid(),
  164. domEl: elements[i],
  165. seen: false,
  166. revealing: false
  167. }
  168. elem.domEl.setAttribute('data-sr-id', elem.id)
  169. }
  170. // Sequence only setup
  171. if (sequence) {
  172. elem.sequence = {
  173. id: sequence.id,
  174. index: sequence.elemIds.length
  175. }
  176. sequence.elemIds.push(elem.id)
  177. }
  178. // New or existing element, it’s time to update its configuration, styles,
  179. // and send the updates to our store.
  180. _configure(elem, config, container)
  181. _style(elem)
  182. _updateStore(elem)
  183. // We need to make sure elements are set to visibility: visible, even when
  184. // on mobile and `config.mobile === false`, or if unsupported.
  185. if (sr.tools.isMobile() && !elem.config.mobile || !sr.isSupported()) {
  186. elem.domEl.setAttribute('style', elem.styles.inline)
  187. elem.disabled = true
  188. } else if (!elem.revealing) {
  189. // Otherwise, proceed normally.
  190. elem.domEl.setAttribute('style',
  191. elem.styles.inline +
  192. elem.styles.transform.initial
  193. )
  194. }
  195. }
  196. // Each `reveal()` is recorded so that when calling `sync()` while working
  197. // with asynchronously loaded content, it can re-trace your steps but with
  198. // all your new elements now in the DOM.
  199. // Since `reveal()` is called internally by `sync()`, we don’t want to
  200. // record or intiialize each reveal during syncing.
  201. if (!sync && sr.isSupported()) {
  202. _record(target, config, interval)
  203. // We push initialization to the event queue using setTimeout, so that we can
  204. // give ScrollReveal room to process all reveal calls before putting things into motion.
  205. // --
  206. // Philip Roberts - What the heck is the event loop anyway? (JSConf EU 2014)
  207. // https://www.youtube.com/watch?v=8aGhZQkoFbQ
  208. if (sr.initTimeout) {
  209. window.clearTimeout(sr.initTimeout)
  210. }
  211. sr.initTimeout = window.setTimeout(_init, 0)
  212. }
  213. return sr
  214. }
  215. /**
  216. * Re-runs `reveal()` for each record stored in history, effectively capturing
  217. * any content loaded asynchronously that matches existing reveal set targets.
  218. * @return {Object} The current ScrollReveal instance.
  219. */
  220. ScrollReveal.prototype.sync = function () {
  221. if (sr.history.length && sr.isSupported()) {
  222. for (var i = 0; i < sr.history.length; i++) {
  223. var record = sr.history[i]
  224. sr.reveal(record.target, record.config, record.interval, true)
  225. }
  226. _init()
  227. } else {
  228. console.log('ScrollReveal: sync failed, no reveals found.')
  229. }
  230. return sr
  231. }
  232. /**
  233. * Private Methods
  234. * ---------------
  235. */
  236. function _resolveContainer (config) {
  237. if (config && config.container) {
  238. if (typeof config.container === 'string') {
  239. return window.document.documentElement.querySelector(config.container)
  240. } else if (sr.tools.isNode(config.container)) {
  241. return config.container
  242. } else {
  243. console.log('ScrollReveal: invalid container "' + config.container + '" provided.')
  244. console.log('ScrollReveal: falling back to default container.')
  245. }
  246. }
  247. return sr.defaults.container
  248. }
  249. /**
  250. * check to see if a node or node list was passed in as the target,
  251. * otherwise query the container using target as a selector.
  252. *
  253. * @param {Node|NodeList|string} [target] client input for reveal target.
  254. * @param {Node} [container] parent element for selector queries.
  255. *
  256. * @return {array} elements to be revealed.
  257. */
  258. function _getRevealElements (target, container) {
  259. if (typeof target === 'string') {
  260. return Array.prototype.slice.call(container.querySelectorAll(target))
  261. } else if (sr.tools.isNode(target)) {
  262. return [target]
  263. } else if (sr.tools.isNodeList(target)) {
  264. return Array.prototype.slice.call(target)
  265. }
  266. return []
  267. }
  268. /**
  269. * A consistent way of creating unique IDs.
  270. * @returns {number}
  271. */
  272. function _nextUid () {
  273. return ++sr.uid
  274. }
  275. function _configure (elem, config, container) {
  276. // If a container was passed as a part of the config object,
  277. // let’s overwrite it with the resolved container passed in.
  278. if (config.container) config.container = container
  279. // If the element hasn’t already been configured, let’s use a clone of the
  280. // defaults extended by the configuration passed as the second argument.
  281. if (!elem.config) {
  282. elem.config = sr.tools.extendClone(sr.defaults, config)
  283. } else {
  284. // Otherwise, let’s use a clone of the existing element configuration extended
  285. // by the configuration passed as the second argument.
  286. elem.config = sr.tools.extendClone(elem.config, config)
  287. }
  288. // Infer CSS Transform axis from origin string.
  289. if (elem.config.origin === 'top' || elem.config.origin === 'bottom') {
  290. elem.config.axis = 'Y'
  291. } else {
  292. elem.config.axis = 'X'
  293. }
  294. }
  295. function _style (elem) {
  296. var computed = window.getComputedStyle(elem.domEl)
  297. if (!elem.styles) {
  298. elem.styles = {
  299. transition: {},
  300. transform: {},
  301. computed: {}
  302. }
  303. // Capture any existing inline styles, and add our visibility override.
  304. // --
  305. // See section 4.2. in the Documentation:
  306. // https://github.com/jlmakes/scrollreveal.js#42-improve-user-experience
  307. elem.styles.inline = elem.domEl.getAttribute('style') || ''
  308. elem.styles.inline += '; visibility: visible; '
  309. // grab the elements existing opacity.
  310. elem.styles.computed.opacity = computed.opacity
  311. // grab the elements existing transitions.
  312. if (!computed.transition || computed.transition === 'all 0s ease 0s') {
  313. elem.styles.computed.transition = ''
  314. } else {
  315. elem.styles.computed.transition = computed.transition + ', '
  316. }
  317. }
  318. // Create transition styles
  319. elem.styles.transition.instant = _generateTransition(elem, 0)
  320. elem.styles.transition.delayed = _generateTransition(elem, elem.config.delay)
  321. // Generate transform styles, first with the webkit prefix.
  322. elem.styles.transform.initial = ' -webkit-transform:'
  323. elem.styles.transform.target = ' -webkit-transform:'
  324. _generateTransform(elem)
  325. // And again without any prefix.
  326. elem.styles.transform.initial += 'transform:'
  327. elem.styles.transform.target += 'transform:'
  328. _generateTransform(elem)
  329. }
  330. function _generateTransition (elem, delay) {
  331. var config = elem.config
  332. return '-webkit-transition: ' + elem.styles.computed.transition +
  333. '-webkit-transform ' + config.duration / 1000 + 's ' +
  334. config.easing + ' ' +
  335. delay / 1000 + 's, opacity ' +
  336. config.duration / 1000 + 's ' +
  337. config.easing + ' ' +
  338. delay / 1000 + 's; ' +
  339. 'transition: ' + elem.styles.computed.transition +
  340. 'transform ' + config.duration / 1000 + 's ' +
  341. config.easing + ' ' +
  342. delay / 1000 + 's, opacity ' +
  343. config.duration / 1000 + 's ' +
  344. config.easing + ' ' +
  345. delay / 1000 + 's; '
  346. }
  347. function _generateTransform (elem) {
  348. var config = elem.config
  349. var cssDistance
  350. var transform = elem.styles.transform
  351. // Let’s make sure our our pixel distances are negative for top and left.
  352. // e.g. origin = 'top' and distance = '25px' starts at `top: -25px` in CSS.
  353. if (config.origin === 'top' || config.origin === 'left') {
  354. cssDistance = /^-/.test(config.distance)
  355. ? config.distance.substr(1)
  356. : '-' + config.distance
  357. } else {
  358. cssDistance = config.distance
  359. }
  360. if (parseInt(config.distance)) {
  361. transform.initial += ' translate' + config.axis + '(' + cssDistance + ')'
  362. transform.target += ' translate' + config.axis + '(0)'
  363. }
  364. if (config.scale) {
  365. transform.initial += ' scale(' + config.scale + ')'
  366. transform.target += ' scale(1)'
  367. }
  368. if (config.rotate.x) {
  369. transform.initial += ' rotateX(' + config.rotate.x + 'deg)'
  370. transform.target += ' rotateX(0)'
  371. }
  372. if (config.rotate.y) {
  373. transform.initial += ' rotateY(' + config.rotate.y + 'deg)'
  374. transform.target += ' rotateY(0)'
  375. }
  376. if (config.rotate.z) {
  377. transform.initial += ' rotateZ(' + config.rotate.z + 'deg)'
  378. transform.target += ' rotateZ(0)'
  379. }
  380. transform.initial += '; opacity: ' + config.opacity + ';'
  381. transform.target += '; opacity: ' + elem.styles.computed.opacity + ';'
  382. }
  383. function _updateStore (elem) {
  384. var container = elem.config.container
  385. // If this element’s container isn’t already in the store, let’s add it.
  386. if (container && sr.store.containers.indexOf(container) === -1) {
  387. sr.store.containers.push(elem.config.container)
  388. }
  389. // Update the element stored with our new element.
  390. sr.store.elements[elem.id] = elem
  391. }
  392. function _record (target, config, interval) {
  393. // Save the `reveal()` arguments that triggered this `_record()` call, so we
  394. // can re-trace our steps when calling the `sync()` method.
  395. var record = {
  396. target: target,
  397. config: config,
  398. interval: interval
  399. }
  400. sr.history.push(record)
  401. }
  402. function _init () {
  403. if (sr.isSupported()) {
  404. // Initial animate call triggers valid reveal animations on first load.
  405. // Subsequent animate calls are made inside the event handler.
  406. _animate()
  407. // Then we loop through all container nodes in the store and bind event
  408. // listeners to each.
  409. for (var i = 0; i < sr.store.containers.length; i++) {
  410. sr.store.containers[i].addEventListener('scroll', _handler)
  411. sr.store.containers[i].addEventListener('resize', _handler)
  412. }
  413. // Let’s also do a one-time binding of window event listeners.
  414. if (!sr.initialized) {
  415. window.addEventListener('scroll', _handler)
  416. window.addEventListener('resize', _handler)
  417. sr.initialized = true
  418. }
  419. }
  420. return sr
  421. }
  422. function _handler () {
  423. _requestAnimationFrame(_animate)
  424. }
  425. function _setActiveSequences () {
  426. var active
  427. var elem
  428. var elemId
  429. var sequence
  430. // Loop through all sequences
  431. sr.tools.forOwn(sr.sequences, function (sequenceId) {
  432. sequence = sr.sequences[sequenceId]
  433. active = false
  434. // For each sequenced elemenet, let’s check visibility and if
  435. // any are visible, set it’s sequence to active.
  436. for (var i = 0; i < sequence.elemIds.length; i++) {
  437. elemId = sequence.elemIds[i]
  438. elem = sr.store.elements[elemId]
  439. if (_isElemVisible(elem) && !active) {
  440. active = true
  441. }
  442. }
  443. sequence.active = active
  444. })
  445. }
  446. function _animate () {
  447. var delayed
  448. var elem
  449. _setActiveSequences()
  450. // Loop through all elements in the store
  451. sr.tools.forOwn(sr.store.elements, function (elemId) {
  452. elem = sr.store.elements[elemId]
  453. delayed = _shouldUseDelay(elem)
  454. // Let’s see if we should revealand if so,
  455. // trigger the `beforeReveal` callback and
  456. // determine whether or not to use delay.
  457. if (_shouldReveal(elem)) {
  458. elem.config.beforeReveal(elem.domEl)
  459. if (delayed) {
  460. elem.domEl.setAttribute('style',
  461. elem.styles.inline +
  462. elem.styles.transform.target +
  463. elem.styles.transition.delayed
  464. )
  465. } else {
  466. elem.domEl.setAttribute('style',
  467. elem.styles.inline +
  468. elem.styles.transform.target +
  469. elem.styles.transition.instant
  470. )
  471. }
  472. // Let’s queue the `afterReveal` callback
  473. // and mark the element as seen and revealing.
  474. _queueCallback('reveal', elem, delayed)
  475. elem.revealing = true
  476. elem.seen = true
  477. if (elem.sequence) {
  478. _queueNextInSequence(elem, delayed)
  479. }
  480. } else if (_shouldReset(elem)) {
  481. //Otherwise reset our element and
  482. // trigger the `beforeReset` callback.
  483. elem.config.beforeReset(elem.domEl)
  484. elem.domEl.setAttribute('style',
  485. elem.styles.inline +
  486. elem.styles.transform.initial +
  487. elem.styles.transition.instant
  488. )
  489. // And queue the `afterReset` callback.
  490. _queueCallback('reset', elem)
  491. elem.revealing = false
  492. }
  493. })
  494. }
  495. function _queueNextInSequence (elem, delayed) {
  496. var elapsed = 0
  497. var delay = 0
  498. var sequence = sr.sequences[elem.sequence.id]
  499. // We’re processing a sequenced element, so let's block other elements in this sequence.
  500. sequence.blocked = true
  501. // Since we’re triggering animations a part of a sequence after animations on first load,
  502. // we need to check for that condition and explicitly add the delay to our timer.
  503. if (delayed && elem.config.useDelay === 'onload') {
  504. delay = elem.config.delay
  505. }
  506. // If a sequence timer is already running, capture the elapsed time and clear it.
  507. if (elem.sequence.timer) {
  508. elapsed = Math.abs(elem.sequence.timer.started - new Date())
  509. window.clearTimeout(elem.sequence.timer)
  510. }
  511. // Start a new timer.
  512. elem.sequence.timer = { started: new Date() }
  513. elem.sequence.timer.clock = window.setTimeout(function () {
  514. // Sequence interval has passed, so unblock the sequence and re-run the handler.
  515. sequence.blocked = false
  516. elem.sequence.timer = null
  517. _handler()
  518. }, Math.abs(sequence.interval) + delay - elapsed)
  519. }
  520. function _queueCallback (type, elem, delayed) {
  521. var elapsed = 0
  522. var duration = 0
  523. var callback = 'after'
  524. // Check which callback we’re working with.
  525. switch (type) {
  526. case 'reveal':
  527. duration = elem.config.duration
  528. if (delayed) {
  529. duration += elem.config.delay
  530. }
  531. callback += 'Reveal'
  532. break
  533. case 'reset':
  534. duration = elem.config.duration
  535. callback += 'Reset'
  536. break
  537. }
  538. // If a timer is already running, capture the elapsed time and clear it.
  539. if (elem.timer) {
  540. elapsed = Math.abs(elem.timer.started - new Date())
  541. window.clearTimeout(elem.timer.clock)
  542. }
  543. // Start a new timer.
  544. elem.timer = { started: new Date() }
  545. elem.timer.clock = window.setTimeout(function () {
  546. // The timer completed, so let’s fire the callback and null the timer.
  547. elem.config[callback](elem.domEl)
  548. elem.timer = null
  549. }, duration - elapsed)
  550. }
  551. function _shouldReveal (elem) {
  552. if (elem.sequence) {
  553. var sequence = sr.sequences[elem.sequence.id]
  554. return sequence.active &&
  555. !sequence.blocked &&
  556. !elem.revealing &&
  557. !elem.disabled
  558. }
  559. return _isElemVisible(elem) &&
  560. !elem.revealing &&
  561. !elem.disabled
  562. }
  563. function _shouldUseDelay (elem) {
  564. var config = elem.config.useDelay
  565. return config === 'always' ||
  566. (config === 'onload' && !sr.initialized) ||
  567. (config === 'once' && !elem.seen)
  568. }
  569. function _shouldReset (elem) {
  570. if (elem.sequence) {
  571. var sequence = sr.sequences[elem.sequence.id]
  572. return !sequence.active &&
  573. elem.config.reset &&
  574. elem.revealing &&
  575. !elem.disabled
  576. }
  577. return !_isElemVisible(elem) &&
  578. elem.config.reset &&
  579. elem.revealing &&
  580. !elem.disabled
  581. }
  582. function _getContainer (container) {
  583. return {
  584. width: container.clientWidth,
  585. height: container.clientHeight
  586. }
  587. }
  588. function _getScrolled (container) {
  589. // Return the container scroll values, plus the its offset.
  590. if (container && container !== window.document.documentElement) {
  591. var offset = _getOffset(container)
  592. return {
  593. x: container.scrollLeft + offset.left,
  594. y: container.scrollTop + offset.top
  595. }
  596. } else {
  597. // Otherwise, default to the window object’s scroll values.
  598. return {
  599. x: window.pageXOffset,
  600. y: window.pageYOffset
  601. }
  602. }
  603. }
  604. function _getOffset (domEl) {
  605. var offsetTop = 0
  606. var offsetLeft = 0
  607. // Grab the element’s dimensions.
  608. var offsetHeight = domEl.offsetHeight
  609. var offsetWidth = domEl.offsetWidth
  610. // Now calculate the distance between the element and its parent, then
  611. // again for the parent to its parent, and again etc... until we have the
  612. // total distance of the element to the document’s top and left origin.
  613. do {
  614. if (!isNaN(domEl.offsetTop)) {
  615. offsetTop += domEl.offsetTop
  616. }
  617. if (!isNaN(domEl.offsetLeft)) {
  618. offsetLeft += domEl.offsetLeft
  619. }
  620. domEl = domEl.offsetParent
  621. } while (domEl)
  622. return {
  623. top: offsetTop,
  624. left: offsetLeft,
  625. height: offsetHeight,
  626. width: offsetWidth
  627. }
  628. }
  629. function _isElemVisible (elem) {
  630. var offset = _getOffset(elem.domEl)
  631. var container = _getContainer(elem.config.container)
  632. var scrolled = _getScrolled(elem.config.container)
  633. var vF = elem.config.viewFactor
  634. // Define the element geometry.
  635. var elemHeight = offset.height
  636. var elemWidth = offset.width
  637. var elemTop = offset.top
  638. var elemLeft = offset.left
  639. var elemBottom = elemTop + elemHeight
  640. var elemRight = elemLeft + elemWidth
  641. return confirmBounds() || isPositionFixed()
  642. function confirmBounds () {
  643. // Define the element’s functional boundaries using its view factor.
  644. var top = elemTop + elemHeight * vF
  645. var left = elemLeft + elemWidth * vF
  646. var bottom = elemBottom - elemHeight * vF
  647. var right = elemRight - elemWidth * vF
  648. // Define the container functional boundaries using its view offset.
  649. var viewTop = scrolled.y + elem.config.viewOffset.top
  650. var viewLeft = scrolled.x + elem.config.viewOffset.left
  651. var viewBottom = scrolled.y - elem.config.viewOffset.bottom + container.height
  652. var viewRight = scrolled.x - elem.config.viewOffset.right + container.width
  653. return top < viewBottom &&
  654. bottom > viewTop &&
  655. left < viewRight &&
  656. right > viewLeft
  657. }
  658. function isPositionFixed () {
  659. return (window.getComputedStyle(elem.domEl).position === 'fixed')
  660. }
  661. }
  662. /**
  663. * Utilities
  664. * ---------
  665. */
  666. function Tools () {}
  667. Tools.prototype.isObject = function (object) {
  668. return object !== null && typeof object === 'object' && object.constructor === Object
  669. }
  670. Tools.prototype.isNode = function (object) {
  671. return typeof window.Node === 'object'
  672. ? object instanceof window.Node
  673. : object && typeof object === 'object' &&
  674. typeof object.nodeType === 'number' &&
  675. typeof object.nodeName === 'string'
  676. }
  677. Tools.prototype.isNodeList = function (object) {
  678. var prototypeToString = Object.prototype.toString.call(object)
  679. var regex = /^\[object (HTMLCollection|NodeList|Object)\]$/
  680. return typeof window.NodeList === 'object'
  681. ? object instanceof window.NodeList
  682. : object && typeof object === 'object' &&
  683. regex.test(prototypeToString) &&
  684. typeof object.length === 'number' &&
  685. (object.length === 0 || this.isNode(object[0]))
  686. }
  687. Tools.prototype.forOwn = function (object, callback) {
  688. if (!this.isObject(object)) {
  689. throw new TypeError('Expected "object", but received "' + typeof object + '".')
  690. } else {
  691. for (var property in object) {
  692. if (object.hasOwnProperty(property)) {
  693. callback(property)
  694. }
  695. }
  696. }
  697. }
  698. Tools.prototype.extend = function (target, source) {
  699. this.forOwn(source, function (property) {
  700. if (this.isObject(source[property])) {
  701. if (!target[property] || !this.isObject(target[property])) {
  702. target[property] = {}
  703. }
  704. this.extend(target[property], source[property])
  705. } else {
  706. target[property] = source[property]
  707. }
  708. }.bind(this))
  709. return target
  710. }
  711. Tools.prototype.extendClone = function (target, source) {
  712. return this.extend(this.extend({}, target), source)
  713. }
  714. Tools.prototype.isMobile = function () {
  715. return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
  716. }
  717. /**
  718. * Polyfills
  719. * --------
  720. */
  721. _requestAnimationFrame = window.requestAnimationFrame ||
  722. window.webkitRequestAnimationFrame ||
  723. window.mozRequestAnimationFrame ||
  724. function (callback) {
  725. window.setTimeout(callback, 1000 / 60)
  726. }
  727. /**
  728. * Module Wrapper
  729. * --------------
  730. */
  731. if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
  732. define(function () {
  733. return ScrollReveal
  734. })
  735. } else if (typeof module !== 'undefined' && module.exports) {
  736. module.exports = ScrollReveal
  737. } else {
  738. window.ScrollReveal = ScrollReveal
  739. }
  740. })();