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.

859 lines
22 KiB

  1. 'use strict';
  2. var setupEnv, requestAnimFrame, cancelAnimFrame, parsePositiveInt;
  3. /**
  4. * Vivus
  5. * Beta version
  6. *
  7. * Take any SVG and make the animation
  8. * to give give the impression of live drawing
  9. *
  10. * This in more than just inspired from codrops
  11. * At that point, it's a pure fork.
  12. */
  13. /**
  14. * Class constructor
  15. * option structure
  16. * type: 'delayed'|'sync'|'oneByOne'|'script' (to know if the items must be drawn synchronously or not, default: delayed)
  17. * duration: <int> (in frames)
  18. * start: 'inViewport'|'manual'|'autostart' (start automatically the animation, default: inViewport)
  19. * delay: <int> (delay between the drawing of first and last path)
  20. * dashGap <integer> whitespace extra margin between dashes
  21. * pathTimingFunction <function> timing animation function for each path element of the SVG
  22. * animTimingFunction <function> timing animation function for the complete SVG
  23. * forceRender <boolean> force the browser to re-render all updated path items
  24. * selfDestroy <boolean> removes all extra styling on the SVG, and leaves it as original
  25. *
  26. * The attribute 'type' is by default on 'delayed'.
  27. * - 'delayed'
  28. * all paths are draw at the same time but with a
  29. * little delay between them before start
  30. * - 'sync'
  31. * all path are start and finish at the same time
  32. * - 'oneByOne'
  33. * only one path is draw at the time
  34. * the end of the first one will trigger the draw
  35. * of the next one
  36. *
  37. * All these values can be overwritten individually
  38. * for each path item in the SVG
  39. * The value of frames will always take the advantage of
  40. * the duration value.
  41. * If you fail somewhere, an error will be thrown.
  42. * Good luck.
  43. *
  44. * @constructor
  45. * @this {Vivus}
  46. * @param {DOM|String} element Dom element of the SVG or id of it
  47. * @param {Object} options Options about the animation
  48. * @param {Function} callback Callback for the end of the animation
  49. */
  50. function Vivus(element, options, callback) {
  51. setupEnv();
  52. // Setup
  53. this.isReady = false;
  54. this.setElement(element, options);
  55. this.setOptions(options);
  56. this.setCallback(callback);
  57. if (this.isReady) {
  58. this.init();
  59. }
  60. }
  61. /**
  62. * Timing functions
  63. **************************************
  64. *
  65. * Default functions to help developers.
  66. * It always take a number as parameter (between 0 to 1) then
  67. * return a number (between 0 and 1)
  68. */
  69. Vivus.LINEAR = function(x) {
  70. return x;
  71. };
  72. Vivus.EASE = function(x) {
  73. return -Math.cos(x * Math.PI) / 2 + 0.5;
  74. };
  75. Vivus.EASE_OUT = function(x) {
  76. return 1 - Math.pow(1 - x, 3);
  77. };
  78. Vivus.EASE_IN = function(x) {
  79. return Math.pow(x, 3);
  80. };
  81. Vivus.EASE_OUT_BOUNCE = function(x) {
  82. var base = -Math.cos(x * (0.5 * Math.PI)) + 1,
  83. rate = Math.pow(base, 1.5),
  84. rateR = Math.pow(1 - x, 2),
  85. progress = -Math.abs(Math.cos(rate * (2.5 * Math.PI))) + 1;
  86. return 1 - rateR + progress * rateR;
  87. };
  88. /**
  89. * Setters
  90. **************************************
  91. */
  92. /**
  93. * Check and set the element in the instance
  94. * The method will not return anything, but will throw an
  95. * error if the parameter is invalid
  96. *
  97. * @param {DOM|String} element SVG Dom element or id of it
  98. */
  99. Vivus.prototype.setElement = function(element, options) {
  100. var onLoad, self;
  101. // Basic check
  102. if (typeof element === 'undefined') {
  103. throw new Error('Vivus [constructor]: "element" parameter is required');
  104. }
  105. // Set the element
  106. if (element.constructor === String) {
  107. element = document.getElementById(element);
  108. if (!element) {
  109. throw new Error(
  110. 'Vivus [constructor]: "element" parameter is not related to an existing ID'
  111. );
  112. }
  113. }
  114. this.parentEl = element;
  115. // Load the SVG with XMLHttpRequest and extract the SVG
  116. if (options && options.file) {
  117. self = this;
  118. onLoad = function() {
  119. var domSandbox = document.createElement('div');
  120. domSandbox.innerHTML = this.responseText;
  121. var svgTag = domSandbox.querySelector('svg');
  122. if (!svgTag) {
  123. throw new Error(
  124. 'Vivus [load]: Cannot find the SVG in the loaded file : ' +
  125. options.file
  126. );
  127. }
  128. self.el = svgTag;
  129. self.el.setAttribute('width', '100%');
  130. self.el.setAttribute('height', '100%');
  131. self.parentEl.appendChild(self.el);
  132. self.isReady = true;
  133. self.init();
  134. self = null;
  135. };
  136. var oReq = new window.XMLHttpRequest();
  137. oReq.addEventListener('load', onLoad);
  138. oReq.open('GET', options.file);
  139. oReq.send();
  140. return;
  141. }
  142. switch (element.constructor) {
  143. case window.SVGSVGElement:
  144. case window.SVGElement:
  145. case window.SVGGElement:
  146. this.el = element;
  147. this.isReady = true;
  148. break;
  149. case window.HTMLObjectElement:
  150. self = this;
  151. onLoad = function(e) {
  152. if (self.isReady) {
  153. return;
  154. }
  155. self.el =
  156. element.contentDocument &&
  157. element.contentDocument.querySelector('svg');
  158. if (!self.el && e) {
  159. throw new Error(
  160. 'Vivus [constructor]: object loaded does not contain any SVG'
  161. );
  162. } else if (self.el) {
  163. if (element.getAttribute('built-by-vivus')) {
  164. self.parentEl.insertBefore(self.el, element);
  165. self.parentEl.removeChild(element);
  166. self.el.setAttribute('width', '100%');
  167. self.el.setAttribute('height', '100%');
  168. }
  169. self.isReady = true;
  170. self.init();
  171. self = null;
  172. }
  173. };
  174. if (!onLoad()) {
  175. element.addEventListener('load', onLoad);
  176. }
  177. break;
  178. default:
  179. throw new Error(
  180. 'Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)'
  181. );
  182. }
  183. };
  184. /**
  185. * Set up user option to the instance
  186. * The method will not return anything, but will throw an
  187. * error if the parameter is invalid
  188. *
  189. * @param {object} options Object from the constructor
  190. */
  191. Vivus.prototype.setOptions = function(options) {
  192. var allowedTypes = [
  193. 'delayed',
  194. 'sync',
  195. 'async',
  196. 'nsync',
  197. 'oneByOne',
  198. 'scenario',
  199. 'scenario-sync'
  200. ];
  201. var allowedStarts = ['inViewport', 'manual', 'autostart'];
  202. // Basic check
  203. if (options !== undefined && options.constructor !== Object) {
  204. throw new Error(
  205. 'Vivus [constructor]: "options" parameter must be an object'
  206. );
  207. } else {
  208. options = options || {};
  209. }
  210. // Set the animation type
  211. if (options.type && allowedTypes.indexOf(options.type) === -1) {
  212. throw new Error(
  213. 'Vivus [constructor]: ' +
  214. options.type +
  215. ' is not an existing animation `type`'
  216. );
  217. } else {
  218. this.type = options.type || allowedTypes[0];
  219. }
  220. // Set the start type
  221. if (options.start && allowedStarts.indexOf(options.start) === -1) {
  222. throw new Error(
  223. 'Vivus [constructor]: ' +
  224. options.start +
  225. ' is not an existing `start` option'
  226. );
  227. } else {
  228. this.start = options.start || allowedStarts[0];
  229. }
  230. this.isIE =
  231. window.navigator.userAgent.indexOf('MSIE') !== -1 ||
  232. window.navigator.userAgent.indexOf('Trident/') !== -1 ||
  233. window.navigator.userAgent.indexOf('Edge/') !== -1;
  234. this.duration = parsePositiveInt(options.duration, 120);
  235. this.delay = parsePositiveInt(options.delay, null);
  236. this.dashGap = parsePositiveInt(options.dashGap, 1);
  237. this.forceRender = options.hasOwnProperty('forceRender')
  238. ? !!options.forceRender
  239. : this.isIE;
  240. this.reverseStack = !!options.reverseStack;
  241. this.selfDestroy = !!options.selfDestroy;
  242. this.onReady = options.onReady;
  243. this.map = [];
  244. this.frameLength = this.currentFrame = this.delayUnit = this.speed = this.handle = null;
  245. this.ignoreInvisible = options.hasOwnProperty('ignoreInvisible')
  246. ? !!options.ignoreInvisible
  247. : false;
  248. this.animTimingFunction = options.animTimingFunction || Vivus.LINEAR;
  249. this.pathTimingFunction = options.pathTimingFunction || Vivus.LINEAR;
  250. if (this.delay >= this.duration) {
  251. throw new Error('Vivus [constructor]: delay must be shorter than duration');
  252. }
  253. };
  254. /**
  255. * Set up callback to the instance
  256. * The method will not return enything, but will throw an
  257. * error if the parameter is invalid
  258. *
  259. * @param {Function} callback Callback for the animation end
  260. */
  261. Vivus.prototype.setCallback = function(callback) {
  262. // Basic check
  263. if (!!callback && callback.constructor !== Function) {
  264. throw new Error(
  265. 'Vivus [constructor]: "callback" parameter must be a function'
  266. );
  267. }
  268. this.callback = callback || function() {};
  269. };
  270. /**
  271. * Core
  272. **************************************
  273. */
  274. /**
  275. * Map the svg, path by path.
  276. * The method return nothing, it just fill the
  277. * `map` array. Each item in this array represent
  278. * a path element from the SVG, with informations for
  279. * the animation.
  280. *
  281. * ```
  282. * [
  283. * {
  284. * el: <DOMobj> the path element
  285. * length: <number> length of the path line
  286. * startAt: <number> time start of the path animation (in frames)
  287. * duration: <number> path animation duration (in frames)
  288. * },
  289. * ...
  290. * ]
  291. * ```
  292. *
  293. */
  294. Vivus.prototype.mapping = function() {
  295. var i, paths, path, pAttrs, pathObj, totalLength, lengthMeter, timePoint;
  296. timePoint = totalLength = lengthMeter = 0;
  297. paths = this.el.querySelectorAll('path');
  298. for (i = 0; i < paths.length; i++) {
  299. path = paths[i];
  300. if (this.isInvisible(path)) {
  301. continue;
  302. }
  303. pathObj = {
  304. el: path,
  305. length: Math.ceil(path.getTotalLength())
  306. };
  307. // Test if the path length is correct
  308. if (isNaN(pathObj.length)) {
  309. if (window.console && console.warn) {
  310. console.warn(
  311. 'Vivus [mapping]: cannot retrieve a path element length',
  312. path
  313. );
  314. }
  315. continue;
  316. }
  317. this.map.push(pathObj);
  318. path.style.strokeDasharray =
  319. pathObj.length + ' ' + (pathObj.length + this.dashGap * 2);
  320. path.style.strokeDashoffset = pathObj.length + this.dashGap;
  321. pathObj.length += this.dashGap;
  322. totalLength += pathObj.length;
  323. this.renderPath(i);
  324. }
  325. totalLength = totalLength === 0 ? 1 : totalLength;
  326. this.delay = this.delay === null ? this.duration / 3 : this.delay;
  327. this.delayUnit = this.delay / (paths.length > 1 ? paths.length - 1 : 1);
  328. // Reverse stack if asked
  329. if (this.reverseStack) {
  330. this.map.reverse();
  331. }
  332. for (i = 0; i < this.map.length; i++) {
  333. pathObj = this.map[i];
  334. switch (this.type) {
  335. case 'delayed':
  336. pathObj.startAt = this.delayUnit * i;
  337. pathObj.duration = this.duration - this.delay;
  338. break;
  339. case 'oneByOne':
  340. pathObj.startAt = (lengthMeter / totalLength) * this.duration;
  341. pathObj.duration = (pathObj.length / totalLength) * this.duration;
  342. break;
  343. case 'sync':
  344. case 'async':
  345. case 'nsync':
  346. pathObj.startAt = 0;
  347. pathObj.duration = this.duration;
  348. break;
  349. case 'scenario-sync':
  350. path = pathObj.el;
  351. pAttrs = this.parseAttr(path);
  352. pathObj.startAt =
  353. timePoint +
  354. (parsePositiveInt(pAttrs['data-delay'], this.delayUnit) || 0);
  355. pathObj.duration = parsePositiveInt(
  356. pAttrs['data-duration'],
  357. this.duration
  358. );
  359. timePoint =
  360. pAttrs['data-async'] !== undefined
  361. ? pathObj.startAt
  362. : pathObj.startAt + pathObj.duration;
  363. this.frameLength = Math.max(
  364. this.frameLength,
  365. pathObj.startAt + pathObj.duration
  366. );
  367. break;
  368. case 'scenario':
  369. path = pathObj.el;
  370. pAttrs = this.parseAttr(path);
  371. pathObj.startAt =
  372. parsePositiveInt(pAttrs['data-start'], this.delayUnit) || 0;
  373. pathObj.duration = parsePositiveInt(
  374. pAttrs['data-duration'],
  375. this.duration
  376. );
  377. this.frameLength = Math.max(
  378. this.frameLength,
  379. pathObj.startAt + pathObj.duration
  380. );
  381. break;
  382. }
  383. lengthMeter += pathObj.length;
  384. this.frameLength = this.frameLength || this.duration;
  385. }
  386. };
  387. /**
  388. * Interval method to draw the SVG from current
  389. * position of the animation. It update the value of
  390. * `currentFrame` and re-trace the SVG.
  391. *
  392. * It use this.handle to store the requestAnimationFrame
  393. * and clear it one the animation is stopped. So this
  394. * attribute can be used to know if the animation is
  395. * playing.
  396. *
  397. * Once the animation at the end, this method will
  398. * trigger the Vivus callback.
  399. *
  400. */
  401. Vivus.prototype.drawer = function() {
  402. var self = this;
  403. this.currentFrame += this.speed;
  404. if (this.currentFrame <= 0) {
  405. this.stop();
  406. this.reset();
  407. } else if (this.currentFrame >= this.frameLength) {
  408. this.stop();
  409. this.currentFrame = this.frameLength;
  410. this.trace();
  411. if (this.selfDestroy) {
  412. this.destroy();
  413. }
  414. } else {
  415. this.trace();
  416. this.handle = requestAnimFrame(function() {
  417. self.drawer();
  418. });
  419. return;
  420. }
  421. this.callback(this);
  422. if (this.instanceCallback) {
  423. this.instanceCallback(this);
  424. this.instanceCallback = null;
  425. }
  426. };
  427. /**
  428. * Draw the SVG at the current instant from the
  429. * `currentFrame` value. Here is where most of the magic is.
  430. * The trick is to use the `strokeDashoffset` style property.
  431. *
  432. * For optimisation reasons, a new property called `progress`
  433. * is added in each item of `map`. This one contain the current
  434. * progress of the path element. Only if the new value is different
  435. * the new value will be applied to the DOM element. This
  436. * method save a lot of resources to re-render the SVG. And could
  437. * be improved if the animation couldn't be played forward.
  438. *
  439. */
  440. Vivus.prototype.trace = function() {
  441. var i, progress, path, currentFrame;
  442. currentFrame =
  443. this.animTimingFunction(this.currentFrame / this.frameLength) *
  444. this.frameLength;
  445. for (i = 0; i < this.map.length; i++) {
  446. path = this.map[i];
  447. progress = (currentFrame - path.startAt) / path.duration;
  448. progress = this.pathTimingFunction(Math.max(0, Math.min(1, progress)));
  449. if (path.progress !== progress) {
  450. path.progress = progress;
  451. path.el.style.strokeDashoffset = Math.floor(path.length * (1 - progress));
  452. this.renderPath(i);
  453. }
  454. }
  455. };
  456. /**
  457. * Method forcing the browser to re-render a path element
  458. * from it's index in the map. Depending on the `forceRender`
  459. * value.
  460. * The trick is to replace the path element by it's clone.
  461. * This practice is not recommended because it's asking more
  462. * ressources, too much DOM manupulation..
  463. * but it's the only way to let the magic happen on IE.
  464. * By default, this fallback is only applied on IE.
  465. *
  466. * @param {Number} index Path index
  467. */
  468. Vivus.prototype.renderPath = function(index) {
  469. if (this.forceRender && this.map && this.map[index]) {
  470. var pathObj = this.map[index],
  471. newPath = pathObj.el.cloneNode(true);
  472. pathObj.el.parentNode.replaceChild(newPath, pathObj.el);
  473. pathObj.el = newPath;
  474. }
  475. };
  476. /**
  477. * When the SVG object is loaded and ready,
  478. * this method will continue the initialisation.
  479. *
  480. * This this mainly due to the case of passing an
  481. * object tag in the constructor. It will wait
  482. * the end of the loading to initialise.
  483. *
  484. */
  485. Vivus.prototype.init = function() {
  486. // Set object variables
  487. this.frameLength = 0;
  488. this.currentFrame = 0;
  489. this.map = [];
  490. // Start
  491. new Pathformer(this.el);
  492. this.mapping();
  493. this.starter();
  494. if (this.onReady) {
  495. this.onReady(this);
  496. }
  497. };
  498. /**
  499. * Trigger to start of the animation.
  500. * Depending on the `start` value, a different script
  501. * will be applied.
  502. *
  503. * If the `start` value is not valid, an error will be thrown.
  504. * Even if technically, this is impossible.
  505. *
  506. */
  507. Vivus.prototype.starter = function() {
  508. switch (this.start) {
  509. case 'manual':
  510. return;
  511. case 'autostart':
  512. this.play();
  513. break;
  514. case 'inViewport':
  515. var self = this,
  516. listener = function() {
  517. if (self.isInViewport(self.parentEl, 1)) {
  518. self.play();
  519. window.removeEventListener('scroll', listener);
  520. }
  521. };
  522. window.addEventListener('scroll', listener);
  523. listener();
  524. break;
  525. }
  526. };
  527. /**
  528. * Controls
  529. **************************************
  530. */
  531. /**
  532. * Get the current status of the animation between
  533. * three different states: 'start', 'progress', 'end'.
  534. * @return {string} Instance status
  535. */
  536. Vivus.prototype.getStatus = function() {
  537. return this.currentFrame === 0
  538. ? 'start'
  539. : this.currentFrame === this.frameLength
  540. ? 'end'
  541. : 'progress';
  542. };
  543. /**
  544. * Reset the instance to the initial state : undraw
  545. * Be careful, it just reset the animation, if you're
  546. * playing the animation, this won't stop it. But just
  547. * make it start from start.
  548. *
  549. */
  550. Vivus.prototype.reset = function() {
  551. return this.setFrameProgress(0);
  552. };
  553. /**
  554. * Set the instance to the final state : drawn
  555. * Be careful, it just set the animation, if you're
  556. * playing the animation on rewind, this won't stop it.
  557. * But just make it start from the end.
  558. *
  559. */
  560. Vivus.prototype.finish = function() {
  561. return this.setFrameProgress(1);
  562. };
  563. /**
  564. * Set the level of progress of the drawing.
  565. *
  566. * @param {number} progress Level of progress to set
  567. */
  568. Vivus.prototype.setFrameProgress = function(progress) {
  569. progress = Math.min(1, Math.max(0, progress));
  570. this.currentFrame = Math.round(this.frameLength * progress);
  571. this.trace();
  572. return this;
  573. };
  574. /**
  575. * Play the animation at the desired speed.
  576. * Speed must be a valid number (no zero).
  577. * By default, the speed value is 1.
  578. * But a negative value is accepted to go forward.
  579. *
  580. * And works with float too.
  581. * But don't forget we are in JavaScript, se be nice
  582. * with him and give him a 1/2^x value.
  583. *
  584. * @param {number} speed Animation speed [optional]
  585. */
  586. Vivus.prototype.play = function(speed, callback) {
  587. this.instanceCallback = null;
  588. if (speed && typeof speed === 'function') {
  589. this.instanceCallback = speed; // first parameter is actually the callback function
  590. speed = null;
  591. } else if (speed && typeof speed !== 'number') {
  592. throw new Error('Vivus [play]: invalid speed');
  593. }
  594. // if the first parameter wasn't the callback, check if the seconds was
  595. if (callback && typeof callback === 'function' && !this.instanceCallback) {
  596. this.instanceCallback = callback;
  597. }
  598. this.speed = speed || 1;
  599. if (!this.handle) {
  600. this.drawer();
  601. }
  602. return this;
  603. };
  604. /**
  605. * Stop the current animation, if on progress.
  606. * Should not trigger any error.
  607. *
  608. */
  609. Vivus.prototype.stop = function() {
  610. if (this.handle) {
  611. cancelAnimFrame(this.handle);
  612. this.handle = null;
  613. }
  614. return this;
  615. };
  616. /**
  617. * Destroy the instance.
  618. * Remove all bad styling attributes on all
  619. * path tags
  620. *
  621. */
  622. Vivus.prototype.destroy = function() {
  623. this.stop();
  624. var i, path;
  625. for (i = 0; i < this.map.length; i++) {
  626. path = this.map[i];
  627. path.el.style.strokeDashoffset = null;
  628. path.el.style.strokeDasharray = null;
  629. this.renderPath(i);
  630. }
  631. };
  632. /**
  633. * Utils methods
  634. * include methods from Codrops
  635. **************************************
  636. */
  637. /**
  638. * Method to best guess if a path should added into
  639. * the animation or not.
  640. *
  641. * 1. Use the `data-vivus-ignore` attribute if set
  642. * 2. Check if the instance must ignore invisible paths
  643. * 3. Check if the path is visible
  644. *
  645. * For now the visibility checking is unstable.
  646. * It will be used for a beta phase.
  647. *
  648. * Other improvments are planned. Like detecting
  649. * is the path got a stroke or a valid opacity.
  650. */
  651. Vivus.prototype.isInvisible = function(el) {
  652. var rect,
  653. ignoreAttr = el.getAttribute('data-ignore');
  654. if (ignoreAttr !== null) {
  655. return ignoreAttr !== 'false';
  656. }
  657. if (this.ignoreInvisible) {
  658. rect = el.getBoundingClientRect();
  659. return !rect.width && !rect.height;
  660. } else {
  661. return false;
  662. }
  663. };
  664. /**
  665. * Parse attributes of a DOM element to
  666. * get an object of {attributeName => attributeValue}
  667. *
  668. * @param {object} element DOM element to parse
  669. * @return {object} Object of attributes
  670. */
  671. Vivus.prototype.parseAttr = function(element) {
  672. var attr,
  673. output = {};
  674. if (element && element.attributes) {
  675. for (var i = 0; i < element.attributes.length; i++) {
  676. attr = element.attributes[i];
  677. output[attr.name] = attr.value;
  678. }
  679. }
  680. return output;
  681. };
  682. /**
  683. * Reply if an element is in the page viewport
  684. *
  685. * @param {object} el Element to observe
  686. * @param {number} h Percentage of height
  687. * @return {boolean}
  688. */
  689. Vivus.prototype.isInViewport = function(el, h) {
  690. var scrolled = this.scrollY(),
  691. viewed = scrolled + this.getViewportH(),
  692. elBCR = el.getBoundingClientRect(),
  693. elHeight = elBCR.height,
  694. elTop = scrolled + elBCR.top,
  695. elBottom = elTop + elHeight;
  696. // if 0, the element is considered in the viewport as soon as it enters.
  697. // if 1, the element is considered in the viewport only when it's fully inside
  698. // value in percentage (1 >= h >= 0)
  699. h = h || 0;
  700. return elTop + elHeight * h <= viewed && elBottom >= scrolled;
  701. };
  702. /**
  703. * Get the viewport height in pixels
  704. *
  705. * @return {integer} Viewport height
  706. */
  707. Vivus.prototype.getViewportH = function() {
  708. var client = this.docElem.clientHeight,
  709. inner = window.innerHeight;
  710. if (client < inner) {
  711. return inner;
  712. } else {
  713. return client;
  714. }
  715. };
  716. /**
  717. * Get the page Y offset
  718. *
  719. * @return {integer} Page Y offset
  720. */
  721. Vivus.prototype.scrollY = function() {
  722. return window.pageYOffset || this.docElem.scrollTop;
  723. };
  724. setupEnv = function() {
  725. if (Vivus.prototype.docElem) {
  726. return;
  727. }
  728. /**
  729. * Alias for document element
  730. *
  731. * @type {DOMelement}
  732. */
  733. Vivus.prototype.docElem = window.document.documentElement;
  734. /**
  735. * Alias for `requestAnimationFrame` or
  736. * `setTimeout` function for deprecated browsers.
  737. *
  738. */
  739. requestAnimFrame = (function() {
  740. return (
  741. window.requestAnimationFrame ||
  742. window.webkitRequestAnimationFrame ||
  743. window.mozRequestAnimationFrame ||
  744. window.oRequestAnimationFrame ||
  745. window.msRequestAnimationFrame ||
  746. function(/* function */ callback) {
  747. return window.setTimeout(callback, 1000 / 60);
  748. }
  749. );
  750. })();
  751. /**
  752. * Alias for `cancelAnimationFrame` or
  753. * `cancelTimeout` function for deprecated browsers.
  754. *
  755. */
  756. cancelAnimFrame = (function() {
  757. return (
  758. window.cancelAnimationFrame ||
  759. window.webkitCancelAnimationFrame ||
  760. window.mozCancelAnimationFrame ||
  761. window.oCancelAnimationFrame ||
  762. window.msCancelAnimationFrame ||
  763. function(id) {
  764. return window.clearTimeout(id);
  765. }
  766. );
  767. })();
  768. };
  769. /**
  770. * Parse string to integer.
  771. * If the number is not positive or null
  772. * the method will return the default value
  773. * or 0 if undefined
  774. *
  775. * @param {string} value String to parse
  776. * @param {*} defaultValue Value to return if the result parsed is invalid
  777. * @return {number}
  778. *
  779. */
  780. parsePositiveInt = function(value, defaultValue) {
  781. var output = parseInt(value, 10);
  782. return output >= 0 ? output : defaultValue;
  783. };