scrollReveal.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. /*
  2. _ _ _____ _ _
  3. | | | __ \ | | (_)
  4. ___ ___ _ __ ___ | | | |__) |_____ _____ __ _| | _ ___
  5. / __|/ __| '__/ _ \| | | _ // _ \ \ / / _ \/ _` | | | / __|
  6. \__ \ (__| | | (_) | | | | \ \ __/\ V / __/ (_| | |_| \__ \
  7. |___/\___|_| \___/|_|_|_| \_\___| \_/ \___|\__,_|_(_) |___/ v.0.1.3
  8. _/ |
  9. |__/
  10. "Declarative on-scroll reveal animations."
  11. /*=============================================================================
  12. scrollReveal.js was inspired by cbpScroller.js (c) 2014 Codrops.
  13. Licensed under the MIT license.
  14. http://www.opensource.org/licenses/mit-license.php
  15. =============================================================================*/
  16. /*! scrollReveal.js v0.1.3 (c) 2014 Julian Lloyd | MIT license */
  17. /*===========================================================================*/
  18. window.scrollReveal = (function (window) {
  19. 'use strict';
  20. // generator (increments) for the next scroll-reveal-id
  21. var nextId = 1;
  22. /**
  23. * RequestAnimationFrame polyfill
  24. * @function
  25. * @private
  26. */
  27. var requestAnimFrame = (function () {
  28. return window.requestAnimationFrame ||
  29. window.webkitRequestAnimationFrame ||
  30. window.mozRequestAnimationFrame ||
  31. function (callback) {
  32. window.setTimeout(callback, 1000 / 60);
  33. };
  34. }());
  35. function scrollReveal(options) {
  36. this.options = this.extend(this.defaults, options);
  37. this.docElem = this.options.elem;
  38. this.styleBank = {};
  39. if (this.options.init == true) this.init();
  40. }
  41. scrollReveal.prototype = {
  42. defaults: {
  43. after: '0s',
  44. enter: 'bottom',
  45. move: '24px',
  46. over: '0.66s',
  47. easing: 'ease-in-out',
  48. opacity: 0,
  49. complete: function() {},
  50. // if 0, the element is considered in the viewport as soon as it enters
  51. // if 1, the element is considered in the viewport when it's fully visible
  52. viewportFactor: 0.33,
  53. // if false, animations occur only once
  54. // if true, animations occur each time an element enters the viewport
  55. reset: false,
  56. // if true, scrollReveal.init() is automaticaly called upon instantiation
  57. init: true,
  58. elem: window.document.documentElement
  59. },
  60. /*=============================================================================*/
  61. init: function () {
  62. this.scrolled = false;
  63. var self = this;
  64. // Check DOM for the data-scrollReveal attribute
  65. // and initialize all found elements.
  66. this.elems = Array.prototype.slice.call(this.docElem.querySelectorAll('[data-scroll-reveal]'));
  67. this.elems.forEach(function (el, i) {
  68. // Capture original style attribute
  69. var id = el.getAttribute("data-scroll-reveal-id");
  70. if (!id) {
  71. id = nextId++;
  72. el.setAttribute("data-scroll-reveal-id", id);
  73. }
  74. if (!self.styleBank[id]) {
  75. self.styleBank[id] = el.getAttribute('style');
  76. }
  77. self.update(el);
  78. });
  79. var scrollHandler = function (e) {
  80. // No changing, exit
  81. if (!self.scrolled) {
  82. self.scrolled = true;
  83. requestAnimFrame(function () {
  84. self._scrollPage();
  85. });
  86. }
  87. };
  88. var resizeHandler = function () {
  89. // If we’re still waiting for settimeout, reset the timer.
  90. if (self.resizeTimeout) {
  91. clearTimeout(self.resizeTimeout);
  92. }
  93. function delayed() {
  94. self._scrollPage();
  95. self.resizeTimeout = null;
  96. }
  97. self.resizeTimeout = setTimeout(delayed, 200);
  98. };
  99. // captureScroll
  100. if (this.docElem == window.document.documentElement) {
  101. window.addEventListener('scroll', scrollHandler, false);
  102. window.addEventListener('resize', resizeHandler, false);
  103. }
  104. else {
  105. this.docElem.addEventListener('scroll', scrollHandler, false);
  106. }
  107. },
  108. /*=============================================================================*/
  109. _scrollPage: function () {
  110. var self = this;
  111. this.elems.forEach(function (el, i) {
  112. self.update(el);
  113. });
  114. this.scrolled = false;
  115. },
  116. /*=============================================================================*/
  117. parseLanguage: function (el) {
  118. // Splits on a sequence of one or more commas or spaces.
  119. var words = el.getAttribute('data-scroll-reveal').split(/[, ]+/),
  120. parsed = {};
  121. function filter (words) {
  122. var ret = [],
  123. blacklist = [
  124. "from",
  125. "the",
  126. "and",
  127. "then",
  128. "but",
  129. "with"
  130. ];
  131. words.forEach(function (word, i) {
  132. if (blacklist.indexOf(word) > -1) {
  133. return;
  134. }
  135. ret.push(word);
  136. });
  137. return ret;
  138. }
  139. words = filter(words);
  140. words.forEach(function (word, i) {
  141. switch (word) {
  142. case "enter":
  143. parsed.enter = words[i + 1];
  144. return;
  145. case "after":
  146. parsed.after = words[i + 1];
  147. return;
  148. case "wait":
  149. parsed.after = words[i + 1];
  150. return;
  151. case "move":
  152. parsed.move = words[i + 1];
  153. return;
  154. case "ease":
  155. parsed.move = words[i + 1];
  156. parsed.ease = "ease";
  157. return;
  158. case "ease-in":
  159. parsed.move = words[i + 1];
  160. parsed.easing = "ease-in";
  161. return;
  162. case "ease-in-out":
  163. parsed.move = words[i + 1];
  164. parsed.easing = "ease-in-out";
  165. return;
  166. case "ease-out":
  167. parsed.move = words[i + 1];
  168. parsed.easing = "ease-out";
  169. return;
  170. case "over":
  171. parsed.over = words[i + 1];
  172. return;
  173. default:
  174. return;
  175. }
  176. });
  177. return parsed;
  178. },
  179. /*=============================================================================*/
  180. update: function (el) {
  181. var that = this;
  182. var css = this.genCSS(el);
  183. var style = this.styleBank[el.getAttribute("data-scroll-reveal-id")];
  184. if (style != null) style += ";"; else style = "";
  185. if (!el.getAttribute('data-scroll-reveal-initialized')) {
  186. el.setAttribute('style', style + css.initial);
  187. el.setAttribute('data-scroll-reveal-initialized', true);
  188. }
  189. if (!this.isElementInViewport(el, this.options.viewportFactor)) {
  190. if (this.options.reset) {
  191. el.setAttribute('style', style + css.initial + css.reset);
  192. }
  193. return;
  194. }
  195. if (el.getAttribute('data-scroll-reveal-complete')) return;
  196. if (this.isElementInViewport(el, this.options.viewportFactor)) {
  197. el.setAttribute('style', style + css.target + css.transition);
  198. // Without reset enabled, we can safely remove the style tag
  199. // to prevent CSS specificy wars with authored CSS.
  200. if (!this.options.reset) {
  201. setTimeout(function () {
  202. if (style != "") {
  203. el.setAttribute('style', style);
  204. } else {
  205. el.removeAttribute('style');
  206. }
  207. el.setAttribute('data-scroll-reveal-complete',true);
  208. that.options.complete(el);
  209. }, css.totalDuration);
  210. }
  211. return;
  212. }
  213. },
  214. /*=============================================================================*/
  215. genCSS: function (el) {
  216. var parsed = this.parseLanguage(el),
  217. enter,
  218. axis;
  219. if (parsed.enter) {
  220. if (parsed.enter == "top" || parsed.enter == "bottom") {
  221. enter = parsed.enter;
  222. axis = "y";
  223. }
  224. if (parsed.enter == "left" || parsed.enter == "right") {
  225. enter = parsed.enter;
  226. axis = "x";
  227. }
  228. } else {
  229. if (this.options.enter == "top" || this.options.enter == "bottom") {
  230. enter = this.options.enter
  231. axis = "y";
  232. }
  233. if (this.options.enter == "left" || this.options.enter == "right") {
  234. enter = this.options.enter
  235. axis = "x";
  236. }
  237. }
  238. // After all values are parsed, let’s make sure our our
  239. // pixel distance is negative for top and left entrances.
  240. //
  241. // ie. "move 25px from top" starts at 'top: -25px' in CSS.
  242. if (enter == "top" || enter == "left") {
  243. if (parsed.move) {
  244. parsed.move = "-" + parsed.move;
  245. }
  246. else {
  247. parsed.move = "-" + this.options.move;
  248. }
  249. }
  250. var dist = parsed.move || this.options.move,
  251. dur = parsed.over || this.options.over,
  252. delay = parsed.after || this.options.after,
  253. easing = parsed.easing || this.options.easing,
  254. opacity = parsed.opacity || this.options.opacity;
  255. var transition = "-webkit-transition: -webkit-transform " + dur + " " + easing + " " + delay + ", opacity " + dur + " " + easing + " " + delay + ";" +
  256. "transition: transform " + dur + " " + easing + " " + delay + ", opacity " + dur + " " + easing + " " + delay + ";" +
  257. "-webkit-perspective: 1000;" +
  258. "-webkit-backface-visibility: hidden;";
  259. // The same as transition, but removing the delay for elements fading out.
  260. var reset = "-webkit-transition: -webkit-transform " + dur + " " + easing + " 0s, opacity " + dur + " " + easing + " " + delay + ";" +
  261. "transition: transform " + dur + " " + easing + " 0s, opacity " + dur + " " + easing + " " + delay + ";" +
  262. "-webkit-perspective: 1000;" +
  263. "-webkit-backface-visibility: hidden;";
  264. var initial = "-webkit-transform: translate" + axis + "(" + dist + ");" +
  265. "transform: translate" + axis + "(" + dist + ");" +
  266. "opacity: " + opacity + ";";
  267. var target = "-webkit-transform: translate" + axis + "(0);" +
  268. "transform: translate" + axis + "(0);" +
  269. "opacity: 1;";
  270. return {
  271. transition: transition,
  272. initial: initial,
  273. target: target,
  274. reset: reset,
  275. totalDuration: ((parseFloat(dur) + parseFloat(delay)) * 1000)
  276. };
  277. },
  278. getViewportH : function () {
  279. var client = this.docElem['clientHeight'],
  280. inner = window['innerHeight'];
  281. if (this.docElem == window.document.documentElement)
  282. return (client < inner) ? inner : client;
  283. else
  284. return client;
  285. },
  286. getOffset : function(el) {
  287. var offsetTop = 0,
  288. offsetLeft = 0;
  289. do {
  290. if (!isNaN(el.offsetTop)) {
  291. offsetTop += el.offsetTop;
  292. }
  293. if (!isNaN(el.offsetLeft)) {
  294. offsetLeft += el.offsetLeft;
  295. }
  296. } while (el = el.offsetParent)
  297. return {
  298. top: offsetTop,
  299. left: offsetLeft
  300. }
  301. },
  302. isElementInViewport : function(el, h) {
  303. var scrolled = this.docElem.scrollTop + this.docElem.offsetTop;
  304. if (this.docElem == window.document.documentElement)scrolled = window.pageYOffset;
  305. var
  306. viewed = scrolled + this.getViewportH(),
  307. elH = el.offsetHeight,
  308. elTop = this.getOffset(el).top,
  309. elBottom = elTop + elH,
  310. h = h || 0;
  311. return (elTop + elH * h) <= viewed
  312. && (elBottom) >= scrolled
  313. || (el.currentStyle? el.currentStyle : window.getComputedStyle(el, null)).position == 'fixed';
  314. },
  315. extend: function (a, b){
  316. for (var key in b) {
  317. if (b.hasOwnProperty(key)) {
  318. a[key] = b[key];
  319. }
  320. }
  321. return a;
  322. }
  323. }; // end scrollReveal.prototype
  324. return scrollReveal;
  325. })(window);