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