diff --git a/README.md b/README.md index 3350b24..005a71d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,182 @@ -angular-smooth-scroll -===================== +Angular smooth scroll +============== -Set of directives and service for animating scrolling to elements. +A pure-javascript library and set of directives to scroll smoothly to an element with easing. Easing support contributed by Willem Liu with code from Gaëtan Renaudeau. + +No jQuery required. + +# Features + + * Exposes a service that scrolls the window to an element's location + * Provides two directives that enable smooth scrolling to elements. + * Clean: No classes are added, no jQuery is required, no CSS files or configuration is needed. + + +# Installation + +Include the .js file in your page then enable usage of the directive by including the "smoothScroll" module +as a dependency + + +# Bower + +Install with bower with: + +```bash +bower install ngSmoothScroll +``` + +# Usage - As a directive + +This module provides two directives: + +####smoothScroll: + +Attribute. Scrolls the window to this element, optionally validating the expression inside scroll-if. + +Example: +```html + +// Basic - The window will scroll to this element's position when compiling this directive +
+ + +// With options +
{{...}}
+ + +// With condition +
{{...}}
+ + +// Inside ng-repeat +
{{...}}
+``` + + +####scrollTo: + +Attribute. Scrolls the window to the specified element ID when clicking this element. + +Example: +```html + +// Basic +Click me! + + +// With options + +``` + + +# Usage - As a service + +Inject the 'smoothScroll' service in your directive / factory / controller / whatever, and call like this: + +```js + +// Using defaults +var element = document.getElementById('my-elem'); +smoothScroll(element); + + +// With options +var element = $elem[0]; + +var options = { + duration: 700, + easing: 'easeInQuad', + offset: 120, + callbackBefore: function(element) { + console.log('about to scroll to element', element); + }, + callbackAfter: function(element) { + console.log('scrolled to element', element); + } +} + +smoothScroll(element, options); + + +// In directive's link function +link: function($scope, $elem, $attrs){ + var options = $attrs; + + smoothScroll($elem[0], options); +} + + +``` + +### Options + +#### duration +Type: `Integer` +Default: `0` + +The duration of the smooth scroll, in miliseconds. + +#### offset +Type: `Integer` +Default: `0` + +The offset from the top of the page in which the scroll should stop. + +#### easing +type: `string` +default: `easeinoutquart` + +the easing function to be used for this scroll. + +#### callbackBefore +type: `function` +default: `function(element) {}` + +a callback function to run before the scroll has started. It is passed the +element that will be scrolled to. + +#### callbackAfter +type: `function` +default: `function(element) {}` + +a callback function to run after the scroll has completed. It is passed the +element that was scrolled to. + +### Easing functions + +The available easing functions are: + * 'easeInQuad' + * 'easeOutQuad' + * 'easeInOutQuad' + * 'easeInCubic' + * 'easeOutCubic' + * 'easeInOutCubic' + * 'easeInQuart' + * 'easeOutQuart' + * 'easeInOutQuart' + * 'easeInQuint' + * 'easeOutQuint' + * 'easeInOutQuint' + +#### Credits + +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 + +Influenced by Chris Ferdinandi +https://github.com/cferdinandi + +Free to use under the MIT License. + + +Cheers. diff --git a/angular-smooth-scroll.js b/angular-smooth-scroll.js new file mode 100644 index 0000000..125da0a --- /dev/null +++ b/angular-smooth-scroll.js @@ -0,0 +1,215 @@ +/* ============================================================= +/* +/* Angular Smooth Scroll 1.7.0 +/* 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 +/* +/* Influenced by Chris Ferdinandi +/* https://github.com/cferdinandi +/* +/* +/* Free to use under the MIT License. +/* +/* ============================================================= */ + +angular.module('smoothScroll', []) + + +// Scrolls the window to this element, optionally validating an expression +// +.directive('smoothScroll', ['$timeout', 'smoothScroll', function($timeout, smoothScroll){ + return { + restrict: 'A', + scope: { + callbackBefore: '&', + callbackAfter: '&', + }, + link: function($scope, $elem, $attrs){ + $timeout(function(){ + if ( typeof $attrs.scrollIf === 'undefined' || $attrs.scrollIf === 'true' ){ + 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 + }); + } + + }); + } + }; +}]) + + +// Scrolls to a specified element ID when this element is clicked +// +.directive('scrollTo', ['smoothScroll', function(smoothScroll){ + return { + restrict: 'A', + scope: { + callbackBefore: '&', + callbackAfter: '&', + }, + link: function($scope, $elem, $attrs){ + var targetElement; + + $elem.on('click', function(e){ + targetElement = document.getElementById($attrs.scrollTo); + + if ( targetElement ) { + e.preventDefault(); + + 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 + }); + + return false; + } + }); + } + }; +}]) + + +// Smooth scrolls the window to the provided element. +// +.factory('smoothScroll', ['$timeout', function($timeout){ + + var getScrollLocation = function() { + return window.pageYOffset ? window.pageYOffset : document.documentElement.scrollTop; + }; + + var smoothScroll = function (element, options) { + $timeout(function(){ + var startLocation = getScrollLocation(), + timeLapsed = 0, + percentage, position; + + + // Options + // + options = options || {}; + var duration = options.duration || 800, + offset = options.offset || 0, + easing = options.easing || 'easeInOutQuart', + callbackBefore = options.callbackBefore || function() {}, + callbackAfter = options.callbackAfter || function() {}; + + + // Calculate the easing pattern + // + var easingPattern = function (type, time) { + if ( type == 'easeInQuad' ) return time * time; // accelerating from zero velocity + if ( type == 'easeOutQuad' ) return time * (2 - time); // decelerating to zero velocity + if ( type == 'easeInOutQuad' ) return time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration + if ( type == 'easeInCubic' ) return time * time * time; // accelerating from zero velocity + if ( type == 'easeOutCubic' ) return (--time) * time * time + 1; // decelerating to zero velocity + if ( type == 'easeInOutCubic' ) return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration + if ( type == 'easeInQuart' ) return time * time * time * time; // accelerating from zero velocity + if ( type == 'easeOutQuart' ) return 1 - (--time) * time * time * time; // decelerating to zero velocity + if ( type == 'easeInOutQuart' ) return time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration + if ( type == 'easeInQuint' ) return time * time * time * time * time; // accelerating from zero velocity + if ( type == 'easeOutQuint' ) return 1 + (--time) * time * time * time * time; // decelerating to zero velocity + if ( type == 'easeInOutQuint' ) return time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration + return time; // no easing, no acceleration + }; + + + // 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; + }; + var endLocation = getEndLocation(element); + var distance = endLocation - startLocation; + + + // Stop the scrolling animation when the anchor is reached (or at the top/bottom of the page) + // + var stopAnimation = function () { + var currentLocation = getScrollLocation(); + if ( position == endLocation || currentLocation == endLocation || ( (window.innerHeight + currentLocation) >= document.body.scrollHeight ) ) { + 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 = ( percentage > 1 ) ? 1 : percentage; + position = startLocation + ( distance * easingPattern(easing, percentage) ); + window.scrollTo( 0, position ); + stopAnimation(); + }; + + + // Init + // + callbackBefore(element); + var runAnimation = setInterval(animateScroll, 16); + }); + }; + + return smoothScroll; +}]); diff --git a/angular-smooth-scroll.min.js b/angular-smooth-scroll.min.js new file mode 100644 index 0000000..13f2ee5 --- /dev/null +++ b/angular-smooth-scroll.min.js @@ -0,0 +1 @@ +angular.module("smoothScroll",[]).directive("smoothScroll",["$timeout","smoothScroll",function(e,t){return{restrict:"A",scope:{callbackBefore:"&",callbackAfter:"&"},link:function(n,o,a){e(function(){if(void 0===a.scrollIf||"true"===a.scrollIf){var e=function(e){if(a.callbackBefore){var t=n.callbackBefore({element:e});"function"==typeof t&&t(e)}},c=function(e){if(a.callbackAfter){var t=n.callbackAfter({element:e});"function"==typeof t&&t(e)}};t(o[0],{duration:a.duration,offset:a.offset,easing:a.easing,callbackBefore:e,callbackAfter:c})}})}}}]).directive("scrollTo",["smoothScroll",function(e){return{restrict:"A",scope:{callbackBefore:"&",callbackAfter:"&"},link:function(t,n,o){var a;n.on("click",function(n){if(a=document.getElementById(o.scrollTo)){n.preventDefault();var c=function(e){if(o.callbackBefore){var n=t.callbackBefore({element:e});"function"==typeof n&&n(e)}},r=function(e){if(o.callbackAfter){var n=t.callbackAfter({element:e});"function"==typeof n&&n(e)}};return e(a,{duration:o.duration,offset:o.offset,easing:o.easing,callbackBefore:c,callbackAfter:r}),!1}})}}}]).factory("smoothScroll",["$timeout",function(e){var t=function(){return window.pageYOffset?window.pageYOffset:document.documentElement.scrollTop},n=function(n,o){e(function(){var e,a,c=t(),r=0;o=o||{};var f=o.duration||800,l=o.offset||0,u=o.easing||"easeInOutQuart",i=o.callbackBefore||function(){},s=o.callbackAfter||function(){},d=function(e,t){return"easeInQuad"==e?t*t:"easeOutQuad"==e?t*(2-t):"easeInOutQuad"==e?.5>t?2*t*t:-1+(4-2*t)*t:"easeInCubic"==e?t*t*t:"easeOutCubic"==e?--t*t*t+1:"easeInOutCubic"==e?.5>t?4*t*t*t:(t-1)*(2*t-2)*(2*t-2)+1:"easeInQuart"==e?t*t*t*t:"easeOutQuart"==e?1- --t*t*t*t:"easeInOutQuart"==e?.5>t?8*t*t*t*t:1-8*--t*t*t*t:"easeInQuint"==e?t*t*t*t*t:"easeOutQuint"==e?1+--t*t*t*t*t:"easeInOutQuint"==e?.5>t?16*t*t*t*t*t:1+16*--t*t*t*t*t:t},b=function(e){var t=0;if(e.offsetParent)do t+=e.offsetTop,e=e.offsetParent;while(e);return t=Math.max(t-l,0)},k=b(n),m=k-c,v=function(){var e=t();a!=k&&e!=k&&window.innerHeight+e1?1:e,a=c+m*d(u,e),window.scrollTo(0,a),v()};i(n);var g=setInterval(I,16)})};return n}]); \ No newline at end of file diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..ac6daf6 --- /dev/null +++ b/bower.json @@ -0,0 +1,26 @@ +{ + "name": "angular-smooth-scroll", + "version": "1.7.0", + "homepage": "https://github.com/d-oliveros/angular-smooth-scroll", + "authors": [ + "David Oliveros " + ], + "description": "A pure-javascript library and set of directives to scroll smoothly to an element with easing.", + "main": "angular-smooth-scroll.js", + "dependencies": { + "angular": "^1.2.0" + }, + "keywords": [ + "angularjs", + "smooth", + "scroll" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +}