├── README.md ├── bower.json ├── dist └── angular-smooth-scroll.min.js └── lib └── angular-smooth-scroll.js /README.md: -------------------------------------------------------------------------------- 1 | Angular smooth scroll 2 | ============== 3 | 4 | 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. 5 | 6 | No jQuery required. 7 | 8 | # Features 9 | 10 | * Exposes a service that scrolls the window to an element's location 11 | * Provides two directives that enable smooth scrolling to elements. 12 | * Clean: No classes are added, no jQuery is required, no CSS files or configuration is needed. 13 | * Scrolling within a custom container added in 2.0.0 14 | 15 | # Installation 16 | 17 | Include the .js file in your page then enable usage of the directive by including the "smoothScroll" module 18 | as a dependency 19 | 20 | 21 | # Bower 22 | 23 | Install with bower with: 24 | 25 | ```bash 26 | bower install ngSmoothScroll 27 | ``` 28 | 29 | # Usage - As a directive 30 | 31 | This module provides two directives: 32 | 33 | ####smoothScroll: 34 | 35 | Attribute. Scrolls the window to this element, optionally validating the expression inside scroll-if. 36 | 37 | Example: 38 | ```html 39 | 40 | // Basic - The window will scroll to this element's position when compiling this directive 41 |
42 | 43 | // With options 44 |
50 | {{...}} 51 |
52 | 53 | // Inside a custom container 54 |
61 | {{...}} 62 |
63 | 64 | // With condition 65 |
67 | {{...}} 68 |
69 | 70 | // Inside ng-repeat 71 |
74 | {{...}} 75 |
76 | ``` 77 | 78 | ####scrollTo: 79 | 80 | Attribute. Scrolls the window to the specified element ID when clicking this element. 81 | 82 | Example: 83 | ```html 84 | 85 | // Basic 86 | 88 | Click me! 89 | 90 | 91 | // Custom containers 92 | 95 | Click me! 96 | 97 | 98 | // onClick for non-anchor tags 99 |
101 | Click me! 102 |
103 | 104 | // With options 105 | 112 | ``` 113 | 114 | 115 | # Usage - As a service 116 | 117 | Inject the 'smoothScroll' service in your directive / factory / controller / whatever, and call like this: 118 | 119 | ```js 120 | 121 | // Using defaults 122 | var element = document.getElementById('my-elem'); 123 | smoothScroll(element); 124 | 125 | // With options 126 | var element = $elem[0]; 127 | 128 | var options = { 129 | duration: 700, 130 | easing: 'easeInQuad', 131 | offset: 120, 132 | callbackBefore: function(element) { 133 | console.log('about to scroll to element', element); 134 | }, 135 | callbackAfter: function(element) { 136 | console.log('scrolled to element', element); 137 | } 138 | } 139 | 140 | smoothScroll(element, options); 141 | 142 | // With options for custom container 143 | var element = $elem[0]; 144 | 145 | var options = { 146 | duration: 700, 147 | easing: 'easeInQuad', 148 | offset: 120, 149 | callbackBefore: function(element) { 150 | console.log('about to scroll to element', element); 151 | }, 152 | callbackAfter: function(element) { 153 | console.log('scrolled to element', element); 154 | }, 155 | containerId: 'custom-container-id' 156 | } 157 | 158 | smoothScroll(element, options); 159 | 160 | // In directive's link function 161 | link: function($scope, $elem, $attrs){ 162 | var options = $attrs; 163 | 164 | smoothScroll($elem[0], options); 165 | } 166 | 167 | 168 | ``` 169 | 170 | ### Options 171 | 172 | #### duration 173 | Type: `Integer` 174 | Default: `800` 175 | 176 | The duration of the smooth scroll, in miliseconds. 177 | 178 | #### offset 179 | Type: `Integer` 180 | Default: `0` 181 | 182 | The offset from the top of the page in which the scroll should stop. 183 | 184 | #### easing 185 | type: `string` 186 | default: `easeInOutQuart` 187 | 188 | the easing function to be used for this scroll. 189 | 190 | #### callbackBefore 191 | type: `function` 192 | default: `function(element) {}` 193 | 194 | a callback function to run before the scroll has started. It is passed the 195 | element that will be scrolled to. 196 | 197 | #### callbackAfter 198 | type: `function` 199 | default: `function(element) {}` 200 | 201 | a callback function to run after the scroll has completed. It is passed the 202 | element that was scrolled to. 203 | 204 | #### containerId 205 | type: `string` 206 | default: null 207 | 208 | ID of the scrollable container which the element is a child of. 209 | 210 | ### Easing functions 211 | 212 | The available easing functions are: 213 | * 'easeInQuad' 214 | * 'easeOutQuad' 215 | * 'easeInOutQuad' 216 | * 'easeInCubic' 217 | * 'easeOutCubic' 218 | * 'easeInOutCubic' 219 | * 'easeInQuart' 220 | * 'easeOutQuart' 221 | * 'easeInOutQuart' 222 | * 'easeInQuint' 223 | * 'easeOutQuint' 224 | * 'easeInOutQuint' 225 | 226 | #### Credits 227 | 228 | Callback hooks contributed by Ben Armston. 229 | https://github.com/benarmston 230 | 231 | Easing support contributed by Willem Liu. 232 | https://github.com/willemliu 233 | 234 | Easing functions forked from Gaëtan Renaudeau. 235 | https://gist.github.com/gre/1650294 236 | 237 | Infinite loop bugs in iOS and Chrome (when zoomed) by Alex Guzman. 238 | https://github.com/alexguzman 239 | 240 | Support for scrolling in custom containers by Joseph Matthias Goh. 241 | https://github.com/zephinzer 242 | 243 | Influenced by Chris Ferdinandi 244 | https://github.com/cferdinandi 245 | 246 | Free to use under the MIT License. 247 | 248 | Cheers. 249 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngSmoothScroll", 3 | "version": "2.0.0", 4 | "homepage": "https://github.com/d-oliveros/ngSmoothScroll", 5 | "authors": [ 6 | "David Oliveros " 7 | ], 8 | "description": "A pure-javascript library and set of directives to scroll smoothly to an element with easing.", 9 | "main": "lib/angular-smooth-scroll.js", 10 | "dependencies": { 11 | "angular": "^1.2.0" 12 | }, 13 | "keywords": [ 14 | "angularjs", 15 | "smooth scroll", 16 | "scrolling", 17 | "scroll effects", 18 | "scroll animations", 19 | "smooth", 20 | "scroll" 21 | ], 22 | "license": "MIT", 23 | "ignore": [ 24 | "**/.*", 25 | "node_modules", 26 | "bower_components", 27 | "test", 28 | "tests" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /dist/angular-smooth-scroll.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Angular Smooth Scroll (ngSmoothScroll) 3 | * Animates scrolling to elements, by David Oliveros. 4 | * 5 | * Callback hooks contributed by Ben Armston https://github.com/benarmston 6 | * Easing support contributed by Willem Liu. https://github.com/willemliu 7 | * Easing functions forked from Gaëtan Renaudeau. https://gist.github.com/gre/1650294 8 | * Infinite loop bugs in iOS and Chrome (when zoomed) by Alex Guzman. https://github.com/alexguzman 9 | * Support for scrolling in custom containers by Joseph Matthias Goh. https://github.com/zephinzer 10 | * Influenced by Chris Ferdinandi 11 | * https://github.com/cferdinandi 12 | * 13 | * Version: 2.0.0 14 | * License: MIT 15 | */ 16 | !function(){"use strict";var e=angular.module("smoothScroll",[]),t=function(e,t){t=t||{};var n=t.duration||800,c=t.offset||0,r=t.easing||"easeInOutQuart",a=t.callbackBefore||function(){},o=t.callbackAfter||function(){},l=document.getElementById(t.containerId)||null,u=void 0!=l&&null!=l,f=function(){return u?l.scrollTop:window.pageYOffset?window.pageYOffset:document.documentElement.scrollTop},i=function(e,t){switch(e){case"easeInQuad":return t*t;case"easeOutQuad":return t*(2-t);case"easeInOutQuad":return.5>t?2*t*t:-1+(4-2*t)*t;case"easeInCubic":return t*t*t;case"easeOutCubic":return--t*t*t+1;case"easeInOutCubic":return.5>t?4*t*t*t:(t-1)*(2*t-2)*(2*t-2)+1;case"easeInQuart":return t*t*t*t;case"easeOutQuart":return 1- --t*t*t*t;case"easeInOutQuart":return.5>t?8*t*t*t*t:1-8*--t*t*t*t;case"easeInQuint":return t*t*t*t*t;case"easeOutQuint":return 1+--t*t*t*t*t;case"easeInOutQuint":return.5>t?16*t*t*t*t*t:1+16*--t*t*t*t*t;default:return t}},s=function(e){var t=0;if(e.offsetParent)do t+=e.offsetTop,e=e.offsetParent;while(e);return t=Math.max(t-c,0)};setTimeout(function(){var t,c,d,b,k=null,m=f(),I=s(e),v=0,g=I-m,h=function(){k=f(),u?(d=l.scrollHeight,b=l.clientHeight+k):(d=document.body.scrollheight,b=window.innerHeight+k),c!=I&&k!=I&&d>b||(clearInterval(A),o(e))},p=function(){v+=16,t=v/n,t=t>1?1:t,c=m+g*i(r,t),u?l.scrollTop=c:window.scrollTo(0,c),h()};a(e);var A=setInterval(p,16)},0)};e.factory("smoothScroll",function(){return t}),e.directive("smoothScroll",["smoothScroll",function(e){return{restrict:"A",scope:{callbackBefore:"&",callbackAfter:"&"},link:function(t,n,c){(void 0===c.scrollIf||"true"===c.scrollIf)&&setTimeout(function(){var r=function(e){if(c.callbackBefore){var n=t.callbackBefore({element:e});"function"==typeof n&&n(e)}},a=function(e){if(c.callbackAfter){var n=t.callbackAfter({element:e});"function"==typeof n&&n(e)}};e(n[0],{duration:c.duration,offset:c.offset,easing:c.easing,callbackBefore:r,callbackAfter:a,containerId:c.containerId})},0)}}}]),e.directive("scrollTo",["smoothScroll",function(e){return{restrict:"A",scope:{callbackBefore:"&",callbackAfter:"&"},link:function(t,n,c){var r;n.on("click",function(n){if(n.preventDefault(),r=document.getElementById(c.scrollTo)){var a=function(e){if(c.callbackBefore){var n=t.callbackBefore({element:e});"function"==typeof n&&n(e)}},o=function(e){if(c.callbackAfter){var n=t.callbackAfter({element:e});"function"==typeof n&&n(e)}};return e(r,{duration:c.duration,offset:c.offset,easing:c.easing,callbackBefore:a,callbackAfter:o,containerId:c.containerId}),!1}})}}}])}(); -------------------------------------------------------------------------------- /lib/angular-smooth-scroll.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Angular Smooth Scroll (ngSmoothScroll) 3 | * Animates scrolling to elements, by David Oliveros. 4 | * 5 | * Callback hooks contributed by Ben Armston https://github.com/benarmston 6 | * Easing support contributed by Willem Liu. https://github.com/willemliu 7 | * Easing functions forked from Gaëtan Renaudeau. https://gist.github.com/gre/1650294 8 | * Infinite loop bugs in iOS and Chrome (when zoomed) by Alex Guzman. https://github.com/alexguzman 9 | * Support for scrolling in custom containers by Joseph Matthias Goh. https://github.com/zephinzer 10 | * Influenced by Chris Ferdinandi 11 | * https://github.com/cferdinandi 12 | * 13 | * Version: 2.0.0 14 | * License: MIT 15 | */ 16 | 17 | (function () { 18 | 'use strict'; 19 | 20 | var module = angular.module('smoothScroll', []); 21 | 22 | 23 | /** 24 | * Smooth scrolls the window/div to the provided element. 25 | * 26 | * 20150713 EDIT - zephinzer 27 | * Added new option - containerId to account for scrolling within a DIV 28 | */ 29 | var smoothScroll = function (element, options) { 30 | options = options || {}; 31 | 32 | // Options 33 | var duration = options.duration || 800, 34 | offset = options.offset || 0, 35 | easing = options.easing || 'easeInOutQuart', 36 | callbackBefore = options.callbackBefore || function() {}, 37 | callbackAfter = options.callbackAfter || function() {}, 38 | container = document.getElementById(options.containerId) || null, 39 | containerPresent = (container != undefined && container != null); 40 | 41 | /** 42 | * Retrieve current location 43 | */ 44 | var getScrollLocation = function() { 45 | if(containerPresent) { 46 | return container.scrollTop; 47 | } else { 48 | if(window.pageYOffset) { 49 | return window.pageYOffset; 50 | } else { 51 | return document.documentElement.scrollTop; 52 | } 53 | } 54 | }; 55 | 56 | /** 57 | * Calculate easing pattern. 58 | * 59 | * 20150713 edit - zephinzer 60 | * - changed if-else to switch 61 | * @see http://archive.oreilly.com/pub/a/server-administration/excerpts/even-faster-websites/writing-efficient-javascript.html 62 | */ 63 | var getEasingPattern = function(type, time) { 64 | switch(type) { 65 | case 'easeInQuad': return time * time; // accelerating from zero velocity 66 | case 'easeOutQuad': return time * (2 - time); // decelerating to zero velocity 67 | case 'easeInOutQuad': return time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration 68 | case 'easeInCubic': return time * time * time; // accelerating from zero velocity 69 | case 'easeOutCubic': return (--time) * time * time + 1; // decelerating to zero velocity 70 | case 'easeInOutCubic': return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration 71 | case 'easeInQuart': return time * time * time * time; // accelerating from zero velocity 72 | case 'easeOutQuart': return 1 - (--time) * time * time * time; // decelerating to zero velocity 73 | case 'easeInOutQuart': return time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration 74 | case 'easeInQuint': return time * time * time * time * time; // accelerating from zero velocity 75 | case 'easeOutQuint': return 1 + (--time) * time * time * time * time; // decelerating to zero velocity 76 | case 'easeInOutQuint': return time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration 77 | default: return time; 78 | } 79 | }; 80 | 81 | /** 82 | * Calculate how far to scroll 83 | */ 84 | var getEndLocation = function(element) { 85 | var location = 0; 86 | if (element.offsetParent) { 87 | do { 88 | location += element.offsetTop; 89 | element = element.offsetParent; 90 | } while (element); 91 | } 92 | location = Math.max(location - offset, 0); 93 | return location; 94 | }; 95 | 96 | // Initialize the whole thing 97 | setTimeout( function() { 98 | var currentLocation = null, 99 | startLocation = getScrollLocation(), 100 | endLocation = getEndLocation(element), 101 | timeLapsed = 0, 102 | distance = endLocation - startLocation, 103 | percentage, 104 | position, 105 | scrollHeight, 106 | internalHeight; 107 | 108 | /** 109 | * Stop the scrolling animation when the anchor is reached (or at the top/bottom of the page) 110 | */ 111 | var stopAnimation = function () { 112 | currentLocation = getScrollLocation(); 113 | if(containerPresent) { 114 | scrollHeight = container.scrollHeight; 115 | internalHeight = container.clientHeight + currentLocation; 116 | } else { 117 | scrollHeight = document.body.scrollheight; 118 | internalHeight = window.innerHeight + currentLocation; 119 | } 120 | 121 | if ( 122 | ( // condition 1 123 | position == endLocation 124 | ) || 125 | ( // condition 2 126 | currentLocation == endLocation 127 | ) || 128 | ( // condition 3 129 | internalHeight >= scrollHeight 130 | ) 131 | ) { // stop 132 | clearInterval(runAnimation); 133 | callbackAfter(element); 134 | } 135 | }; 136 | 137 | /** 138 | * Scroll the page by an increment, and check if it's time to stop 139 | */ 140 | var animateScroll = function () { 141 | timeLapsed += 16; 142 | percentage = ( timeLapsed / duration ); 143 | percentage = ( percentage > 1 ) ? 1 : percentage; 144 | position = startLocation + ( distance * getEasingPattern(easing, percentage) ); 145 | if(containerPresent) { 146 | container.scrollTop = position; 147 | } else { 148 | window.scrollTo( 0, position ); 149 | } 150 | stopAnimation(); 151 | }; 152 | 153 | callbackBefore(element); 154 | var runAnimation = setInterval(animateScroll, 16); 155 | }, 0); 156 | }; 157 | 158 | 159 | // Expose the library in a factory 160 | // 161 | module.factory('smoothScroll', function() { 162 | return smoothScroll; 163 | }); 164 | 165 | 166 | /** 167 | * Scrolls the window to this element, optionally validating an expression 168 | * 169 | * 20150713 EDIT - zephinzer 170 | * Added containerId to attributes for smooth scrolling within a DIV 171 | */ 172 | module.directive('smoothScroll', ['smoothScroll', function(smoothScroll) { 173 | return { 174 | restrict: 'A', 175 | scope: { 176 | callbackBefore: '&', 177 | callbackAfter: '&', 178 | }, 179 | link: function($scope, $elem, $attrs) { 180 | if ( typeof $attrs.scrollIf === 'undefined' || $attrs.scrollIf === 'true' ) { 181 | setTimeout( function() { 182 | 183 | var callbackBefore = function(element) { 184 | if ( $attrs.callbackBefore ) { 185 | var exprHandler = $scope.callbackBefore({ element: element }); 186 | if (typeof exprHandler === 'function') { 187 | exprHandler(element); 188 | } 189 | } 190 | }; 191 | 192 | var callbackAfter = function(element) { 193 | if ( $attrs.callbackAfter ) { 194 | var exprHandler = $scope.callbackAfter({ element: element }); 195 | if (typeof exprHandler === 'function') { 196 | exprHandler(element); 197 | } 198 | } 199 | }; 200 | 201 | smoothScroll($elem[0], { 202 | duration: $attrs.duration, 203 | offset: $attrs.offset, 204 | easing: $attrs.easing, 205 | callbackBefore: callbackBefore, 206 | callbackAfter: callbackAfter, 207 | containerId: $attrs.containerId 208 | }); 209 | }, 0); 210 | } 211 | } 212 | }; 213 | }]); 214 | 215 | 216 | /** 217 | * Scrolls to a specified element ID when this element is clicked 218 | * 219 | * 20150713 EDIT - zephinzer 220 | * Added containerId to attributes for smooth scrolling within a DIV 221 | */ 222 | module.directive('scrollTo', ['smoothScroll', function(smoothScroll) { 223 | return { 224 | restrict: 'A', 225 | scope: { 226 | callbackBefore: '&', 227 | callbackAfter: '&', 228 | }, 229 | link: function($scope, $elem, $attrs) { 230 | var targetElement; 231 | 232 | $elem.on('click', function(e) { 233 | e.preventDefault(); 234 | 235 | targetElement = document.getElementById($attrs.scrollTo); 236 | if ( !targetElement ) return; 237 | 238 | var callbackBefore = function(element) { 239 | if ( $attrs.callbackBefore ) { 240 | var exprHandler = $scope.callbackBefore({element: element}); 241 | if (typeof exprHandler === 'function') { 242 | exprHandler(element); 243 | } 244 | } 245 | }; 246 | 247 | var callbackAfter = function(element) { 248 | if ( $attrs.callbackAfter ) { 249 | var exprHandler = $scope.callbackAfter({element: element}); 250 | if (typeof exprHandler === 'function') { 251 | exprHandler(element); 252 | } 253 | } 254 | }; 255 | 256 | smoothScroll(targetElement, { 257 | duration: $attrs.duration, 258 | offset: $attrs.offset, 259 | easing: $attrs.easing, 260 | callbackBefore: callbackBefore, 261 | callbackAfter: callbackAfter, 262 | containerId: $attrs.containerId 263 | }); 264 | 265 | return false; 266 | }); 267 | } 268 | }; 269 | }]); 270 | 271 | }()); --------------------------------------------------------------------------------