1 2 3'use strict'; 4 5(function (window, document) { 6 7 'use strict'; 8 9function Pathformer(element) { 10 // Test params 11 if (typeof element === 'undefined') { 12 throw new Error('Pathformer [constructor]: "element" parameter is required'); 13 } 14 15 // Set the element 16 if (element.constructor === String) { 17 element = document.getElementById(element); 18 if (!element) { 19 throw new Error('Pathformer [constructor]: "element" parameter is not related to an existing ID'); 20 } 21 } 22 if (element.constructor instanceof window.SVGElement || /^svg$/i.test(element.nodeName)) { 23 this.el = element; 24 } else { 25 throw new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement'); 26 } 27 28 // Start 29 this.scan(element); 30} 31 32/** 33 * List of tags which can be transformed 34 * to path elements 35 * 36 * @type {Array} 37 */ 38Pathformer.prototype.TYPES = ['line', 'ellipse', 'circle', 'polygon', 'polyline', 'rect']; 39 40/** 41 * List of attribute names which contain 42 * data. This array list them to check if 43 * they contain bad values, like percentage. 44 * 45 * @type {Array} 46 */ 47Pathformer.prototype.ATTR_WATCH = ['cx', 'cy', 'points', 'r', 'rx', 'ry', 'x', 'x1', 'x2', 'y', '
47y1', 'y2']; 48 49/** 50 * Finds the elements compatible for transform 51 * and apply the liked method 52 * 53 * @param {object} options Object from the constructor 54 */ 55Pathformer.prototype.scan = function (svg) { 56 var fn, element, pathData, pathDom, 57 elements = svg.querySelectorAll(this.TYPES.join(',')); 58 for (var i = 0; i < elements.length; i++) { 59 element = elements[i]; 60 fn = this[element.tagName.toLowerCase() + 'ToPath']; 61 pathData = fn(this.parseAttr(element.attributes)); 62 pathDom = this.pathMaker(element, pathData); 63 element.parentNode.replaceChild(pathDom, element); 64 } 65}; 66 67 68/** 69 * Read `line` element to extract and transform 70 * data, to make it ready for a `path` object. 71 * 72 * @param {DOMelement} element Line element to transform 73 * @return {object} Data for a `path` element 74 */ 75Pathformer.prototype.lineToPath = function (element) { 76 var newElement = {}; 77 newElement.d = 'M' + element.x1 + ',' + element.y1 + 'L' + element.x2 + ',' + element.y2; 78 return newElement; 79}; 80 81/** 82 * Read `rect` element to extract and transform 83 * data, to make it ready for a `path` object. 84 * The radius-border is not taken in charge yet. 85 * (your help is more than welcomed) 86 * 87 * @param {DOMelement} element Rect element to transform 88 * @return {object} Data for a `path` element 89 */ 90Pathformer.prototype.rectToPath = function (element) { 91 var newElement = {}, 92 x = parseFloat(element.x) || 0, 93 y = parseFloat(element.y) || 0, 94 width = parseFloat(element.width) || 0, 95 height = parseFloat(element.height) || 0; 96 newElement.d = 'M' + x + ' ' + y + ' '; 97 newElement.d += 'L' + (x + width) + ' ' + y + ' '; 98 newElement.d += 'L' + (x + width) + ' ' + (y + height) + ' '; 99 newElement.d += 'L' + x + ' ' + (y + height) + ' Z'; 100 return newElement; 101}; 102 103/** 104 * Read `polyline` element to extract and transform 105 * data, to make it ready for a `path` object. 106 * 107 * @param {DOMelement} element Polyline element to transform 108 * @return {object} Data for a `path` element 109 */ 110Pathformer.prototype.polylineToPath = function (element) { 111 var i, path; 112 var newElement = {}; 113 var points = element.points.trim().split(' '); 114 115 // Reformatting if points are defined without commas 116 if (element.points.indexOf(',') === -1) { 117 var formattedPoints = []; 118 for (i = 0; i < points.length; i+=2) { 119 formattedPoints.push(points[i] + ',' + points[i+1]); 120 } 121 points = formattedPoints; 122 } 123 124 // Generate the path.d value 125 path = 'M' + points[0]; 126 for(i = 1; i < points.length; i++) { 127 if (points[i].indexOf(',') !== -1) { 128 path += 'L' + points[i]; 129 } 130 } 131 newElement.d = path; 132 return newElement; 133}; 134 135/** 136 * Read `polygon` element to extract and transform 137 * data, to make it ready for a `path` object. 138 * This method rely on polylineToPath, because the 139 * logic is similar. The path created is just closed, 140 * so it needs an 'Z' at the end. 141 * 142 * @param {DOMelement} element Polygon element to transform 143 * @return {object} Data for a `path` element 144 */ 145Pathformer.prototype.polygonToPath = function (element) { 146 var newElement = Pathformer.prototype.polylineToPath(element); 147 newElement.d += 'Z'; 148 return newElement; 149}; 150 151/** 152 * Read `ellipse` element to extract and transform 153 * data, to make it ready for a `path` object. 154 * 155 * @param {DOMelement} element ellipse element to transform 156 * @return {object} Data for a `path` element 157 */ 158Pathformer.prototype.ellipseToPath = function (element) { 159 var startX = element.cx - element.rx, 160 startY = element.cy; 161 var endX = parseFloat(element.cx) + parseFloat(element.rx), 162 endY = element.cy; 163 164 var newElement = {}; 165 newElement.d = 'M' + startX + ',' + startY + 166 'A' + element.rx + ',' + element.ry + ' 0,1,1 ' + endX + ',' + endY + 167 'A' + element.rx + ',' + element.ry + ' 0,1,1 ' + startX + ',' + endY; 168 return newElement; 169}; 170 171/** 172 * Read `circle` element to extract and transform 173 * data, to make it ready for a `path` object. 174 * 175 * @param {DOMelement} element Circle element to transform 176 * @return {object} Data for a `path` element 177 */ 178Pathformer.prototype.circleToPath = function (element) { 179 var newElement = {}; 180 var startX = element.cx - element.r, 181 startY = element.cy; 182 var endX = parseFloat(element.cx) + parseFloat(element.r), 183 endY = element.cy; 184 newElement.d = 'M' + startX + ',' + startY + 185 'A' + element.r + ',' + element.r + ' 0,1,1 ' + endX + ',' + endY + 186 'A' + element.r + ',' + element.r + ' 0,1,1 ' + startX + ',' + endY; 187 return newElement; 188}; 189 190/** 191 * Create `path` elements form original element 192 * and prepared objects 193 * 194 * @param {DOMelement} element Original element to transform 195 * @param {object} pathData Path data (from `toPath` methods) 196 * @return {DOMelement} Path element 197 */ 198Pathformer.prototype.pathMaker = function (element, pathData) { 199 var i, attr, pathTag = document.createElementNS('http://www.w3.org/2000/svg','path'); 200 for(i = 0; i < element.attributes.length; i++) { 201 attr = element.attributes[i];
vendor: 4,144 bytes, lines 202-325
202 if (this.ATTR_WATCH.indexOf(attr.name) === -1) { 203 pathTag.setAttribute(attr.name, attr.value); 204 } 205 } 206 for(i in pathData) { 207 pathTag.setAttribute(i, pathData[i]); 208 } 209 return pathTag; 210}; 211 212/** 213 * Parse attributes of a DOM element to 214 * get an object of attribute => value 215 * 216 * @param {NamedNodeMap} attributes Attributes object from DOM element to parse 217 * @return {object} Object of attributes 218 */ 219Pathformer.prototype.parseAttr = function (element) { 220 var attr, output = {}; 221 for (var i = 0; i < element.length; i++) { 222 attr = element[i]; 223 // Check if no data attribute contains '%', or the transformation is impossible 224 if (this.ATTR_WATCH.indexOf(attr.name) !== -1 && attr.value.indexOf('%') !== -1) { 225 throw new Error('Pathformer [parseAttr]: a SVG shape got values in percentage. This cannot be transformed into \'path\' tags. Please use \'viewBox\'.'); 226 } 227 output[attr.name] = attr.value; 228 } 229 return output; 230}; 231 232 'use strict'; 233 234var requestAnimFrame, cancelAnimFrame, parsePositiveInt; 235 236/** 237 * Vivus 238 * Beta version 239 * 240 * Take any SVG and make the animation 241 * to give give the impression of live drawing 242 * 243 * This in more than just inspired from codrops 244 * At that point, it's a pure fork. 245 */ 246 247/** 248 * Class constructor 249 * option structure 250 * type: 'delayed'|'async'|'oneByOne'|'script' (to know if the item must be drawn asynchronously or not, default: delayed) 251 * duration: <int> (in frames) 252 * start: 'inViewport'|'manual'|'autostart' (start automatically the animation, default: inViewport) 253 * delay: <int> (delay between the drawing of first and last path) 254 * dashGap <integer> whitespace extra margin between dashes 255 * pathTimingFunction <function> timing animation function for each path element of the SVG 256 * animTimingFunction <function> timing animation function for the complete SVG 257 * forceRender <boolean> force the browser to re-render all updated path items 258 * selfDestroy <boolean> removes all extra styling on the SVG, and leaves it as original 259 * 260 * The attribute 'type' is by default on 'delayed'. 261 * - 'delayed' 262 * all paths are draw at the same time but with a 263 * little delay between them before start 264 * - 'async' 265 * all path are start and finish at the same time 266 * - 'oneByOne' 267 * only one path is draw at the time 268 * the end of the first one will trigger the draw 269 * of the next one 270 * 271 * All these values can be overwritten individually 272 * for each path item in the SVG 273 * The value of frames will always take the advantage of 274 * the duration value. 275 * If you fail somewhere, an error will be thrown. 276 * Good luck. 277 * 278 * @constructor 279 * @this {Vivus} 280 * @param {DOM|String} element Dom element of the SVG or id of it 281 * @param {Object} options Options about the animation 282 * @param {Function} callback Callback for the end of the animation 283 */ 284function Vivus (element, options, callback) { 285 286 // Setup 287 this.isReady = false; 288 this.setElement(element, options); 289 this.setOptions(options); 290 this.setCallback(callback); 291 292 if (this.isReady) { 293 this.init(); 294 } 295} 296 297/** 298 * Timing functions 299 ************************************** 300 * 301 * Default functions to help developers. 302 * It always take a number as parameter (between 0 to 1) then 303 * return a number (between 0 and 1) 304 */ 305Vivus.LINEAR = function (x) {return x;}; 306Vivus.EASE = function (x) {return -Math.cos(x * Math.PI) / 2 + 0.5;}; 307Vivus.EASE_OUT = function (x) {return 1 - Math.pow(1-x, 3);}; 308Vivus.EASE_IN = function (x) {return Math.pow(x, 3);}; 309Vivus.EASE_OUT_BOUNCE = function (x) { 310 var base = -Math.cos(x * (0.5 * Math.PI)) + 1, 311 rate = Math.pow(base,1.5), 312 rateR = Math.pow(1 - x, 2), 313 progress = -Math.abs(Math.cos(rate * (2.5 * Math.PI) )) + 1; 314 return (1- rateR) + (progress * rateR); 315}; 316 317 318/** 319 * Setters 320 ************************************** 321 */ 322 323/** 324 * Check and set the element in the instance 325 * The method will not return anything, but will throw an
326 * error if the parameter is invalid 327 * 328 * @param {DOM|String} element SVG Dom element or id of it 329 */ 330Vivus.prototype.setElement = function (element, options) { 331 // Basic check 332 if (typeof element === 'undefined') { 333 throw new Error('Vivus [constructor]: "element" parameter is required'); 334 } 335 336 // Set the element 337 if (element.constructor === String) { 338 element = document.getElementById(element); 339 if (!element) { 340 throw new Error('Vivus [constructor]: "element" parameter is not related to an existing ID'); 341 } 342 } 343 this.parentEl = element; 344 345 // Create the object element if the property `file` exists in the options object 346 if (options && options.file) { 347 var objElm = document.createElement('object'); 348 objElm.setAttribute('type', 'image/svg+xml'); 349 objElm.setAttribute('data', options.file); 350 objElm.setAttribute('built-by-vivus', 'true'); 351 element.appendChild(objElm); 352 element = objElm; 353 } 354 355 switch (element.constructor) { 356 case window.SVGSVGElement: 357 case window.SVGElement: 358 this.el = element; 359 this.isReady = true; 360 break; 361 362 case window.HTMLObjectElement: 363 // If we have to wait for it 364 var onLoad, self; 365 366 self = this; 367 onLoad = function (e) { 368 if (self.isReady) { 369 return; 370 } 371 self.el = element.contentDocument && element.contentDocument.querySelector('svg'); 372 if (!self.el && e) { 373 throw new Error('Vivus [constructor]: object loaded does not contain any SVG'); 374 } 375 else if (self.el) { 376 if (element.getAttribute('built-by-vivus')) { 377 self.parentEl.insertBefore(self.el, element); 378 self.parentEl.removeChild(element); 379 self.el.setAttribute('width', '100%'); 380 self.el.setAttribute('height', '100%'); 381 } 382 self.isReady = true; 383 self.init(); 384 return true; 385 } 386 }; 387 388 if (!onLoad()) { 389 element.addEventListener('load', onLoad); 390 } 391 break; 392 393 default: 394 throw new Error('Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)'); 395 } 396}; 397 398/** 399 * Set up user option to the instance 400 * The method will not return anything, but will throw an 401 * error if the parameter is invalid 402 * 403 * @param {object} options Object from the constructor 404 */ 405Vivus.prototype.setOptions = function (options) { 406 var allowedTypes = ['delayed', 'async', 'oneByOne', 'scenario', 'scenario-sync']; 407 var allowedStarts = ['inViewport', 'manual', 'autostart']; 408 409 // Basic check 410 if (options !== undefined && options.constructor !== Object) { 411 throw new Error('Vivus [constructor]: "options" parameter must be an object'); 412 } 413 else { 414 options = options || {}; 415 } 416 417 // Set the animation type 418 if (options.type && allowedTypes.indexOf(options.type) === -1) { 419 throw new Error('Vivus [constructor]: ' + options.type + ' is not an existing animation `type`'); 420 } 421 else { 422 this.type = options.type || allowedTypes[0]; 423 } 424 425 // Set the start type 426 if (options.start && allowedStarts.indexOf(options.start) === -1) { 427 throw new Error('Vivus [constructor]: ' + options.start + ' is not an existing `start` option'); 428 } 429 else { 430 this.start = options.start || allowedStarts[0]; 431 } 432 433 this.isIE = (window.navigator.userAgent.indexOf('MSIE') !== -1 || window.navigator.userAgent.indexOf('Trident/') !== -1 || window.navigator.userAgent.indexOf('Edge/') !== -1 ); 434 this.duration = parsePositiveInt(options.duration, 120); 435 this.delay = parsePositiveInt(options.delay, null); 436 this.dashGap = parsePositiveInt(options.dashGap, 1); 437 this.forceRender = options.hasOwnProperty('forceRender') ? !!options.forceRender : this.isIE; 438 this.selfDestroy = !!options.selfDestroy; 439 this.onReady = options.onReady; 440 this.frameLength = this.currentFrame = this.map = this.delayUnit = this.speed = this.handle = null; 441 442 this.ignoreInvisible = options.hasOwnProperty('ignoreInvisible') ? !!options.ignoreInvisible : false; 443 444 this.animTimingFunction = options.animTimingFunction || Vivus.LINEAR; 445 this.pathTimingFunction = options.pathTimingFunction || Vivus.LINEAR; 446 447 if (this.delay >= this.duration) { 448 throw new Error('Vivus [constructor]: delay must be shorter than duration'); 449 } 450}; 451 452/** 453 * Set up callback to the instance 454 * The method will not return enything, but will throw an
455 * error if the parameter is invalid 456 * 457 * @param {Function} callback Callback for the animation end 458 */ 459Vivus.prototype.setCallback = function (callback) { 460 // Basic check 461 if (!!callback && callback.constructor !== Function) { 462 throw new Error('Vivus [constructor]: "callback" parameter must be a function'); 463 } 464 this.callback = callback || function () {}; 465}; 466 467 468/** 469 * Core 470 ************************************** 471 */ 472 473/** 474 * Map the svg, path by path. 475 * The method return nothing, it just fill the 476 * `map` array. Each item in this array represent 477 * a path element from the SVG, with informations for 478 * the animation. 479 * 480 * ``` 481 * [ 482 * { 483 * el: <DOMobj> the path element 484 * length: <number> length of the path line 485 * startAt: <number> time start of the path animation (in frames) 486 * duration: <number> path animation duration (in frames) 487 * }, 488 * ... 489 * ] 490 * ``` 491 * 492 */ 493Vivus.prototype.mapping = function () { 494 var i, paths, path, pAttrs, pathObj, totalLength, lengthMeter, timePoint; 495 timePoint = totalLength = lengthMeter = 0; 496 paths = this.el.querySelectorAll('path'); 497 498 for (i = 0; i < paths.length; i++) { 499 path = paths[i]; 500 if (this.isInvisible(path)) { 501 continue; 502 } 503 pathObj = { 504 el: path, 505 length: Math.ceil(path.getTotalLength()) 506 }; 507 // Test if the path length is correct 508 if (isNaN(pathObj.length)) { 509 if (window.console && console.warn) { 510 console.warn('Vivus [mapping]: cannot retrieve a path element length', path); 511 } 512 continue; 513 } 514 this.map.push(pathObj); 515 path.style.strokeDasharray = pathObj.length + ' ' + (pathObj.length + this.dashGap * 2); 516 path.style.strokeDashoffset = pathObj.length + this.dashGap; 517 pathObj.length += this.dashGap; 518 totalLength += pathObj.length; 519 520 this.renderPath(i); 521 } 522 523 totalLength = totalLength === 0 ? 1 : totalLength; 524 this.delay = this.delay === null ? this.duration / 3 : this.delay; 525 this.delayUnit = this.delay / (paths.length > 1 ? paths.length - 1 : 1); 526 527 for (i = 0; i < this.map.length; i++) { 528 pathObj = this.map[i]; 529 530 switch (this.type) { 531 case 'delayed': 532 pathObj.startAt = this.delayUnit * i; 533 pathObj.duration = this.duration - this.delay; 534 break; 535 536 case 'oneByOne': 537 pathObj.startAt = lengthMeter / totalLength * this.duration; 538 pathObj.duration = pathObj.length / totalLength * this.duration; 539 break; 540 541 case 'async': 542 pathObj.startAt = 0; 543 pathObj.duration = this.duration; 544 break; 545 546 case 'scenario-sync': 547 path = pathObj.el; 548 pAttrs = this.parseAttr(path); 549 pathObj.startAt = timePoint + (parsePositiveInt(pAttrs['data-delay'], this.delayUnit) || 0); 550 pathObj.duration = parsePositiveInt(pAttrs['data-duration'], this.duration); 551 timePoint = pAttrs['data-async'] !== undefined ? pathObj.startAt : pathObj.startAt + pathObj.duration; 552 this.frameLength = Math.max(this.frameLength, (pathObj.startAt + pathObj.duration)); 553 break; 554 555 case 'scenario': 556 path = pathObj.el; 557 pAttrs = this.parseAttr(path); 558 pathObj.startAt = parsePositiveInt(pAttrs['data-start'], this.delayUnit) || 0; 559 pathObj.duration = parsePositiveInt(pAttrs['data-duration'], this.duration); 560 this.frameLength = Math.max(this.frameLength, (pathObj.startAt + pathObj.duration)); 561 break; 562 } 563 lengthMeter += pathObj.length; 564 this.frameLength = this.frameLength || this.duration; 565 } 566}; 567 568/** 569 * Interval method to draw the SVG from current 570 * position of the animation. It update the value of 571 * `currentFrame` and re-trace the SVG. 572 * 573 * It use this.handle to store the requestAnimationFrame 574 * and clear it one the animation is stopped. So this 575 * attribute can be used to know if the animation is 576 * playing. 577 * 578 * Once the animation at the end, this method will 579 * trigger the Vivus callback. 580 * 581 */ 582Vivus.prototype.drawer = function () { 583 var self = this; 584 this.currentFrame += this.speed; 585 586 if (this.currentFrame <= 0) { 587 this.stop(); 588 this.reset(); 589 this.callback(this); 590 } else if (this.currentFrame >= this.frameLength) { 591 this.stop(); 592 this.currentFrame = this.frameLength; 593 this.trace();
vendor: 4,671 bytes, lines 594-760
594 if (this.selfDestroy) { 595 this.destroy(); 596 } 597 this.callback(this); 598 } else { 599 this.trace(); 600 this.handle = requestAnimFrame(function () { 601 self.drawer(); 602 }); 603 } 604}; 605 606/** 607 * Draw the SVG at the current instant from the 608 * `currentFrame` value. Here is where most of the magic is. 609 * The trick is to use the `strokeDashoffset` style property. 610 * 611 * For optimisation reasons, a new property called `progress` 612 * is added in each item of `map`. This one contain the current 613 * progress of the path element. Only if the new value is different 614 * the new value will be applied to the DOM element. This 615 * method save a lot of resources to re-render the SVG. And could 616 * be improved if the animation couldn't be played forward. 617 * 618 */ 619Vivus.prototype.trace = function () { 620 var i, progress, path, currentFrame; 621 currentFrame = this.animTimingFunction(this.currentFrame / this.frameLength) * this.frameLength; 622 for (i = 0; i < this.map.length; i++) { 623 path = this.map[i]; 624 progress = (currentFrame - path.startAt) / path.duration; 625 progress = this.pathTimingFunction(Math.max(0, Math.min(1, progress))); 626 if (path.progress !== progress) { 627 path.progress = progress; 628 path.el.style.strokeDashoffset = Math.floor(path.length * (1 - progress)); 629 this.renderPath(i); 630 } 631 } 632}; 633 634/** 635 * Method forcing the browser to re-render a path element 636 * from it's index in the map. Depending on the `forceRender` 637 * value. 638 * The trick is to replace the path element by it's clone. 639 * This practice is not recommended because it's asking more 640 * ressources, too much DOM manupulation.. 641 * but it's the only way to let the magic happen on IE. 642 * By default, this fallback is only applied on IE. 643 * 644 * @param {Number} index Path index 645 */ 646Vivus.prototype.renderPath = function (index) { 647 if (this.forceRender && this.map && this.map[index]) { 648 var pathObj = this.map[index], 649 newPath = pathObj.el.cloneNode(true); 650 pathObj.el.parentNode.replaceChild(newPath, pathObj.el); 651 pathObj.el = newPath; 652 } 653}; 654 655/** 656 * When the SVG object is loaded and ready, 657 * this method will continue the initialisation. 658 * 659 * This this mainly due to the case of passing an 660 * object tag in the constructor. It will wait 661 * the end of the loading to initialise. 662 * 663 */ 664Vivus.prototype.init = function () { 665 // Set object variables 666 this.frameLength = 0; 667 this.currentFrame = 0; 668 this.map = []; 669 670 // Start 671 new Pathformer(this.el); 672 this.mapping(); 673 this.starter(); 674 675 if (this.onReady) { 676 this.onReady(this); 677 } 678}; 679 680/** 681 * Trigger to start of the animation. 682 * Depending on the `start` value, a different script 683 * will be applied. 684 * 685 * If the `start` value is not valid, an error will be thrown. 686 * Even if technically, this is impossible. 687 * 688 */ 689Vivus.prototype.starter = function () { 690 switch (this.start) { 691 case 'manual': 692 return; 693 694 case 'autostart': 695 this.play(); 696 break; 697 698 case 'inViewport': 699 var self = this, 700 listener = function () { 701 if (self.isInViewport(self.parentEl, 1)) { 702 console.log('viewed') 703 self.play(); 704 window.removeEventListener('scroll', listener); 705 } 706 }; 707 window.addEventListener('scroll', listener); 708 listener(); 709 break; 710 } 711}; 712 713 714/** 715 * Controls 716 ************************************** 717 */ 718 719/** 720 * Get the current status of the animation between 721 * three different states: 'start', 'progress', 'end'. 722 * @return {string} Instance status 723 */ 724Vivus.prototype.getStatus = function () { 725 return this.currentFrame === 0 ? 'start' : this.currentFrame === this.frameLength ? 'end' : 'progress'; 726}; 727 728/** 729 * Reset the instance to the initial state : undraw 730 * Be careful, it just reset the animation, if you're 731 * playing the animation, this won't stop it. But just 732 * make it start from start. 733 * 734 */ 735Vivus.prototype.reset = function () { 736 return this.setFrameProgress(0); 737}; 738 739/** 740 * Set the instance to the final state : drawn 741 * Be careful, it just set the animation, if you're 742 * playing the animation on rewind, this won't stop it. 743 * But just make it start from the end. 744 * 745 */ 746Vivus.prototype.finish = function () { 747 return this.setFrameProgress(1); 748}; 749 750/** 751 * Set the level of progress of the drawing. 752 * 753 * @param {number} progress Level of progress to set 754 */ 755Vivus.prototype.setFrameProgress = function (progress) { 756 progress = Math.min(1, Math.max(0, progress)); 757 this.currentFrame = Math.round(this.frameLength * progress); 758 this.trace(); 759 return this; 760};
761 762/** 763 * Play the animation at the desired speed. 764 * Speed must be a valid number (no zero). 765 * By default, the speed value is 1. 766 * But a negative value is accepted to go forward. 767 * 768 * And works with float too. 769 * But don't forget we are in JavaScript, se be nice 770 * with him and give him a 1/2^x value. 771 * 772 * @param {number} speed Animation speed [optional] 773 */ 774Vivus.prototype.play = function (speed) { 775 if (speed && typeof speed !== 'number') { 776 throw new Error('Vivus [play]: invalid speed'); 777 } 778 this.speed = speed || 1; 779 if (!this.handle) { 780 this.drawer(); 781 } 782 return this; 783}; 784 785/** 786 * Stop the current animation, if on progress. 787 * Should not trigger any error. 788 * 789 */ 790Vivus.prototype.stop = function () { 791 if (this.handle) { 792 cancelAnimFrame(this.handle); 793 this.handle = null; 794 } 795 return this; 796}; 797 798/** 799 * Destroy the instance. 800 * Remove all bad styling attributes on all 801 * path tags 802 * 803 */ 804Vivus.prototype.destroy = function () { 805 this.stop(); 806 var i, path; 807 for (i = 0; i < this.map.length; i++) { 808 path = this.map[i]; 809 path.el.style.strokeDashoffset = null; 810 path.el.style.strokeDasharray = null; 811 this.renderPath(i); 812 } 813}; 814 815 816/** 817 * Utils methods 818 * include methods from Codrops 819 ************************************** 820 */ 821 822/** 823 * Method to best guess if a path should added into 824 * the animation or not. 825 * 826 * 1. Use the `data-vivus-ignore` attribute if set 827 * 2. Check if the instance must ignore invisible paths 828 * 3. Check if the path is visible 829 * 830 * For now the visibility checking is unstable. 831 * It will be used for a beta phase. 832 * 833 * Other improvments are planned. Like detecting 834 * is the path got a stroke or a valid opacity. 835 */ 836Vivus.prototype.isInvisible = function (el) { 837 var rect, 838 ignoreAttr = el.getAttribute('data-ignore'); 839 840 if (ignoreAttr !== null) { 841 return ignoreAttr !== 'false'; 842 } 843 844 if (this.ignoreInvisible) { 845 rect = el.getBoundingClientRect(); 846 return !rect.width && !rect.height; 847 } 848 else { 849 return false; 850 } 851}; 852 853/** 854 * Parse attributes of a DOM element to 855 * get an object of {attributeName => attributeValue} 856 * 857 * @param {object} element DOM element to parse 858 * @return {object} Object of attributes 859 */ 860Vivus.prototype.parseAttr = function (element) { 861 var attr, output = {}; 862 if (element && element.attributes) { 863 for (var i = 0; i < element.attributes.length; i++) { 864 attr = element.attributes[i]; 865 output[attr.name] = attr.value; 866 } 867 } 868 return output; 869}; 870 871/** 872 * Reply if an element is in the page viewport 873 * 874 * @param {object} el Element to observe 875 * @param {number} h Percentage of height 876 * @return {boolean} 877 */ 878Vivus.prototype.isInViewport = function (el, h) { 879 var scrolled = this.scrollY(), 880 viewed = scrolled + this.getViewportH() + 100, 881 elBCR = el.getBoundingClientRect(), 882 elHeight = elBCR.height, 883 elTop = scrolled + elBCR.top, 884 elBottom = elTop + elHeight; 885 886 // if 0, the element is considered in the viewport as soon as it enters. 887 // if 1, the element is considered in the viewport only when it's fully inside 888 // value in percentage (1 >= h >= 0) 889 h = h || 0; 890 891 return (elTop + elHeight * h) <= viewed && (elBottom) >= scrolled; 892}; 893 894/** 895 * Alias for document element 896 * 897 * @type {DOMelement} 898 */ 899Vivus.prototype.docElem = window.document.documentElement; 900 901/** 902 * Get the viewport height in pixels 903 * 904 * @return {integer} Viewport height 905 */ 906Vivus.prototype.getViewportH = function () { 907 var client = this.docElem.clientHeight, 908 inner = window.innerHeight; 909 910 if (client < inner) { 911 return inner; 912 } 913 else { 914 return client; 915 } 916}; 917 918/** 919 * Get the page Y offset 920 * 921 * @return {integer} Page Y offset 922 */ 923Vivus.prototype.scrollY = function () { 924 return window.pageYOffset || this.docElem.scrollTop; 925}; 926 927/** 928 * Alias for `requestAnimationFrame` or 929 * `setTimeout` function for deprecated browsers. 930 * 931 */ 932requestAnimFrame = (function () { 933 return ( 934 window.requestAnimationFrame || 935 window.webkitRequestAnimationFrame || 936 window.mozRequestAnimationFrame || 937 window.oRequestAnimationFrame || 938 window.msRequestAnimationFrame || 939 function(/* function */ callback){ 940 return window.setTimeout(callback, 1000 / 60); 941 } 942 ); 943})(); 944 945/** 946 * Alias for `cancelAnimationFrame` or 947 * `cancelTimeout` function for deprecated browsers. 948 * 949 */ 950cancelAnimFrame = (function () { 951 return ( 952 window.cancelAnimationFrame || 953 window.webkitCancelAnimationFrame || 954 window.mozCancelAnimationFrame || 955 window.oCancelAnimationFrame || 956 window.msCancelAnimationFrame || 957 function(id){ 958 return window.clearTimeout(id); 959 } 960 ); 961})(); 962 963/** 964 * Parse string to integer. 965 * If the number is not positive or null 966 * the method will return the default value 967 * or 0 if undefined 968 * 969 * @param {string} value String to parse 970 * @param {*}
970 defaultValue Value to return if the result parsed is invalid 971 * @return {number} 972 * 973 */ 974parsePositiveInt = function (value, defaultValue) { 975 var output = parseInt(value, 10); 976 return (output >= 0) ? output : defaultValue; 977}; 978 979 980 if (typeof define === 'function' && define.amd) { 981 // AMD. Register as an anonymous module. 982 define([], function() { 983 return Vivus; 984 }); 985 } else if (typeof exports === 'object') { 986 // Node. Does not work with strict CommonJS, but 987 // only CommonJS-like environments that support module.exports, 988 // like Node. 989 module.exports = Vivus; 990 } else { 991 // Browser globals 992 window.Vivus = Vivus; 993 } 994 995}(window, document));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.