1/*! 2 * skrollr core 3 * 4 * Alexander Prinzhorn - https://github.com/Prinzhorn/skrollr 5 * 6 * Free to use under terms of MIT license 7 */ 8(function(window, document, undefined) { 9 'use strict'; 10 11 /* 12 * Global api. 13 */ 14 var skrollr = window.skrollr = { 15 get: function() { 16 return _instance; 17 }, 18 //Main entry point. 19 init: function(options) { 20 return _instance || new Skrollr(options); 21 }, 22 VERSION: '0.6.21' 23 }; 24 25 //Minify optimization. 26 var hasProp = Object.prototype.hasOwnProperty; 27 var Math = window.Math; 28 var getStyle = window.getComputedStyle; 29 30 //They will be filled when skrollr gets initialized. 31 var documentElement; 32 33// var body; // line edited by SNO 34 var body = '.parallaxcontainer'; 35 36 var EVENT_TOUCHSTART = 'touchstart'; 37 var EVENT_TOUCHMOVE = 'touchmove'; 38 var EVENT_TOUCHCANCEL = 'touchcancel'; 39 var EVENT_TOUCHEND = 'touchend'; 40 41 var SKROLLABLE_CLASS = 'skrollable'; 42 var SKROLLABLE_BEFORE_CLASS = SKROLLABLE_CLASS + '-before'; 43 var SKROLLABLE_BETWEEN_CLASS = SKROLLABLE_CLASS + '-between'; 44 var SKROLLABLE_AFTER_CLASS = SKROLLABLE_CLASS + '-after'; 45 46 var SKROLLR_CLASS = 'skrollr'; 47 var NO_SKROLLR_CLASS = 'no-' + SKROLLR_CLASS; 48 var SKROLLR_DESKTOP_CLASS = SKROLLR_CLASS + '-desktop'; 49 var SKROLLR_MOBILE_CLASS = SKROLLR_CLASS + '-mobile'; 50 51 var DEFAULT_EASING = 'linear'; 52 var DEFAULT_DURATION = 1000;//ms 53 var DEFAULT_MOBILE_DECELERATION = 0.004;//pixel/ms² 54 55 var DEFAULT_SMOOTH_SCROLLING_DURATION = 200;//ms 56 57 var ANCHOR_START = 'start'; 58 var ANCHOR_END = 'end'; 59 var ANCHOR_CENTER = 'center'; 60 var ANCHOR_BOTTOM = 'bottom'; 61 62 //The property which will be added to the DOM element to hold the ID of the skrollable. 63 var SKROLLABLE_ID_DOM_PROPERTY = '___skrollable_id'; 64 65 var rxTouchIgnoreTags = /^(?:input|textarea|button|select)$/i; 66 67 var rxTrim = /^\s+|\s+$/g; 68 69 //Find all data-attributes. data-[_constant]-[offset]-[anchor]-[anchor]. 70 var rxKeyframeAttribute = /^data(?:-(_\w+))?(?:-?(-?\d*\.?\d+p?))?(?:-?(start|end|top|center|bottom))?(?:-?(top|center|bottom))?$/; 71 72 var rxPropValue = /\s*([\w\-\[\]]+)\s*:\s*(.+?)\s*(?:;|$)/gi; 73 74 //Easing function names follow the property in square brackets. 75 var rxPropEasing = /^([a-z\-]+)\[(\w+)\]$/; 76 77 var rxCamelCase = /-([a-z])/g; 78 var rxCamelCaseFn = function(str, letter) { 79 return letter.toUpperCase(); 80 }; 81 82 //Numeric values with optional sign.
83 var rxNumericValue = /[\-+]?[\d]*\.?[\d]+/g; 84 85 //Used to replace occurences of {?} with a number. 86 var rxInterpolateString = /\{\?\}/g; 87 88 //Finds rgb(a) colors, which don't use the percentage notation. 89 var rxRGBAIntegerColor = /rgba?\(\s*-?\d+\s*,\s*-?\d+\s*,\s*-?\d+/g; 90 91 //Finds all gradients. 92 var rxGradient = /[a-z\-]+-gradient/g; 93 94 //Vendor prefix. Will be set once skrollr gets initialized. 95 var theCSSPrefix = ''; 96 var theDashedCSSPrefix = ''; 97 98 //Will be called once (when skrollr gets initialized). 99 var detectCSSPrefix = function() { 100 //Only relevant prefixes. May be extended. 101 //Could be dangerous if there will ever be a CSS property which actually starts with "ms". Don't hope so. 102 var rxPrefixes = /^(?:O|Moz|webkit|ms)|(?:-(?:o|moz|webkit|ms)-)/; 103 104 //Detect prefix for current browser by finding the first property using a prefix. 105 if(!getStyle) { 106 return; 107 } 108 109 var style = getStyle(body, null); 110 111 for(var k in style) { 112 //We check the key and if the key is a number, we check the value as well, because safari's getComputedStyle returns some weird array-like thingy. 113 theCSSPrefix = (k.match(rxPrefixes) || (+k == k && style[k].match(rxPrefixes))); 114 115 if(theCSSPrefix) { 116 break; 117 } 118 } 119 120 //Did we even detect a prefix? 121 if(!theCSSPrefix) { 122 theCSSPrefix = theDashedCSSPrefix = ''; 123 124 return; 125 } 126 127 theCSSPrefix = theCSSPrefix[0]; 128 129 //We could have detected either a dashed prefix or this camelCaseish-inconsistent stuff. 130 if(theCSSPrefix.slice(0,1) === '-') { 131 theDashedCSSPrefix = theCSSPrefix; 132 133 //There's no logic behind these. Need a look up. 134 theCSSPrefix = ({ 135 '-webkit-': 'webkit', 136 '-moz-': 'Moz', 137 '-ms-': 'ms', 138 '-o-': 'O' 139 })[theCSSPrefix]; 140 } else { 141 theDashedCSSPrefix = '-' + theCSSPrefix.toLowerCase() + '-'; 142 } 143 }; 144 145 var polyfillRAF = function() { 146 var requestAnimFrame = window.requestAnimationFrame || window[theCSSPrefix.toLowerCase() + 'RequestAnimationFrame']; 147 148 var lastTime = _now(); 149 150 if(_isMobile || !requestAnimFrame) { 151 requestAnimFrame = function(callback) { 152 //How long did it take to render? 153 var deltaTime = _now() - lastTime; 154 var delay = Math.max(0, 1000 / 60 - deltaTime); 155 156 return window.setTimeout(function() { 157 lastTime = _now(); 158 callback(); 159 }, delay); 160 }; 161 } 162 163 return requestAnimFrame; 164 }; 165 166 var polyfillCAF = function() { 167 var cancelAnimFrame = window.cancelAnimationFrame || window[theCSSPrefix.toLowerCase() + 'CancelAnimationFrame']; 168 169 if(_isMobile || !cancelAnimFrame) { 170 cancelAnimFrame = function(timeout) { 171 return window.clearTimeout(timeout); 172 }; 173 } 174 175 return cancelAnimFrame; 176 }; 177 178 //Built-in easing functions. 179 var easings = { 180 begin: function() { 181 return 0; 182 }, 183 end: function() { 184 return 1; 185 }, 186 linear: function(p) { 187 return p; 188 }, 189 quadratic: function(p) { 190 return p * p; 191 }, 192 cubic: function(p) { 193 return p * p * p; 194 }, 195 swing: function(p) { 196 return (-Math.cos(p * Math.PI) / 2) + 0.5; 197 }, 198 sqrt: function(p) { 199 return Math.sqrt(p); 200 }, 201 outCubic: function(p) { 202 return (Math.pow((p - 1), 3) + 1); 203 }, 204 //see https://www.desmos.com/calculator/tbr20s8vd2 for how I did this 205 bounce: function(p) { 206 var a; 207 208 if(p <= 0.5083) { 209 a = 3; 210 } else if(p <= 0.8489) { 211 a = 9; 212 } else if(p <= 0.96208) { 213 a = 27; 214 } else if(p <= 0.99981) { 215 a = 91; 216 } else { 217 return 1; 218 } 219 220 return 1 - Math.abs(3 * Math.cos(p * a * 1.028) / a); 221 } 222 }; 223 224 /** 225 * Constructor. 226 */ 227 function Skrollr(options) { 228 documentElement = document.documentElement; 229 body = document.body; 230 detectCSSPrefix(); 231 232 _instance = this; 233 234 options = options || {}; 235 236 _constants = options.constants || {}; 237 238 //We allow defining custom easings or overwrite existing. 239 if(options.easing) { 240 for(var e in options.easing) { 241 easings[e] = options.easing[e]; 242 } 243 } 244 245 _edgeStrategy = options.edgeStrategy || 'set'; 246 247 _listeners = { 248 //Function to be called right before rendering. 249 beforerender: options.beforerender, 250 251 //Function to be called right after finishing rendering. 252 render: options.render 253 }; 254 255 //forceHeight is true by default 256 _forceHeight = options.forceHeight !== false; 257 258 if(_forceHeight) { 259 _scale = options.scale || 1; 260 } 261 262 _mobileDeceleration = options.mobileDeceleration || DEFAULT_MOBILE_DECELERATION; 263 264 _smoothScrollingEnabled = options.smoothScrolling !== false; 265 _smoothScrollingDuration = options.smoothScrollingDuration || DEFAULT_SMOOTH_SCROLLING_DURATION; 266 267 //Dummy object. Will be overwritten in the _render method when smooth scrolling is calculated. 268 _smoothScrolling = { 269 targetTop: _instance.getScrollTop() 270 }; 271 272 //A custom check function may be passed. 273 _isMobile = ((options.mobileCheck || function() { 274 return (/Android|iPhone|iPad|iPod|BlackBerry/i).test(navigator.userAgent || navigator.vendor || window.opera); 275 })()); 276 277 if(_isMobile) { 278 _skrollrBody = document.getElementById('skrollr-body'); 279 280 //Detect 3d transform if there's a skrollr-body (only needed for #skrollr-body). 281 if(_skrollrBody) { 282 _detect3DTransforms(); 283 } 284 285 _initMobile();
286 _updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_MOBILE_CLASS], [NO_SKROLLR_CLASS]); 287 } else { 288 _updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS], [NO_SKROLLR_CLASS]); 289 } 290 291 //Triggers parsing of elements and a first reflow. 292 _instance.refresh(); 293 294 _addEvent(window, 'resize orientationchange', function() { 295 var width = documentElement.clientWidth; 296 var height = documentElement.clientHeight; 297 298 //Only reflow if the size actually changed (#271). 299 if(height !== _lastViewportHeight || width !== _lastViewportWidth) { 300 _lastViewportHeight = height; 301 _lastViewportWidth = width; 302 303 _requestReflow = true; 304 } 305 }); 306 307 var requestAnimFrame = polyfillRAF(); 308 309 //Let's go. 310 (function animloop(){ 311 _render(); 312 _animFrame = requestAnimFrame(animloop); 313 }()); 314 315 return _instance; 316 } 317 318 /** 319 * (Re)parses some or all elements. 320 */ 321 Skrollr.prototype.refresh = function(elements) { 322 var elementIndex; 323 var elementsLength; 324 var ignoreID = false; 325 326 //Completely reparse anything without argument. 327 if(elements === undefined) { 328 //Ignore that some elements may already have a skrollable ID. 329 ignoreID = true; 330 331 _skrollables = []; 332 _skrollableIdCounter = 0; 333 334 elements = document.getElementsByTagName('*'); 335 } else { 336 //We accept a single element or an array of elements. 337 elements = [].concat(elements); 338 } 339 340 elementIndex = 0; 341 elementsLength = elements.length; 342 343 for(; elementIndex < elementsLength; elementIndex++) { 344 var el = elements[elementIndex]; 345 var anchorTarget = el; 346 var keyFrames = []; 347 348 //If this particular element should be smooth scrolled. 349 var smoothScrollThis = _smoothScrollingEnabled; 350 351 //The edge strategy for this particular element. 352 var edgeStrategy = _edgeStrategy; 353 354 if(!el.attributes) { 355 continue; 356 } 357 358 //Iterate over all attributes and search for key frame attributes. 359 var attributeIndex = 0; 360 var attributesLength = el.attributes.length; 361 362 for (; attributeIndex < attributesLength; attributeIndex++) { 363 var attr = el.attributes[attributeIndex]; 364 365 if(attr.name === 'data-anchor-target') { 366 anchorTarget = document.querySelector(attr.value); 367 368 if(anchorTarget === null) { 369 throw 'Unable to find anchor target "' + attr.value + '"'; 370 } 371 372 continue; 373 } 374 375 //Global smooth scrolling can be overridden by the element attribute. 376 if(attr.name === 'data-smooth-scrolling') { 377 smoothScrollThis = attr.value !== 'off'; 378 379 continue; 380 } 381 382 //Global edge strategy can be overridden by the element attribute. 383 if(attr.name === 'data-edge-strategy') { 384 edgeStrategy = attr.value; 385 386 continue; 387 } 388 389 var match = attr.name.match(rxKeyframeAttribute); 390 391 if(match === null) { 392 continue; 393 } 394 395 var kf = { 396 props: attr.value, 397 //Point back to the element as well. 398 element: el 399 }; 400 401 keyFrames.push(kf); 402 403 var constant = match[1]; 404 405 if(constant) { 406 //Strip the underscore prefix. 407 kf.constant = constant.substr(1); 408 } 409 410 //Get the key frame offset. 411 var offset = match[2]; 412 413 //Is it a percentage offset? 414 if(/p$/.test(offset)) { 415 kf.isPercentage = true; 416 kf.offset = (offset.slice(0, -1) | 0) / 100; 417 } else { 418 kf.offset = (offset | 0); 419 } 420 421 var anchor1 = match[3]; 422 423 //If second anchor is not set, the first will be taken for both. 424 var anchor2 = match[4] || anchor1; 425 426 //"absolute" (or "classic") mode, where numbers mean absolute scroll offset. 427 if(!anchor1 || anchor1 === ANCHOR_START || anchor1 === ANCHOR_END) { 428 kf.mode = 'absolute'; 429 430 //data-end needs to be calculated after all key frames are known. 431 if(anchor1 === ANCHOR_END) { 432 kf.isEnd = true; 433 } else if(!kf.isPercentage) {
434 //For data-start we can already set the key frame w/o calculations. 435 //#59: "scale" options should only affect absolute mode. 436 kf.offset = kf.offset * _scale; 437 } 438 } 439 //"relative" mode, where numbers are relative to anchors. 440 else { 441 kf.mode = 'relative'; 442 kf.anchors = [anchor1, anchor2]; 443 } 444 } 445 446 //Does this element have key frames? 447 if(!keyFrames.length) { 448 continue; 449 } 450 451 //Will hold the original style and class attributes before we controlled the element (see #80). 452 var styleAttr, classAttr; 453 454 var id; 455 456 if(!ignoreID && SKROLLABLE_ID_DOM_PROPERTY in el) { 457 //We already have this element under control. Grab the corresponding skrollable id. 458 id = el[SKROLLABLE_ID_DOM_PROPERTY]; 459 styleAttr = _skrollables[id].styleAttr; 460 classAttr = _skrollables[id].classAttr; 461 } else { 462 //It's an unknown element. Asign it a new skrollable id. 463 id = (el[SKROLLABLE_ID_DOM_PROPERTY] = _skrollableIdCounter++); 464 styleAttr = el.style.cssText; 465 classAttr = _getClass(el); 466 } 467 468 _skrollables[id] = { 469 element: el, 470 styleAttr: styleAttr, 471 classAttr: classAttr, 472 anchorTarget: anchorTarget, 473 keyFrames: keyFrames, 474 smoothScrolling: smoothScrollThis, 475 edgeStrategy: edgeStrategy 476 }; 477 478 _updateClass(el, [SKROLLABLE_CLASS], []); 479 } 480 481 //Reflow for the first time. 482 _reflow(); 483 484 //Now that we got all key frame numbers right, actually parse the properties. 485 elementIndex = 0; 486 elementsLength = elements.length; 487 488 for(; elementIndex < elementsLength; elementIndex++) { 489 var sk = _skrollables[elements[elementIndex][SKROLLABLE_ID_DOM_PROPERTY]]; 490 491 if(sk === undefined) { 492 continue; 493 } 494 495 //Parse the property string to objects 496 _parseProps(sk); 497 498 //Fill key frames with missing properties from left and right 499 _fillProps(sk); 500 } 501 502 return _instance; 503 }; 504 505 /** 506 * Transform "relative" mode to "absolute" mode. 507 * That is, calculate anchor position and offset of element. 508 */ 509 Skrollr.prototype.relativeToAbsolute = function(element, viewportAnchor, elementAnchor) { 510 var viewportHeight = documentElement.clientHeight; 511 var box = element.getBoundingClientRect(); 512 var absolute = box.top; 513 514 //#100: IE doesn't supply "height" with getBoundingClientRect. 515 var boxHeight = box.bottom - box.top; 516 517 if(viewportAnchor === ANCHOR_BOTTOM) { 518 absolute -= viewportHeight; 519 } else if(viewportAnchor === ANCHOR_CENTER) { 520 absolute -= viewportHeight / 2; 521 } 522 523 if(elementAnchor === ANCHOR_BOTTOM) { 524 absolute += boxHeight; 525 } else if(elementAnchor === ANCHOR_CENTER) { 526 absolute += boxHeight / 2; 527 } 528 529 //Compensate scrolling since getBoundingClientRect is relative to viewport. 530 absolute += _instance.getScrollTop(); 531 532 return (absolute + 0.5) | 0; 533 }; 534 535 /** 536 * Animates scroll top to new position. 537 */ 538 Skrollr.prototype.animateTo = function(top, options) { 539 options = options || {}; 540 541 var now = _now(); 542 var scrollTop = _instance.getScrollTop(); 543 544 //Setting this to a new value will automatically cause the current animation to stop, if any. 545 _scrollAnimation = { 546 startTop: scrollTop, 547 topDiff: top - scrollTop, 548 targetTop: top, 549 duration: options.duration || DEFAULT_DURATION, 550 startTime: now, 551 endTime: now + (options.duration || DEFAULT_DURATION), 552 easing: easings[options.easing || DEFAULT_EASING], 553 done: options.done 554 }; 555 556 //Don't queue the animation if there's nothing to animate. 557 if(!_scrollAnimation.topDiff) { 558 if(_scrollAnimation.done) { 559 _scrollAnimation.done.call(_instance, false); 560 } 561 562 _scrollAnimation = undefined; 563 } 564 565 return _instance; 566 }; 567 568 /** 569 * Stops animateTo animation. 570 */ 571 Skrollr.prototype.stopAnimateTo = function() { 572 if(_scrollAnimation && _scrollAnimation.done) { 573 _scrollAnimation.done.call(_instance, true); 574 } 575 576 _scrollAnimation = undefined; 577 }; 578 579 /** 580 * Returns if an animation caused by animateTo is currently running. 581 */ 582 Skrollr.prototype.isAnimatingTo = function() { 583 return !!_scrollAnimation; 584 }; 585 586 Skrollr.prototype.setScrollTop = function(top, force) { 587 _forceRender = (force === true); 588 589 if(_isMobile) { 590 _mobileOffset = Math.min(Math.max(top, 0), _maxKeyFrame); 591 } else { 592 window.scrollTo(0, top); 593 } 594 595 return _instance; 596 }; 597 598 Skrollr.prototype.getScrollTop = function() { 599 if(_isMobile) { 600 return _mobileOffset; 601 } else { 602 return window.pageYOffset || documentElement.scrollTop || body.scrollTop || 0; 603 } 604 }; 605 606 Skrollr.prototype.getMaxScrollTop = function() { 607 return _maxKeyFrame; 608 }; 609 610 Skrollr.prototype.on = function(name, fn) { 611 _listeners[name] = fn; 612 613 return _instance; 614 }; 615 616 Skrollr.prototype.off = function(name) { 617 delete _listeners[name]; 618 619 return _instance; 620 }; 621 622 Skrollr.prototype.destroy = function() { 623 var cancelAnimFrame = polyfillCAF();
624 cancelAnimFrame(_animFrame); 625 _removeAllEvents(); 626 627 _updateClass(documentElement, [NO_SKROLLR_CLASS], [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS, SKROLLR_MOBILE_CLASS]); 628 629 var skrollableIndex = 0; 630 var skrollablesLength = _skrollables.length; 631 632 for(; skrollableIndex < skrollablesLength; skrollableIndex++) { 633 _reset(_skrollables[skrollableIndex].element); 634 } 635 636 documentElement.style.overflow = body.style.overflow = 'auto'; 637 documentElement.style.height = body.style.height = 'auto'; 638 639 if(_skrollrBody) { 640 skrollr.setStyle(_skrollrBody, 'transform', 'none'); 641 } 642 643 _instance = undefined; 644 _skrollrBody = undefined; 645 _listeners = undefined; 646 _forceHeight = undefined; 647 _maxKeyFrame = 0; 648 _scale = 1; 649 _constants = undefined; 650 _mobileDeceleration = undefined; 651 _direction = 'down'; 652 _lastTop = -1; 653 _lastViewportWidth = 0; 654 _lastViewportHeight = 0; 655 _requestReflow = false; 656 _scrollAnimation = undefined; 657 _smoothScrollingEnabled = undefined; 658 _smoothScrollingDuration = undefined; 659 _smoothScrolling = undefined; 660 _forceRender = undefined; 661 _skrollableIdCounter = 0; 662 _edgeStrategy = undefined; 663 _isMobile = false; 664 _mobileOffset = 0; 665 _translateZ = undefined; 666 }; 667 668 /* 669 Private methods. 670 */ 671 672 var _initMobile = function() { 673 var initialElement; 674 var initialTouchY; 675 var initialTouchX; 676 var currentElement; 677 var currentTouchY; 678 var currentTouchX; 679 var lastTouchY; 680 var deltaY; 681 682 var initialTouchTime; 683 var currentTouchTime; 684 var lastTouchTime; 685 var deltaTime; 686 687 _addEvent(documentElement, [EVENT_TOUCHSTART, EVENT_TOUCHMOVE, EVENT_TOUCHCANCEL, EVENT_TOUCHEND].join(' '), function(e) { 688 var touch = e.changedTouches[0]; 689 690 currentElement = e.target; 691 692 //We don't want text nodes. 693 while(currentElement.nodeType === 3) { 694 currentElement = currentElement.parentNode; 695 } 696 697 currentTouchY = touch.clientY; 698 currentTouchX = touch.clientX; 699 currentTouchTime = e.timeStamp; 700 701 if(!rxTouchIgnoreTags.test(currentElement.tagName)) { 702 e.preventDefault(); 703 } 704 705 switch(e.type) { 706 case EVENT_TOUCHSTART: 707 //The last element we tapped on. 708 if(initialElement) { 709 initialElement.blur(); 710 } 711 712 _instance.stopAnimateTo(); 713 714 initialElement = currentElement; 715 716 initialTouchY = lastTouchY = currentTouchY; 717 initialTouchX = currentTouchX; 718 initialTouchTime = currentTouchTime; 719 720 break; 721 case EVENT_TOUCHMOVE: 722 //Prevent default event on touchIgnore elements in case they don't have focus yet. 723 if(rxTouchIgnoreTags.test(currentElement.tagName) && document.activeElement !== currentElement) { 724 e.preventDefault(); 725 } 726 727 deltaY = currentTouchY - lastTouchY; 728 deltaTime = currentTouchTime - lastTouchTime; 729 730 _instance.setScrollTop(_mobileOffset - deltaY, true); 731 732 lastTouchY = currentTouchY; 733 lastTouchTime = currentTouchTime; 734 break; 735 default: 736 case EVENT_TOUCHCANCEL: 737 case EVENT_TOUCHEND: 738 var distanceY = initialTouchY - currentTouchY; 739 var distanceX = initialTouchX - currentTouchX; 740 var distance2 = distanceX * distanceX + distanceY * distanceY; 741 742 //Check if it was more like a tap (moved less than 7px). 743 if(distance2 < 49) { 744 if(!rxTouchIgnoreTags.test(initialElement.tagName)) { 745 initialElement.focus(); 746 747 //It was a tap, click the element. 748 var clickEvent = document.createEvent('MouseEvents'); 749 clickEvent.initMouseEvent('click', true, true, e.view, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null); 750 initialElement.dispatchEvent(clickEvent); 751 } 752 753 return; 754 } 755 756 initialElement = undefined; 757 758 var speed = deltaY / deltaTime; 759 760 //Cap speed at 3 pixel/ms. 761 speed = Math.max(Math.min(speed, 3), -3); 762 763 var duration = Math.abs(speed / _mobileDeceleration); 764 var targetOffset = speed * duration + 0.5 * _mobileDeceleration * duration * duration; 765 var targetTop = _instance.getScrollTop() - targetOffset; 766 767 //Relative duration change for when scrolling above bounds. 768 var targetRatio = 0; 769 770 //Change duration proportionally when scrolling would leave bounds. 771 if(targetTop > _maxKeyFrame) { 772 targetRatio = (_maxKeyFrame - targetTop) / targetOffset; 773 774 targetTop = _maxKeyFrame; 775 } else if(targetTop < 0) { 776 targetRatio = -targetTop / targetOffset; 777 778 targetTop = 0; 779 } 780 781 duration = duration * (1 - targetRatio); 782 783 _instance.animateTo((targetTop + 0.5) | 0, {easing: 'outCubic', duration: duration}); 784 break; 785 } 786 }); 787 788 //Just in case there has already been some native scrolling, reset it. 789 window.scrollTo(0, 0); 790 791 // removed the line below in testing for the bug that caused no scrolling 792 //documentElement.style.overflow = body.style.overflow = 'hidden'; 793 }; 794 795 /**
796 * Updates key frames which depend on others / need to be updated on resize. 797 * That is "end" in "absolute" mode and all key frames in "relative" mode. 798 * Also handles constants, because they may change on resize. 799 */ 800 var _updateDependentKeyFrames = function() { 801 var viewportHeight = documentElement.clientHeight; 802 var processedConstants = _processConstants(); 803 var skrollable; 804 var element; 805 var anchorTarget; 806 var keyFrames; 807 var keyFrameIndex; 808 var keyFramesLength; 809 var kf; 810 var skrollableIndex; 811 var skrollablesLength; 812 var offset; 813 var constantValue; 814 815 //First process all relative-mode elements and find the max key frame. 816 skrollableIndex = 0; 817 skrollablesLength = _skrollables.length; 818 819 for(; skrollableIndex < skrollablesLength; skrollableIndex++) { 820 skrollable = _skrollables[skrollableIndex]; 821 element = skrollable.element; 822 anchorTarget = skrollable.anchorTarget; 823 keyFrames = skrollable.keyFrames; 824 825 keyFrameIndex = 0; 826 keyFramesLength = keyFrames.length; 827 828 for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) { 829 kf = keyFrames[keyFrameIndex]; 830 831 offset = kf.offset; 832 constantValue = processedConstants[kf.constant] || 0; 833 834 kf.frame = offset; 835 836 if(kf.isPercentage) { 837 //Convert the offset to percentage of the viewport height. 838 offset = offset * viewportHeight; 839 840 //Absolute + percentage mode. 841 kf.frame = offset; 842 } 843 844 if(kf.mode === 'relative') { 845 _reset(element); 846 847 kf.frame = _instance.relativeToAbsolute(anchorTarget, kf.anchors[0], kf.anchors[1]) - offset; 848 849 _reset(element, true); 850 } 851 852 kf.frame += constantValue; 853 854 //Only search for max key frame when forceHeight is enabled. 855 if(_forceHeight) { 856 //Find the max key frame, but don't use one of the data-end ones for comparison. 857 if(!kf.isEnd && kf.frame > _maxKeyFrame) { 858 _maxKeyFrame = kf.frame; 859 } 860 } 861 } 862 } 863 864 //#133: The document can be larger than the maxKeyFrame we found. 865 _maxKeyFrame = Math.max(_maxKeyFrame, _getDocumentHeight()); 866 867 //Now process all data-end keyframes. 868 skrollableIndex = 0; 869 skrollablesLength = _skrollables.length; 870 871 for(; skrollableIndex < skrollablesLength; skrollableIndex++) { 872 skrollable = _skrollables[skrollableIndex]; 873 keyFrames = skrollable.keyFrames; 874 875 keyFrameIndex = 0; 876 keyFramesLength = keyFrames.length; 877 878 for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) { 879 kf = keyFrames[keyFrameIndex]; 880 881 constantValue = processedConstants[kf.constant] || 0; 882 883 if(kf.isEnd) { 884 kf.frame = _maxKeyFrame - kf.offset + constantValue; 885 } 886 } 887 888 skrollable.keyFrames.sort(_keyFrameComparator); 889 } 890 }; 891 892 /** 893 * Calculates and sets the style properties for the element at the given frame. 894 * @param fakeFrame The frame to render at when smooth scrolling is enabled. 895 * @param actualFrame The actual frame we are at. 896 */ 897 var _calcSteps = function(fakeFrame, actualFrame) { 898 //Iterate over all skrollables. 899 var skrollableIndex = 0; 900 var skrollablesLength = _skrollables.length; 901 902 for(; skrollableIndex < skrollablesLength; skrollableIndex++) { 903 var skrollable = _skrollables[skrollableIndex]; 904 var element = skrollable.element; 905 var frame = skrollable.smoothScrolling ? fakeFrame : actualFrame; 906 var frames = skrollable.keyFrames; 907 var firstFrame = frames[0].frame; 908 var lastFrame = frames[frames.length - 1].frame; 909 var beforeFirst = frame < firstFrame; 910 var afterLast = frame > lastFrame; 911 var firstOrLastFrame = frames[beforeFirst ? 0 : frames.length - 1]; 912 var key; 913 var value; 914 915 //If we are before/after the first/last frame, set the styles according to the given edge strategy. 916 if(beforeFirst || afterLast) { 917 //Check if we already handled this edge case last time. 918 //Note: using setScrollTop it's possible that we jumped from one edge to the other. 919 if(beforeFirst && skrollable.edge === -1 || afterLast && skrollable.edge === 1) { 920 continue; 921 } 922 923 //Add the skrollr-before or -after class. 924 _updateClass(element, [beforeFirst ? SKROLLABLE_BEFORE_CLASS : SKROLLABLE_AFTER_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_BETWEEN_CLASS, SKROLLABLE_AFTER_CLASS]); 925 926 //Remember that we handled the edge case (before/after the first/last keyframe). 927 skrollable.edge = beforeFirst ? -1 : 1; 928 929 switch(skrollable.edgeStrategy) { 930 case 'reset': 931 _reset(element); 932 continue; 933 case 'ease': 934 //Handle this case like it would be exactly at first/last keyframe and just pass it on. 935 frame = firstOrLastFrame.frame; 936 break; 937 default: 938 case 'set': 939 var props = firstOrLastFrame.props; 940 941 for(key in props) { 942 if(hasProp.call(props, key)) { 943 value = _interpolateString(props[key].value); 944 945 skrollr.setStyle(element, key, value); 946 } 947 } 948 949 continue; 950 } 951 } else { 952 //Did we handle an edge last time? 953 if(skrollable.edge !== 0) {
954 _updateClass(element, [SKROLLABLE_CLASS, SKROLLABLE_BETWEEN_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_AFTER_CLASS]); 955 skrollable.edge = 0; 956 } 957 } 958 959 //Find out between which two key frames we are right now. 960 var keyFrameIndex = 0; 961 var framesLength = frames.length - 1; 962 963 for(; keyFrameIndex < framesLength; keyFrameIndex++) { 964 if(frame >= frames[keyFrameIndex].frame && frame <= frames[keyFrameIndex + 1].frame) { 965 var left = frames[keyFrameIndex]; 966 var right = frames[keyFrameIndex + 1]; 967 968 for(key in left.props) { 969 if(hasProp.call(left.props, key)) { 970 var progress = (frame - left.frame) / (right.frame - left.frame); 971 972 //Transform the current progress using the given easing function. 973 progress = left.props[key].easing(progress); 974 975 //Interpolate between the two values 976 value = _calcInterpolation(left.props[key].value, right.props[key].value, progress); 977 978 value = _interpolateString(value); 979 980 skrollr.setStyle(element, key, value); 981 } 982 } 983 984 break; 985 } 986 } 987 } 988 }; 989 990 /** 991 * Renders all elements. 992 */ 993 var _render = function() { 994 if(_requestReflow) { 995 _requestReflow = false; 996 _reflow(); 997 } 998 999 //We may render something else than the actual scrollbar position. 1000 var renderTop = _instance.getScrollTop(); 1001 1002 //If there's an animation, which ends in current render call, call the callback after rendering. 1003 var afterAnimationCallback; 1004 var now = _now(); 1005 var progress; 1006 1007 //Before actually rendering handle the scroll animation, if any. 1008 if(_scrollAnimation) { 1009 //It's over 1010 if(now >= _scrollAnimation.endTime) { 1011 renderTop = _scrollAnimation.targetTop; 1012 afterAnimationCallback = _scrollAnimation.done; 1013 _scrollAnimation = undefined; 1014 } else { 1015 //Map the current progress to the new progress using given easing function. 1016 progress = _scrollAnimation.easing((now - _scrollAnimation.startTime) / _scrollAnimation.duration); 1017 1018 renderTop = (_scrollAnimation.startTop + progress * _scrollAnimation.topDiff) | 0; 1019 } 1020 1021 _instance.setScrollTop(renderTop, true); 1022 } 1023 //Smooth scrolling only if there's no animation running and if we're not forcing the rendering. 1024 else if(!_forceRender) { 1025 var smoothScrollingDiff = _smoothScrolling.targetTop - renderTop; 1026 1027 //The user scrolled, start new smooth scrolling. 1028 if(smoothScrollingDiff) { 1029 _smoothScrolling = { 1030 startTop: _lastTop, 1031 topDiff: renderTop - _lastTop, 1032 targetTop: renderTop, 1033 startTime: _lastRenderCall, 1034 endTime: _lastRenderCall + _smoothScrollingDuration 1035 }; 1036 } 1037 1038 //Interpolate the internal scroll position (not the actual scrollbar). 1039 if(now <= _smoothScrolling.endTime) { 1040 //Map the current progress to the new progress using easing function. 1041 progress = easings.sqrt((now - _smoothScrolling.startTime) / _smoothScrollingDuration); 1042 1043 renderTop = (_smoothScrolling.startTop + progress * _smoothScrolling.topDiff) | 0; 1044 } 1045 } 1046 1047 //That's were we actually "scroll" on mobile. 1048 if(_isMobile && _skrollrBody) { 1049 //Set the transform ("scroll it"). 1050 skrollr.setStyle(_skrollrBody, 'transform', 'translate(0, ' + -(_mobileOffset) + 'px) ' + _translateZ); 1051 } 1052 1053 //Did the scroll position even change? 1054 if(_forceRender || _lastTop !== renderTop) { 1055 //Remember in which direction are we scrolling? 1056 _direction = (renderTop > _lastTop) ? 'down' : (renderTop < _lastTop ? 'up' : _direction); 1057 1058 _forceRender = false;
1059 1060 var listenerParams = { 1061 curTop: renderTop, 1062 lastTop: _lastTop, 1063 maxTop: _maxKeyFrame, 1064 direction: _direction 1065 }; 1066 1067 //Tell the listener we are about to render. 1068 var continueRendering = _listeners.beforerender && _listeners.beforerender.call(_instance, listenerParams); 1069 1070 //The beforerender listener function is able the cancel rendering. 1071 if(continueRendering !== false) { 1072 //Now actually interpolate all the styles. 1073 _calcSteps(renderTop, _instance.getScrollTop()); 1074 1075 //Remember when we last rendered. 1076 _lastTop = renderTop; 1077 1078 if(_listeners.render) { 1079 _listeners.render.call(_instance, listenerParams); 1080 } 1081 } 1082 1083 if(afterAnimationCallback) { 1084 afterAnimationCallback.call(_instance, false); 1085 } 1086 } 1087 1088 _lastRenderCall = now; 1089 }; 1090 1091 /** 1092 * Parses the properties for each key frame of the given skrollable. 1093 */ 1094 var _parseProps = function(skrollable) { 1095 //Iterate over all key frames 1096 var keyFrameIndex = 0; 1097 var keyFramesLength = skrollable.keyFrames.length; 1098 1099 for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) { 1100 var frame = skrollable.keyFrames[keyFrameIndex]; 1101 var easing; 1102 var value; 1103 var prop; 1104 var props = {}; 1105 1106 var match; 1107 1108 while((match = rxPropValue.exec(frame.props)) !== null) { 1109 prop = match[1]; 1110 value = match[2]; 1111 1112 easing = prop.match(rxPropEasing); 1113 1114 //Is there an easing specified for this prop? 1115 if(easing !== null) { 1116 prop = easing[1]; 1117 easing = easing[2]; 1118 } else { 1119 easing = DEFAULT_EASING; 1120 } 1121 1122 //Exclamation point at first position forces the value to be taken literal. 1123 value = value.indexOf('!') ? _parseProp(value) : [value.slice(1)]; 1124 1125 //Save the prop for this key frame with his value and easing function 1126 props[prop] = { 1127 value: value, 1128 easing: easings[easing] 1129 }; 1130 } 1131 1132 frame.props = props; 1133 } 1134 }; 1135 1136 /** 1137 * Parses a value extracting numeric values and generating a format string 1138 * for later interpolation of the new values in old string. 1139 * 1140 * @param val The CSS value to be parsed. 1141 * @return Something like ["rgba(?%,?%, ?%,?)", 100, 50, 0, .7] 1142 * where the first element is the format string later used 1143 * and all following elements are the numeric value. 1144 */ 1145 var _parseProp = function(val) { 1146 var numbers = []; 1147 1148 //One special case, where floats don't work. 1149 //We replace all occurences of rgba colors 1150 //which don't use percentage notation with the percentage notation. 1151 rxRGBAIntegerColor.lastIndex = 0; 1152 val = val.replace(rxRGBAIntegerColor, function(rgba) { 1153 return rgba.replace(rxNumericValue, function(n) { 1154 return n / 255 * 100 + '%'; 1155 }); 1156 }); 1157 1158 //Handle prefixing of "gradient" values. 1159 //For now only the prefixed value will be set. Unprefixed isn't supported anyway. 1160 if(theDashedCSSPrefix) { 1161 rxGradient.lastIndex = 0; 1162 val = val.replace(rxGradient, function(s) { 1163 return theDashedCSSPrefix + s; 1164 }); 1165 } 1166 1167 //Now parse ANY number inside this string and create a format string. 1168 val = val.replace(rxNumericValue, function(n) { 1169 numbers.push(+n); 1170 return '{?}'; 1171 }); 1172 1173 //Add the formatstring as first value. 1174 numbers.unshift(val); 1175 1176 return numbers; 1177 }; 1178 1179 /** 1180 * Fills the key frames with missing left and right hand properties. 1181 * If key frame 1 has property X and key frame 2 is missing X, 1182 * but key frame 3 has X again, then we need to assign X to key frame 2 too. 1183 * 1184 * @param sk A skrollable. 1185 */ 1186 var _fillProps = function(sk) { 1187 //Will collect the properties key frame by key frame 1188 var propList = {}; 1189 var keyFrameIndex; 1190 var keyFramesLength; 1191 1192 //Iterate over all key frames from left to right 1193 keyFrameIndex = 0; 1194 keyFramesLength = sk.keyFrames.length; 1195 1196 for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) { 1197 _fillPropForFrame(sk.keyFrames[keyFrameIndex], propList); 1198 } 1199 1200 //Now do the same from right to fill the last gaps 1201 1202 propList = {}; 1203 1204 //Iterate over all key frames from right to left 1205 keyFrameIndex = sk.keyFrames.length - 1; 1206 1207 for(; keyFrameIndex >= 0; keyFrameIndex--) { 1208 _fillPropForFrame(sk.keyFrames[keyFrameIndex], propList); 1209 } 1210 }; 1211 1212 var _fillPropForFrame = function(frame, propList) { 1213 var key; 1214 1215 //For each key frame iterate over all right hand properties and assign them, 1216 //but only if the current key frame doesn't have the property by itself 1217 for(key in propList) { 1218 //The current frame misses this property, so assign it. 1219 if(!hasProp.call(frame.props, key)) { 1220 frame.props[key] = propList[key]; 1221 } 1222 } 1223 1224 //Iterate over all props of the current frame and collect them 1225 for(key in frame.props) { 1226 propList[key] = frame.props[key]; 1227 } 1228 }; 1229 1230 /** 1231 * Calculates the new values for two given values array. 1232 */ 1233 var _calcInterpolation = function(val1, val2, progress) { 1234 var valueIndex; 1235 var val1Length = val1.length; 1236 1237 //They both need to have the same length 1238 if(val1Length !== val2.length) { 1239 throw 'Can\'t interpolate between "' + val1[0] + '" and "' + val2[0] + '"'; 1240 } 1241
1242 //Add the format string as first element. 1243 var interpolated = [val1[0]]; 1244 1245 valueIndex = 1; 1246 1247 for(; valueIndex < val1Length; valueIndex++) { 1248 //That's the line where the two numbers are actually interpolated. 1249 interpolated[valueIndex] = val1[valueIndex] + ((val2[valueIndex] - val1[valueIndex]) * progress); 1250 } 1251 1252 return interpolated; 1253 }; 1254 1255 /** 1256 * Interpolates the numeric values into the format string. 1257 */ 1258 var _interpolateString = function(val) { 1259 var valueIndex = 1; 1260 1261 rxInterpolateString.lastIndex = 0; 1262 1263 return val[0].replace(rxInterpolateString, function() { 1264 return val[valueIndex++]; 1265 }); 1266 }; 1267 1268 /** 1269 * Resets the class and style attribute to what it was before skrollr manipulated the element. 1270 * Also remembers the values it had before reseting, in order to undo the reset. 1271 */ 1272 var _reset = function(elements, undo) { 1273 //We accept a single element or an array of elements. 1274 elements = [].concat(elements); 1275 1276 var skrollable; 1277 var element; 1278 var elementsIndex = 0; 1279 var elementsLength = elements.length; 1280 1281 for(; elementsIndex < elementsLength; elementsIndex++) { 1282 element = elements[elementsIndex]; 1283 skrollable = _skrollables[element[SKROLLABLE_ID_DOM_PROPERTY]]; 1284 1285 //Couldn't find the skrollable for this DOM element. 1286 if(!skrollable) { 1287 continue; 1288 } 1289 1290 if(undo) { 1291 //Reset class and style to the "dirty" (set by skrollr) values. 1292 element.style.cssText = skrollable.dirtyStyleAttr; 1293 _updateClass(element, skrollable.dirtyClassAttr); 1294 } else { 1295 //Remember the "dirty" (set by skrollr) class and style. 1296 skrollable.dirtyStyleAttr = element.style.cssText; 1297 skrollable.dirtyClassAttr = _getClass(element); 1298 1299 //Reset class and style to what it originally was. 1300 element.style.cssText = skrollable.styleAttr; 1301 _updateClass(element, skrollable.classAttr); 1302 } 1303 } 1304 }; 1305 1306 /** 1307 * Detects support for 3d transforms by applying it to the skrollr-body. 1308 */ 1309 var _detect3DTransforms = function() { 1310 _translateZ = 'translateZ(0)'; 1311 skrollr.setStyle(_skrollrBody, 'transform', _translateZ); 1312 1313 var computedStyle = getStyle(_skrollrBody); 1314 var computedTransform = computedStyle.getPropertyValue('transform'); 1315 var computedTransformWithPrefix = computedStyle.getPropertyValue(theDashedCSSPrefix + 'transform'); 1316 var has3D = (computedTransform && computedTransform !== 'none') || (computedTransformWithPrefix && computedTransformWithPrefix !== 'none'); 1317 1318 if(!has3D) { 1319 _translateZ = ''; 1320 } 1321 }; 1322 1323 /** 1324 * Set the CSS property on the given element. Sets prefixed properties as well. 1325 */ 1326 skrollr.setStyle = function(el, prop, val) { 1327 var style = el.style; 1328 1329 //Camel case. 1330 prop = prop.replace(rxCamelCase, rxCamelCaseFn).replace('-', ''); 1331 1332 //Make sure z-index gets a <integer>. 1333 //This is the only <integer> case we need to handle. 1334 if(prop === 'zIndex') { 1335 if(isNaN(val)) { 1336 //If it's not a number, don't touch it. 1337 //It could for example be "auto" (#351). 1338 style[prop] = val; 1339 } else { 1340 //Floor the number. 1341 style[prop] = '' + (val | 0); 1342 } 1343 } 1344 //#64: "float" can't be set across browsers. Needs to use "cssFloat" for all except IE. 1345 else if(prop === 'float') { 1346 style.styleFloat = style.cssFloat = val; 1347 } 1348 else { 1349 //Need try-catch for old IE. 1350 try { 1351 //Set prefixed property if there's a prefix. 1352 if(theCSSPrefix) { 1353 style[theCSSPrefix + prop.slice(0,1).toUpperCase() + prop.slice(1)] = val; 1354 } 1355 1356 //Set unprefixed. 1357 style[prop] = val; 1358 } catch(ignore) {} 1359 } 1360 }; 1361 1362 /** 1363 * Cross browser event handling. 1364 */ 1365 var _addEvent = skrollr.addEvent = function(element, names, callback) { 1366 var intermediate = function(e) { 1367 //Normalize IE event stuff. 1368 e = e || window.event; 1369 1370 if(!e.target) { 1371 e.target = e.srcElement; 1372 } 1373 1374 if(!e.preventDefault) { 1375 e.preventDefault = function() { 1376 e.returnValue = false; 1377 }; 1378 } 1379 1380 return callback.call(this, e); 1381 }; 1382 1383 names = names.split(' '); 1384 1385 var name; 1386 var nameCounter = 0; 1387 var namesLength = names.length; 1388 1389 for(; nameCounter < namesLength; nameCounter++) { 1390 name = names[nameCounter]; 1391 1392 if(element.addEventListener) { 1393 element.addEventListener(name, callback, false); 1394 } else { 1395 element.attachEvent('on' + name, intermediate); 1396 } 1397 1398 //Remember the events to be able to flush them later. 1399 _registeredEvents.push({ 1400 element: element, 1401 name: name, 1402 listener: callback 1403 }); 1404 } 1405 }; 1406 1407 var _removeEvent = skrollr.removeEvent = function(element, names, callback) { 1408 names = names.split(' '); 1409 1410 var nameCounter = 0; 1411 var namesLength = names.length; 1412 1413 for(; nameCounter < namesLength; nameCounter++) { 1414 if(element.removeEventListener) { 1415 element.removeEventListener(names[nameCounter], callback, false); 1416 } else { 1417 element.detachEvent('on' + names[nameCounter], callback); 1418 } 1419 } 1420 }; 1421 1422 var _removeAllEvents = function() { 1423 var eventData; 1424 var eventCounter = 0; 1425 var eventsLength = _registeredEvents.length; 1426 1427 for(; eventCounter < eventsLength; eventCounter++) { 1428 eventData = _registeredEvents[eventCounter]; 1429 1430 _removeEvent(eventData.element, eventData.name, eventData.listener); 1431 } 1432 1433 _registeredEvents = []; 1434 }; 1435 1436 var _reflow = function() { 1437 var pos = _instance.getScrollTop(); 1438 1439 //Will be recalculated by _updateDependentKeyFrames. 1440 _maxKeyFrame = 0; 1441 1442 if(_forceHeight && !_isMobile) { 1443 //un-"force" the height to not mess with the calculations in _updateDependentKeyFrames (#216). 1444 // body.style.height = 'auto'; 1445 } 1446 1447 _updateDependentKeyFrames(); 1448 1449 if(_forceHeight && !_isMobile) { 1450 //"force" the height. 1451 // body.style.height = (_maxKeyFrame + documentElement.clientHeight) + 'px'; 1452 } 1453 1454 //The scroll offset may now be larger than needed (on desktop the browser/os prevents scrolling farther than the bottom). 1455 if(_isMobile) { 1456 _instance.setScrollTop(Math.min(_instance.getScrollTop(), _maxKeyFrame)); 1457 } else { 1458 //Remember and reset the scroll pos (#217). 1459 _instance.setScrollTop(pos, true); 1460 } 1461 1462 _forceRender = true; 1463 }; 1464 1465 /* 1466 * Returns a copy of the constants object where all functions and strings have been evaluated. 1467 */ 1468 var _processConstants = function() {
1469 var viewportHeight = documentElement.clientHeight; 1470 var copy = {}; 1471 var prop; 1472 var value; 1473 1474 for(prop in _constants) { 1475 value = _constants[prop]; 1476 1477 if(typeof value === 'function') { 1478 value = value.call(_instance); 1479 } 1480 //Percentage offset. 1481 else if((/p$/).test(value)) { 1482 value = (value.slice(0, -1) / 100) * viewportHeight; 1483 } 1484 1485 copy[prop] = value; 1486 } 1487 1488 return copy; 1489 }; 1490 1491 /* 1492 * Returns the height of the document. 1493 */ 1494 var _getDocumentHeight = function() { 1495 var skrollrBodyHeight = (_skrollrBody && _skrollrBody.offsetHeight || 0); 1496 var bodyHeight = Math.max(skrollrBodyHeight, body.scrollHeight, body.offsetHeight, documentElement.scrollHeight, documentElement.offsetHeight, documentElement.clientHeight); 1497 1498 return bodyHeight - documentElement.clientHeight; 1499 }; 1500 1501 /** 1502 * Returns a string of space separated classnames for the current element. 1503 * Works with SVG as well. 1504 */ 1505 var _getClass = function(element) { 1506 var prop = 'className'; 1507 1508 //SVG support by using className.baseVal instead of just className. 1509 if(window.SVGElement && element instanceof window.SVGElement) { 1510 element = element[prop]; 1511 prop = 'baseVal'; 1512 } 1513 1514 return element[prop]; 1515 }; 1516 1517 /** 1518 * Adds and removes a CSS classes. 1519 * Works with SVG as well.
1520 * add and remove are arrays of strings, 1521 * or if remove is ommited add is a string and overwrites all classes. 1522 */ 1523 var _updateClass = function(element, add, remove) { 1524 var prop = 'className'; 1525 1526 //SVG support by using className.baseVal instead of just className. 1527 if(window.SVGElement && element instanceof window.SVGElement) { 1528 element = element[prop]; 1529 prop = 'baseVal'; 1530 } 1531 1532 //When remove is ommited, we want to overwrite/set the classes. 1533 if(remove === undefined) { 1534 element[prop] = add; 1535 return; 1536 } 1537 1538 //Cache current classes. We will work on a string before passing back to DOM. 1539 var val = element[prop]; 1540 1541 //All classes to be removed. 1542 var classRemoveIndex = 0; 1543 var removeLength = remove.length; 1544 1545 for(; classRemoveIndex < removeLength; classRemoveIndex++) { 1546 val = _untrim(val).replace(_untrim(remove[classRemoveIndex]), ' '); 1547 } 1548 1549 val = _trim(val); 1550 1551 //All classes to be added. 1552 var classAddIndex = 0; 1553 var addLength = add.length; 1554 1555 for(; classAddIndex < addLength; classAddIndex++) { 1556 //Only add if el not already has class. 1557 if(_untrim(val).indexOf(_untrim(add[classAddIndex])) === -1) { 1558 val += ' ' + add[classAddIndex]; 1559 } 1560 } 1561 1562 element[prop] = _trim(val); 1563 }; 1564 1565 var _trim = function(a) { 1566 return a.replace(rxTrim, ''); 1567 }; 1568 1569 /** 1570 * Adds a space before and after the string. 1571 */ 1572 var _untrim = function(a) { 1573 return ' ' + a + ' '; 1574 }; 1575 1576 var _now = Date.now || function() { 1577 return +new Date(); 1578 }; 1579 1580 var _keyFrameComparator = function(a, b) { 1581 return a.frame - b.frame; 1582 }; 1583 1584 /* 1585 * Private variables. 1586 */ 1587 1588 //Singleton 1589 var _instance; 1590 1591 /* 1592 A list of all elements which should be animated associated with their the metadata. 1593 Exmaple skrollable with two key frames animating from 100px width to 20px: 1594 1595 skrollable = { 1596 element: <the DOM element>, 1597 styleAttr: <style attribute of the element before skrollr>, 1598 classAttr: <class attribute of the element before skrollr>, 1599 keyFrames: [ 1600 { 1601 frame: 100, 1602 props: { 1603 width: { 1604 value: ['{?}px', 100], 1605 easing: <reference to easing function> 1606 } 1607 }, 1608 mode: "absolute" 1609 }, 1610 { 1611 frame: 200, 1612 props: { 1613 width: { 1614 value: ['{?}px', 20], 1615 easing: <reference to easing function> 1616 } 1617 }, 1618 mode: "absolute" 1619 } 1620 ] 1621 }; 1622 */ 1623 var _skrollables; 1624 1625 var _skrollrBody; 1626 1627 var _listeners; 1628 var _forceHeight; 1629 var _maxKeyFrame = 0; 1630 1631 var _scale = 1; 1632 var _constants; 1633 1634 var _mobileDeceleration; 1635 1636 //Current direction (up/down). 1637 var _direction = 'down'; 1638 1639 //The last top offset value. Needed to determine direction. 1640 var _lastTop = -1; 1641 1642 //The last time we called the render method (doesn't mean we rendered!). 1643 var _lastRenderCall = _now(); 1644 1645 //For detecting if it actually resized (#271). 1646 var _lastViewportWidth = 0; 1647 var _lastViewportHeight = 0; 1648 1649 var _requestReflow = false; 1650 1651 //Will contain data about a running scrollbar animation, if any. 1652 var _scrollAnimation; 1653 1654 var _smoothScrollingEnabled; 1655 1656 var _smoothScrollingDuration; 1657 1658 //Will contain settins for smooth scrolling if enabled. 1659 var _smoothScrolling; 1660 1661 //Can be set by any operation/event to force rendering even if the scrollbar didn't move. 1662 var _forceRender; 1663 1664 //Each skrollable gets an unique ID incremented for each skrollable. 1665 //The ID is the index in the _skrollables array. 1666 var _skrollableIdCounter = 0; 1667 1668 var _edgeStrategy; 1669 1670 1671 //Mobile specific vars. Will be stripped by UglifyJS when not in use. 1672 var _isMobile = false; 1673 1674 //The virtual scroll offset when using mobile scrolling. 1675 var _mobileOffset = 0; 1676 1677 //If the browser supports 3d transforms, this will be filled with 'translateZ(0)' (empty string otherwise). 1678 var _translateZ; 1679 1680 //Will contain data about registered events by skrollr. 1681 var _registeredEvents = []; 1682 1683 //Animation frame id returned by RequestAnimationFrame (or timeout when RAF is not supported). 1684 var _animFrame; 1685}(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.