<!DOCTYPE html>
<html>

<head>
  <link type="text/css" rel="stylesheet" href="style.css">
  
  <script src="http://code.angularjs.org/1.5.0/angular.js"></script>
  <script src="ngSmoothScroll.js"></script>
  <script src="script.js"></script>
</head>

<body ng-app="app" ng-controller="AppCtrl">
  <ul id="scrollable-container" class="scrollable-container">
    <li id="li-idx-{{ value+1 }}" ng-repeat="value in values">{{ value }}</li>
  </ul>
  
  <form ng-submit="scrollTo(scrollIdx)">
    <input type="number" ng-model="scrollIdx">
    <button type="submit">Scroll</button>  
  </form>
  
</body>

</html>
angular.module('app', ['smoothScroll'])
  .controller('AppCtrl', function($scope, smoothScroll) {
    $scope.values = [];
    
    for (var i = 0; i < 100; i++) {
      $scope.values.push(i);
    }
    
    $scope.scrollTo = function(scrollIdx) {
      var element = document.getElementById('li-idx-' + scrollIdx);
      if (element) {
        smoothScroll(element, {
          containerId: 'scrollable-container'
        });
      }
    };
    
  });
.scrollable-container {
  height: 300px;
  overflow-y: scroll;
  background-color: #f1f1f1;
}
/*!
 *	 Angular Smooth Scroll (ngSmoothScroll)
 *	 Animates scrolling to elements, by David Oliveros.
 *
 *   Callback hooks contributed by Ben Armston https://github.com/benarmston
 *	 Easing support contributed by Willem Liu. https://github.com/willemliu
 *	 Easing functions forked from Gaƫtan Renaudeau. https://gist.github.com/gre/1650294
 *	 Infinite loop bugs in iOS and Chrome (when zoomed) by Alex Guzman. https://github.com/alexguzman
 *	 Support for scrolling in custom containers by Joseph Matthias Goh. https://github.com/zephinzer
 *	 Influenced by Chris Ferdinandi
 *	 https://github.com/cferdinandi
 *
 *	 Version: 2.0.0
 * 	 License: MIT
 */

(function () {
	'use strict';

	var module = angular.module('smoothScroll', []);


	/**
	 * Smooth scrolls the window/div to the provided element.
	 *
	 * 20150713 EDIT - zephinzer
	 * 	Added new option - containerId to account for scrolling within a DIV
	 */
	var smoothScroll = function (element, options) {
		options = options || {};

		// Options
		var duration = options.duration || 800,
			offset = options.offset || 0,
			easing = options.easing || 'easeInOutQuart',
			callbackBefore = options.callbackBefore || function() {},
			callbackAfter = options.callbackAfter || function() {},
			container = document.getElementById(options.containerId) || null,
			containerPresent = (container != undefined && container != null);

		/**
		 * Retrieve current location
		 */
		var getScrollLocation = function() {
			if(containerPresent) {
				return container.scrollTop;
			} else {
				if(window.pageYOffset) {
					return window.pageYOffset;
				} else {
					return document.documentElement.scrollTop;
				}
			}
		};

		/**
		 * Calculate easing pattern.
		 *
		 * 20150713 edit - zephinzer
		 * - changed if-else to switch
		 * @see http://archive.oreilly.com/pub/a/server-administration/excerpts/even-faster-websites/writing-efficient-javascript.html
		 */
		var getEasingPattern = function(type, time) {
			switch(type) {
				case 'easeInQuad': 		return time * time; // accelerating from zero velocity
				case 'easeOutQuad': 	return time * (2 - time); // decelerating to zero velocity
				case 'easeInOutQuad': 	return time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration
				case 'easeInCubic': 	return time * time * time; // accelerating from zero velocity
				case 'easeOutCubic': 	return (--time) * time * time + 1; // decelerating to zero velocity
				case 'easeInOutCubic': 	return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration
				case 'easeInQuart': 	return time * time * time * time; // accelerating from zero velocity
				case 'easeOutQuart': 	return 1 - (--time) * time * time * time; // decelerating to zero velocity
				case 'easeInOutQuart': 	return time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration
				case 'easeInQuint': 	return time * time * time * time * time; // accelerating from zero velocity
				case 'easeOutQuint': 	return 1 + (--time) * time * time * time * time; // decelerating to zero velocity
				case 'easeInOutQuint': 	return time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration
				default:				return time;
			}
		};

		/**
		 * Calculate how far to scroll
		 */
		var getEndLocation = function(element) {
			var location = 0;
			if (element.offsetParent) {
				do {
					location += element.offsetTop;
					element = element.offsetParent;
				} while (element);
			}
			location = Math.max(location - offset, 0);
			return location;
		};

		// Initialize the whole thing
		setTimeout( function() {
			var currentLocation = null,
				startLocation 	= getScrollLocation(),
				endLocation 	= getEndLocation(element),
				timeLapsed 		= 0,
				distance 		= Math.abs(endLocation - startLocation),
				percentage,
				position,
				positionSign = Math.sign(endLocation - startLocation),
				scrollHeight,
				internalHeight;

			/**
			 * Stop the scrolling animation when the anchor is reached (or at the top/bottom of the page)
			 */
			var stopAnimation = function () {
				currentLocation = getScrollLocation();
				if (containerPresent) {
					scrollHeight = container.scrollHeight;
					internalHeight = container.clientHeight + currentLocation * positionSign;
				} else {
					scrollHeight = document.body.scrollheight;
					internalHeight = window.innerHeight + currentLocation * positionSign;
				}

				if (
					( // condition 1
						position == endLocation
					) ||
					( // condition 2
						currentLocation == endLocation
					) ||
					( // condition 3
						internalHeight >= scrollHeight
					)
				) { // stop
					clearInterval(runAnimation);
					callbackAfter(element);
				}
			};

			/**
			 * Scroll the page by an increment, and check if it's time to stop
			 */
			var animateScroll = function () {
				timeLapsed += 16;
				percentage = ( timeLapsed / duration );
				percentage = Math.min(percentage, 1);
				position = startLocation + (distance * getEasingPattern(easing, percentage)) * positionSign;
				if (containerPresent) {
					container.scrollTop = position;
				} else {
					window.scrollTo( 0, position );
				}
				stopAnimation();
			};

			callbackBefore(element);
			var runAnimation = setInterval(animateScroll, 16);
		}, 0);
	};


	// Expose the library in a factory
	//
	module.factory('smoothScroll', function() {
		return smoothScroll;
	});


	/**
	 * Scrolls the window to this element, optionally validating an expression
	 *
	 * 20150713 EDIT - zephinzer
	 * 	Added containerId to attributes for smooth scrolling within a DIV
	 */
	module.directive('smoothScroll', ['smoothScroll', function(smoothScroll) {
		return {
			restrict: 'A',
			scope: {
				callbackBefore: '&',
				callbackAfter: '&',
			},
			link: function($scope, $elem, $attrs) {
				if ( typeof $attrs.scrollIf === 'undefined' || $attrs.scrollIf === 'true' ) {
					setTimeout( function() {

						var callbackBefore = function(element) {
							if ( $attrs.callbackBefore ) {
								var exprHandler = $scope.callbackBefore({ element: element });
								if (typeof exprHandler === 'function') {
									exprHandler(element);
								}
							}
						};

						var callbackAfter = function(element) {
							if ( $attrs.callbackAfter ) {
								var exprHandler = $scope.callbackAfter({ element: element });
								if (typeof exprHandler === 'function') {
									exprHandler(element);
								}
							}
						};

						smoothScroll($elem[0], {
							duration: $attrs.duration,
							offset: $attrs.offset,
							easing: $attrs.easing,
							callbackBefore: callbackBefore,
							callbackAfter: callbackAfter,
							containerId: $attrs.containerId
						});
					}, 0);
				}
			}
		};
	}]);


	/**
	 * Scrolls to a specified element ID when this element is clicked
	 *
	 * 20150713 EDIT - zephinzer
	 * 	Added containerId to attributes for smooth scrolling within a DIV
	 */
	module.directive('scrollTo', ['smoothScroll', function(smoothScroll) {
		return {
			restrict: 'A',
			scope: {
				callbackBefore: '&',
				callbackAfter: '&',
			},
			link: function($scope, $elem, $attrs) {
				var targetElement;

				$elem.on('click', function(e) {
					e.preventDefault();

					targetElement = document.getElementById($attrs.scrollTo);
					if ( !targetElement ) return;

					var callbackBefore = function(element) {
						if ( $attrs.callbackBefore ) {
							var exprHandler = $scope.callbackBefore({element: element});
							if (typeof exprHandler === 'function') {
								exprHandler(element);
							}
						}
					};

					var callbackAfter = function(element) {
						if ( $attrs.callbackAfter ) {
							var exprHandler = $scope.callbackAfter({element: element});
							if (typeof exprHandler === 'function') {
								exprHandler(element);
							}
						}
					};

					smoothScroll(targetElement, {
						duration: $attrs.duration,
						offset: $attrs.offset,
						easing: $attrs.easing,
						callbackBefore: callbackBefore,
						callbackAfter: callbackAfter,
						containerId: $attrs.containerId
					});

					return false;
				});
			}
		};
	}]);

}());