1/** 2 * x is a value between 0 and 1, indicating where in the animation you are. 3 */ 4var duScrollDefaultEasing = function (x) { 5 'use strict'; 6 7 if(x < 0.5) { 8 return Math.pow(x*2, 2)/2; 9 } 10 return 1-Math.pow((1-x)*2, 2)/2; 11}; 12 13var duScroll = angular.module('duScroll', [ 14 'duScroll.scrollspy', 15 'duScroll.smoothScroll', 16 'duScroll.scrollContainer', 17 'duScroll.spyContext', 18 'duScroll.scrollHelpers' 19]) 20 //Default animation duration for smoothScroll directive 21 .value('duScrollDuration', 350) 22 //Scrollspy debounce interval, set to 0 to disable 23 .value('duScrollSpyWait', 100) 24 //Scrollspy forced refresh interval, use if your content changes or reflows without scrolling. 25 //0 to disable 26 .value('duScrollSpyRefreshInterval', 0) 27 //Wether or not multiple scrollspies can be active at once 28 .value('duScrollGreedy', false) 29 //Default offset for smoothScroll directive 30 .value('duScrollOffset', 0) 31 //Default easing function for scroll animation 32 .value('duScrollEasing', duScrollDefaultEasing) 33 //Which events on the container (such as body) should cancel scroll animations 34 .value('duScrollCancelOnEvents', 'scroll mousedown mousewheel touchmove keydown') 35 //Whether or not to activate the last scrollspy, when page/container bottom is reached 36 .value('duScrollBottomSpy', false) 37 //Active class name 38 .value('duScrollActiveClass', 'active'); 39 40if (typeof module !== 'undefined' && module && module.exports) { 41 module.exports = duScroll; 42} 43 44 45angular.module('duScroll.scrollHelpers', ['duScroll.requestAnimation']) 46.run(["$window", "$q", "cancelAnimation", "requestAnimation", "duScrollEasing", "duScrollDuration", "duScrollOffset", "duScrollCancelOnEvents", function($window, $q, cancelAnimation, requestAnimation, duScrollEasing, duScrollDuration, duScrollOffset, duScrollCancelOnEvents) { 47 'use strict'; 48 49 var proto = {}; 50 51 var isDocument = function(el) { 52 return (typeof HTMLDocument !== 'undefined' && el instanceof HTMLDocument) || (el.nodeType && el.nodeType === el.DOCUMENT_NODE); 53 }; 54 55 var isElement = function(el) { 56 return (typeof HTMLElement !== 'undefined' && el instanceof HTMLElement) || (el.nodeType && el.nodeType === el.ELEMENT_NODE); 57 }; 58 59 var unwrap = function(el) { 60 return isElement(el) || isDocument(el) ? el : el[0]; 61 }; 62 63 proto.duScrollTo = function(left, top, duration, easing) { 64 var aliasFn; 65 if(angular.isElement(left)) { 66 aliasFn = this.duScrollToElement; 67 } else if(angular.isDefined(duration)) { 68 aliasFn = this.duScrollToAnimated; 69 } 70 if(aliasFn) { 71 return aliasFn.apply(this, arguments); 72 } 73 var el = unwrap(this); 74 if(isDocument(el)) { 75 return $window.scrollTo(left, top); 76 } 77 el.scrollLeft = left; 78 el.scrollTop = top; 79 }; 80 81 var scrollAnimation, deferred; 82 proto.duScrollToAnimated = function(left, top, duration, easing) { 83 if(duration && !easing) { 84 easing = duScrollEasing; 85 } 86 var startLeft = this.duScrollLeft(), 87 startTop = this.duScrollTop(), 88 deltaLeft = Math.round(left - startLeft), 89 deltaTop = Math.round(top - startTop); 90 91 var startTime = null, progress = 0; 92 var el = this; 93 94 var cancelScrollAnimation = function($event) { 95 if (!$event || (progress && $event.which > 0)) { 96 if(duScrollCancelOnEvents) { 97 el.unbind(duScrollCancelOnEvents, cancelScrollAnimation); 98 } 99 cancelAnimation(scrollAnimation); 100 deferred.reject(); 101 scrollAnimation = null; 102 } 103 }; 104 105 if(scrollAnimation) { 106 cancelScrollAnimation(); 107 } 108 deferred = $q.defer(); 109 110 if(duration === 0 || (!deltaLeft && !deltaTop)) { 111 if(duration === 0) { 112 el.duScrollTo(left, top); 113 } 114 deferred.resolve(); 115 return deferred.promise; 116 } 117 118 var animationStep = function(timestamp) { 119 if (startTime === null) { 120 startTime = timestamp; 121 } 122 123 progress = timestamp - startTime; 124 var percent = (progress >= duration ? 1 : easing(progress/duration)); 125 126 el.scrollTo( 127 startLeft + Math.ceil(deltaLeft * percent),
128 startTop + Math.ceil(deltaTop * percent) 129 ); 130 if(percent < 1) { 131 scrollAnimation = requestAnimation(animationStep); 132 } else { 133 if(duScrollCancelOnEvents) { 134 el.unbind(duScrollCancelOnEvents, cancelScrollAnimation); 135 } 136 scrollAnimation = null; 137 deferred.resolve(); 138 } 139 }; 140 141 //Fix random mobile safari bug when scrolling to top by hitting status bar 142 el.duScrollTo(startLeft, startTop); 143 144 if(duScrollCancelOnEvents) { 145 el.bind(duScrollCancelOnEvents, cancelScrollAnimation); 146 } 147 148 scrollAnimation = requestAnimation(animationStep); 149 return deferred.promise; 150 }; 151 152 proto.duScrollToElement = function(target, offset, duration, easing) { 153 var el = unwrap(this); 154 if(!angular.isNumber(offset) || isNaN(offset)) { 155 offset = duScrollOffset; 156 } 157 var top = this.duScrollTop() + unwrap(target).getBoundingClientRect().top - offset; 158 if(isElement(el)) { 159 top -= el.getBoundingClientRect().top; 160 } 161 return this.duScrollTo(0, top, duration, easing); 162 }; 163 164 proto.duScrollLeft = function(value, duration, easing) { 165 if(angular.isNumber(value)) { 166 return this.duScrollTo(value, this.duScrollTop(), duration, easing); 167 } 168 var el = unwrap(this); 169 if(isDocument(el)) { 170 return $window.scrollX || document.documentElement.scrollLeft || document.body.scrollLeft; 171 } 172 return el.scrollLeft; 173 }; 174 proto.duScrollTop = function(value, duration, easing) { 175 if(angular.isNumber(value)) { 176 return this.duScrollTo(this.duScrollLeft(), value, duration, easing); 177 } 178 var el = unwrap(this); 179 if(isDocument(el)) { 180 return $window.scrollY || document.documentElement.scrollTop || document.body.scrollTop; 181 } 182 return el.scrollTop; 183 }; 184 185 proto.duScrollToElementAnimated = function(target, offset, duration, easing) { 186 return this.duScrollToElement(target, offset, duration || duScrollDuration, easing); 187 }; 188 189 proto.duScrollTopAnimated = function(top, duration, easing) { 190 return this.duScrollTop(top, duration || duScrollDuration, easing); 191 }; 192 193 proto.duScrollLeftAnimated = function(left, duration, easing) { 194 return this.duScrollLeft(left, duration || duScrollDuration, easing); 195 }; 196 197 angular.forEach(proto, function(fn, key) { 198 angular.element.prototype[key] = fn; 199 200 //Remove prefix if not already claimed by jQuery / ui.utils 201 var unprefixed = key.replace(/^duScroll/, 'scroll'); 202 if(angular.isUndefined(angular.element.prototype[unprefixed])) { 203 angular.element.prototype[unprefixed] = fn; 204 } 205 }); 206 207}]); 208 209 210//Adapted from https://gist.github.com/paulirish/1579671 211angular.module('duScroll.polyfill', []) 212.factory('polyfill', ["$window", function($window) { 213 'use strict'; 214 215 var vendors = ['webkit', 'moz', 'o', 'ms']; 216 217 return function(fnName, fallback) { 218 if($window[fnName]) { 219 return $window[fnName]; 220 } 221 var suffix = fnName.substr(0, 1).toUpperCase() + fnName.substr(1); 222 for(var key, i = 0; i < vendors.length; i++) { 223 key = vendors[i]+suffix; 224 if($window[key]) { 225 return $window[key]; 226 } 227 } 228 return fallback; 229 }; 230}]); 231 232angular.module('duScroll.requestAnimation', ['duScroll.polyfill']) 233.factory('requestAnimation', ["polyfill", "$timeout", function(polyfill, $timeout) { 234 'use strict'; 235 236 var lastTime = 0; 237 var fallback = function(callback, element) { 238 var currTime = new Date().getTime(); 239 var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 240 var id = $timeout(function() { callback(currTime + timeToCall); }, 241 timeToCall); 242 lastTime = currTime + timeToCall; 243 return id; 244 }; 245 246 return polyfill('requestAnimationFrame', fallback); 247}]) 248.factory('cancelAnimation', ["polyfill", "$timeout", function(polyfill, $timeout) { 249 'use strict'; 250 251 var fallback = function(promise) { 252 $timeout.cancel(promise); 253 }; 254 255 return polyfill('cancelAnimationFrame', fallback); 256}]); 257 258 259angular.module('duScroll.spyAPI', ['duScroll.scrollContainerAPI']) 260.factory('spyAPI', ["$rootScope", "$timeout", "$interval", "$window", "$document", "scrollC
260ontainerAPI", "duScrollGreedy", "duScrollSpyWait", "duScrollSpyRefreshInterval", "duScrollBottomSpy", "duScrollActiveClass", function($rootScope, $timeout, $interval, $window, $document, scrollContainerAPI, duScrollGreedy, duScrollSpyWait, duScrollSpyRefreshInterval, duScrollBottomSpy, duScrollActiveClass) { 261 'use strict'; 262 263 var createScrollHandler = function(context) { 264 var timer = false, queued = false; 265 var handler = function() { 266 queued = false; 267 var container = context.container, 268 containerEl = container[0], 269 containerOffset = 0, 270 bottomReached; 271 272 if (typeof HTMLElement !== 'undefined' && containerEl instanceof HTMLElement || containerEl.nodeType && containerEl.nodeType === containerEl.ELEMENT_NODE) { 273 containerOffset = containerEl.getBoundingClientRect().top; 274 bottomReached = Math.round(containerEl.scrollTop + containerEl.clientHeight) >= containerEl.scrollHeight; 275 } else { 276 var documentScrollHeight = $document[0].body.scrollHeight || $document[0].documentElement.scrollHeight; // documentElement for IE11 277 bottomReached = Math.round($window.pageYOffset + $window.innerHeight) >= documentScrollHeight; 278 } 279 var compareProperty = (duScrollBottomSpy && bottomReached ? 'bottom' : 'top'); 280 281 var i, currentlyActive, toBeActive, spies, spy, pos; 282 spies = context.spies; 283 currentlyActive = context.currentlyActive; 284 toBeActive = undefined; 285 286 for(i = 0; i < spies.length; i++) { 287 spy = spies[i]; 288 pos = spy.getTargetPosition(); 289 if (!pos || !spy.$element) continue; 290 291 if((duScrollBottomSpy && bottomReached) || (pos.top + spy.offset - containerOffset < 20 && (duScrollGreedy || pos.top*-1 + containerOffset) < pos.height)) { 292 //Find the one closest the viewport top or the page bottom if it's reached 293 if(!toBeActive || toBeActive[compareProperty] < pos[compareProperty]) { 294 toBeActive = { 295 spy: spy 296 }; 297 toBeActive[compareProperty] = pos[compareProperty]; 298 } 299 } 300 } 301 302 if(toBeActive) { 303 toBeActive = toBeActive.spy; 304 } 305 if(currentlyActive === toBeActive || (duScrollGreedy && !toBeActive)) return; 306 if(currentlyActive && currentlyActive.$element) { 307 currentlyActive.$element.removeClass(duScrollActiveClass); 308 $rootScope.$broadcast( 309 'duScrollspy:becameInactive', 310 currentlyActive.$element, 311 angular.element(currentlyActive.getTargetElement()) 312 ); 313 } 314 if(toBeActive) { 315 toBeActive.$element.addClass(duScrollActiveClass); 316 $rootScope.$broadcast( 317 'duScrollspy:becameActive', 318 toBeActive.$element, 319 angular.element(toBeActive.getTargetElement()) 320 ); 321 } 322 context.currentlyActive = toBeActive; 323 }; 324 325 if(!duScrollSpyWait) { 326 return handler; 327 } 328 329 //Debounce for potential performance savings 330 return function() { 331 if(!timer) { 332 handler(); 333 timer = $timeout(function() { 334 timer = false; 335 if(queued) { 336 handler(); 337 } 338 }, duScrollSpyWait, false); 339 } else { 340 queued = true; 341 } 342 }; 343 }; 344 345 var contexts = {}; 346 347 var createContext = function($scope) { 348 var id = $scope.$id; 349 var context = { 350 spies: [] 351 }; 352 353 context.handler = createScrollHandler(context); 354 contexts[id] = context; 355 356 $scope.$on('$destroy', function() { 357 destroyContext($scope); 358 }); 359 360 return id; 361 }; 362 363 var destroyContext = function($scope) { 364 var id = $scope.$id; 365 var context = contexts[id], container = context.container; 366 if(context.intervalPromise) { 367 $interval.cancel(context.intervalPromise); 368 } 369 if(container) { 370 container.off('scroll', context.handler); 371 } 372 delete contexts[id]; 373 }; 374 375 var defaultContextId = createContext($rootScope); 376 377 var getContextForScope = function(scope) { 378 if(contexts[scope.$id]) { 379 return contexts[scope.$id]; 380 } 381 if(scope.$parent) { 382 return getContextForScope(scope.$parent); 383 } 384 return contexts[defaultContextId]; 385 }; 386 387 var getContextForSpy = function(spy) { 388 var context, contextId, scope = spy.$scope; 389 if(scope) { 390 return getContextForScope(scope); 391 } 392 //No scope, most likely destroyed 393 for(contextId in contexts) { 394 context = contexts[contextId]; 395 if(context.spies.indexOf(spy) !== -1) { 396 return context; 397 } 398 } 399 }; 400 401 var isElementInDocument = function(element) { 402 while (element.parentNode) { 403 element = element.parentNode; 404 if (element === document) { 405 return true; 406 } 407 } 408 return false;
409 }; 410 411 var addSpy = function(spy) { 412 var context = getContextForSpy(spy); 413 if (!context) return; 414 context.spies.push(spy); 415 if (!context.container || !isElementInDocument(context.container)) { 416 if(context.container) { 417 context.container.off('scroll', context.handler); 418 } 419 context.container = scrollContainerAPI.getContainer(spy.$scope); 420 if (duScrollSpyRefreshInterval && !context.intervalPromise) { 421 context.intervalPromise = $interval(context.handler, duScrollSpyRefreshInterval, 0, false); 422 } 423 context.container.on('scroll', context.handler).triggerHandler('scroll'); 424 } 425 }; 426 427 var removeSpy = function(spy) { 428 var context = getContextForSpy(spy); 429 if(spy === context.currentlyActive) { 430 $rootScope.$broadcast('duScrollspy:becameInactive', context.currentlyActive.$element); 431 context.currentlyActive = null; 432 } 433 var i = context.spies.indexOf(spy); 434 if(i !== -1) { 435 context.spies.splice(i, 1); 436 } 437 spy.$element = null; 438 }; 439 440 return { 441 addSpy: addSpy, 442 removeSpy: removeSpy, 443 createContext: createContext, 444 destroyContext: destroyContext, 445 getContextForScope: getContextForScope 446 }; 447}]); 448 449 450angular.module('duScroll.scrollContainerAPI', []) 451.factory('scrollContainerAPI', ["$document", function($document) { 452 'use strict'; 453 454 var containers = {}; 455 456 var setContainer = function(scope, element) { 457 var id = scope.$id; 458 containers[id] = element; 459 return id; 460 }; 461 462 var getContainerId = function(scope) { 463 if(containers[scope.$id]) { 464 return scope.$id; 465 } 466 if(scope.$parent) { 467 return getContainerId(scope.$parent); 468 } 469 return; 470 }; 471 472 var getContainer = function(scope) { 473 var id = getContainerId(scope); 474 return id ? containers[id] : $document; 475 }; 476 477 var removeContainer = function(scope) { 478 var id = getContainerId(scope); 479 if(id) { 480 delete containers[id]; 481 } 482 }; 483 484 return { 485 getContainerId: getContainerId, 486 getContainer: getContainer, 487 setContainer: setContainer, 488 removeContainer: removeContainer 489 }; 490}]); 491 492 493angular.module('duScroll.smoothScroll', ['duScroll.scrollHelpers', 'duScroll.scrollContainerAPI']) 494.directive('duSmoothScroll', ["duScrollDuration", "duScrollOffset", "scrollContainerAPI", function(duScrollDuration, duScrollOffset, scrollContainerAPI) { 495 'use strict'; 496 497 return { 498 link : function($scope, $element, $attr) { 499 $element.on('click', function(e) { 500 if((!$attr.href || $attr.href.indexOf('#') === -1) && $attr.duSmoothScroll === '') return; 501 502 var id = $attr.href ? $attr.href.replace(/.*(?=#[^\s]+$)/, '').substring(1) : $attr.duSmoothScroll; 503 504 var target = document.getElementById(id) || document.getElementsByName(id)[0]; 505 if(!target || !target.getBoundingClientRect) return; 506 507 if (e.stopPropagation) e.stopPropagation(); 508 if (e.preventDefault) e.preventDefault(); 509 510 var offset = $attr.offset ? parseInt($attr.offset, 10) : duScrollOffset; 511 var duration = $attr.duration ? parseInt($attr.duration, 10) : duScrollDuration; 512 var container = scrollContainerAPI.getContainer($scope); 513 514 container.duScrollToElement( 515 angular.element(target), 516 isNaN(offset) ? 0 : offset, 517 isNaN(duration) ? 0 : duration 518 ); 519 }); 520 } 521 }; 522}]); 523 524 525angular.module('duScroll.spyContext', ['duScroll.spyAPI']) 526.directive('duSpyContext', ["spyAPI", function(spyAPI) { 527 'use strict'; 528 529 return { 530 restrict: 'A', 531 scope: true, 532 compile: function compile(tElement, tAttrs, transclude) { 533 return { 534 pre: function preLink($scope, iElement, iAttrs, controller) { 535 spyAPI.createContext($scope); 536 } 537 }; 538 } 539 }; 540}]); 541 542 543angular.module('duScroll.scrollContainer', ['duScroll.scrollContainerAPI']) 544.directive('duScrollContainer', ["scrollContainerAPI", function(scrollContainerAPI){ 545 'use strict'; 546 547 return { 548 restrict: 'A', 549 scope: true, 550 compile: function compile(tElement, tAttrs, transclude) { 551 return { 552 pre: function preLink($scope, iElement, iAttrs, controller) {
553 iAttrs.$observe('duScrollContainer', function(element) { 554 if(angular.isString(element)) { 555 element = document.getElementById(element); 556 } 557 558 element = (angular.isElement(element) ? angular.element(element) : iElement); 559 scrollContainerAPI.setContainer($scope, element); 560 $scope.$on('$destroy', function() { 561 scrollContainerAPI.removeContainer($scope); 562 }); 563 }); 564 } 565 }; 566 } 567 }; 568}]); 569 570 571angular.module('duScroll.scrollspy', ['duScroll.spyAPI']) 572.directive('duScrollspy', ["spyAPI", "duScrollOffset", "$timeout", "$rootScope", function(spyAPI, duScrollOffset, $timeout, $rootScope) { 573 'use strict'; 574 575 var Spy = function(targetElementOrId, $scope, $element, offset) { 576 if(angular.isElement(targetElementOrId)) { 577 this.target = targetElementOrId; 578 } else if(angular.isString(targetElementOrId)) { 579 this.targetId = targetElementOrId; 580 } 581 this.$scope = $scope; 582 this.$element = $element; 583 this.offset = offset; 584 }; 585 586 Spy.prototype.getTargetElement = function() { 587 if (!this.target && this.targetId) { 588 this.target = document.getElementById(this.targetId) || document.getElementsByName(this.targetId)[0]; 589 } 590 return this.target; 591 }; 592 593 Spy.prototype.getTargetPosition = function() { 594 var target = this.getTargetElement(); 595 if(target) { 596 return target.getBoundingClientRect(); 597 } 598 }; 599 600 Spy.prototype.flushTargetCache = function() { 601 if(this.targetId) { 602 this.target = undefined; 603 } 604 }; 605 606 return { 607 link: function ($scope, $element, $attr) { 608 var href = $attr.ngHref || $attr.href; 609 var targetId; 610 611 if (href && href.indexOf('#') !== -1) { 612 targetId = href.replace(/.*(?=#[^\s]+$)/, '').substring(1); 613 } else if($attr.duScrollspy) { 614 targetId = $attr.duScrollspy; 615 } else if($attr.duSmoothScroll) { 616 targetId = $attr.duSmoothScroll; 617 } 618 if(!targetId) return; 619 620 // Run this in the next execution loop so that the scroll context has a chance 621 // to initialize 622 var timeoutPromise = $timeout(function() { 623 var spy = new Spy(targetId, $scope, $element, -($attr.offset ? parseInt($attr.offset, 10) : duScrollOffset)); 624 spyAPI.addSpy(spy); 625 626 $scope.$on('$locationChangeSuccess', spy.flushTargetCache.bind(spy)); 627 var deregisterOnStateChange = $rootScope.$on('$stateChangeSuccess', spy.flushTargetCache.bind(spy)); 628 $scope.$on('$destroy', function() { 629 spyAPI.removeSpy(spy); 630 deregisterOnStateChange(); 631 }); 632 }, 0, false); 633 $scope.$on('$destroy', function() {$timeout.cancel(timeoutPromise);}); 634 } 635 }; 636}]);
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.