├── 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 | }());
--------------------------------------------------------------------------------