├── .gitignore ├── bower.json ├── gulpfile.js ├── LICENSE ├── dist ├── ng-sticky.js └── ng-sticky.min.js ├── package.json ├── README.md ├── index.html ├── src └── index.js └── sidebar.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-sticky-navigation-directive", 3 | "version": "0.0.8", 4 | "homepage": "https://github.com/ng-milk/angular-sticky-navigation-directive", 5 | "authors": [ 6 | "Dan Mindru " 7 | ], 8 | "description": "Angular directive to make a sticky element, read more on https://ngmilk.rocks/2015/04/09/angularjs-sticky-navigation-directive/", 9 | "main": "dist/ng-sticky.js", 10 | "keywords": [ 11 | "angular", 12 | "angularjs", 13 | "directive", 14 | "sticky", 15 | "navigation" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "lib", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var del = require('del'), 4 | gulp = require('gulp'), 5 | gulpsync = require('gulp-sync')(gulp), 6 | uglify = require('gulp-uglify'), 7 | rename = require('gulp-rename'), 8 | eslint = require('gulp-eslint'); 9 | 10 | var options = { 11 | src: 'src', 12 | dist: 'dist' 13 | }; 14 | 15 | gulp.task('scripts', function () { 16 | return gulp.src([options.src + '/**/*.js']) 17 | .pipe(eslint()) 18 | .pipe(uglify()) 19 | .pipe(rename('ng-sticky.js')) 20 | .pipe(gulp.dest(options.dist)); 21 | }); 22 | 23 | gulp.task('clean', function (cb){ 24 | return del([options.dist], cb); 25 | }); 26 | 27 | gulp.task('watch', function () { 28 | return gulp.watch([options.src + '/**/*'], ['build']); 29 | }); 30 | 31 | gulp.task('default', gulpsync.sync([['build'], ['watch']])); 32 | gulp.task('build', gulpsync.sync([['clean'], ['scripts']])); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) ngmilk, https://ngmilk.rocks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /dist/ng-sticky.js: -------------------------------------------------------------------------------- 1 | function stickyNavDirective(e,t,i){function n(n,s,c){function o(){var t=Math.max(i[0].documentElement.clientHeight,i[0].body.scrollHeight,i[0].documentElement.scrollHeight,i[0].body.offsetHeight,i[0].documentElement.offsetHeight);return f+r>=t?void s.removeClass(d):void(!s.hasClass(d)&&e.pageYOffset>f+r*(g?0:1)?s.addClass(d):s.hasClass(d)&&e.pageYOffset<=f+r*(g?0:1)&&s.removeClass(d))}function a(i){isNaN(+i)&&(i=c.maxTries||0),i>=0&&t(function(){var t=s[0].getBoundingClientRect().top+e.pageYOffset,n=s[0].clientHeight;t>0||n>0?(f=t,r=n):a(i-1)},c.msRetryDelay||100)}var l=angular.element(e),r=0,f=0,u="ng-sticky-fixed",d=c.stickyNav||u,g="undefined"!=typeof c.ignoreElementSize;!function(){a(),n.$watch(function(){return s[0].getBoundingClientRect().top+e.pageYOffset},function(e,t){e===t||s.hasClass(d)||(f=e)}),n.$watch(function(){return s[0].clientHeight},function(e,t){e===t||s.hasClass(d)||(r=e)}),l.bind("resize",function(){s.removeClass(d),f=s[0].getBoundingClientRect().top+e.pageYOffset,o()}),l.bind("scroll",o)}()}return{scope:{},restrict:"A",link:n}}angular.module("dm.stickyNav",[]).directive("stickyNav",stickyNavDirective),stickyNavDirective.$inject=["$window","$timeout","$document"]; -------------------------------------------------------------------------------- /dist/ng-sticky.min.js: -------------------------------------------------------------------------------- 1 | function stickyNavDirective(e,t,i){function n(n,s,c){function o(){var t=Math.max(i[0].documentElement.clientHeight,i[0].body.scrollHeight,i[0].documentElement.scrollHeight,i[0].body.offsetHeight,i[0].documentElement.offsetHeight);return f+r>=t?void s.removeClass(d):void(!s.hasClass(d)&&e.pageYOffset>f+r*(g?0:1)?s.addClass(d):s.hasClass(d)&&e.pageYOffset<=f+r*(g?0:1)&&s.removeClass(d))}function a(i){isNaN(+i)&&(i=c.maxTries||0),i>=0&&t(function(){var t=s[0].getBoundingClientRect().top+e.pageYOffset,n=s[0].clientHeight;t>0||n>0?(f=t,r=n):a(i-1)},c.msRetryDelay||100)}var l=angular.element(e),r=0,f=0,u="ng-sticky-fixed",d=c.stickyNav||u,g="undefined"!=typeof c.ignoreElementSize;!function(){a(),n.$watch(function(){return s[0].getBoundingClientRect().top+e.pageYOffset},function(e,t){e===t||s.hasClass(d)||(f=e)}),n.$watch(function(){return s[0].clientHeight},function(e,t){e===t||s.hasClass(d)||(r=e)}),l.bind("resize",function(){s.removeClass(d),f=s[0].getBoundingClientRect().top+e.pageYOffset,o()}),l.bind("scroll",o)}()}return{scope:{},restrict:"A",link:n}}angular.module("dm.stickyNav",[]).directive("stickyNav",stickyNavDirective),stickyNavDirective.$inject=["$window","$timeout","$document"]; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-sticky-navigation-directive", 3 | "version": "0.0.8", 4 | "description": "Angular directive to make a sticky element, read more on https://ngmilk.rocks/2015/04/09/angularjs-sticky-navigation-directive/", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ng-milk/angular-sticky-navigation-directive.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "angularjs", 16 | "directive", 17 | "sticky", 18 | "navigation" 19 | ], 20 | "author": "Dan Mindru (http://mindrudan.com/)", 21 | "contributors": [ 22 | { 23 | "name" : "Luciano Lattes", 24 | "url" : "https://github.com/llattes" 25 | } 26 | ], 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/ng-milk/angular-sticky-navigation-directive/issues" 30 | }, 31 | "homepage": "https://github.com/ng-milk/angular-sticky-navigation-directive#readme", 32 | "devDependencies": { 33 | "del": "^2.2.0", 34 | "gulp": "^3.9.0", 35 | "gulp-eslint": "^1.1.1", 36 | "gulp-rename": "^1.2.2", 37 | "gulp-sync": "^0.1.4", 38 | "gulp-uglify": "^1.5.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular sticky navigation directive 2 | ![Angular sticky navigation directive demo](http://cdn.makeagif.com/media/9-14-2015/D7S9Ra.gif) 3 | 4 | 5 | ### [Quick Demo](http://ng-milk.github.io/angular-sticky-navigation-directive/) 6 | Angular directive to make a sticky element, read about it on [here](https://ngmilk.rocks/2015/04/09/angularjs-sticky-navigation-directive/). 7 | It will add a `ng-sticky-fixed` class whenever your navigation is not visible on the viewport (therefore it will make it visible & sticky). 8 | It's up to you to style the class properly, see the example for more. 9 | 10 | 11 | ## Usage 12 | 1. Include `ng-sticky.js`. 13 | 2. Add `dm.stickyNav` as a dependency to your app. 14 | 3. Profit! 15 | 16 | 17 | ## Bower 18 | Installable via `bower`: 19 | 20 | ```bash 21 | bower install ng-sticky 22 | ``` 23 | 24 | ## Example 25 | See [index.html](https://github.com/ng-milk/angular-sticky-navigation-directive/blob/master/index.html) for an example. 26 | 27 | ```html 28 | 31 | 32 | 43 | 44 |
45 |
46 | 47 | 48 |
49 | 50 |
51 | [...] Lots of text 52 |
53 |
54 | ``` 55 | 56 | ## Custom sticky class 57 | By default `ng-sticky-fixed` will be appended to the element's class. By providing a value to the `sticky-nav` attribute you can use any other class: 58 | 59 | ```html 60 | [...] 61 | 66 | 67 |
68 |
69 | [...] 70 |
71 | [...] 72 |
73 | ``` 74 | ## Additional directive attributes 75 | By default, the directive will attempt a 100 milliseconds $timeout call to wait for the DOM to load. You can provide different values for retrying until DOM is ready using `max-tries` and `ms-retry-delay`: 76 | 77 | ```html 78 |
79 | ``` 80 | When you don't want to use the height of your sticky element as part of the threshold for applying the sticky class, you can add the `ignore-element-size` directive attribute. It is useful for 'tall' elements like sidebars or side menus. 81 | 82 | ```html 83 |
84 | ``` 85 | 86 | ## About ngmilk 87 | 88 | 89 | **ngmilk** is the place to go for fresh front-end articles, with a focus on AngularJS. 90 | See more on [ngmilk.rocks](https://ngmilk.rocks) 91 | 92 | Follow [@ngmilkrocks](http://twitter.com/ngmilkrocks) on Twitter to stay ahead of the game. 93 | 94 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular sticky nav demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 55 | 56 | 57 |
58 | Fork on Github 59 | Submit an issue 60 | 61 | See sidebar example 62 |
63 | 64 |
65 |

66 |

67 |
68 | 69 | 70 | 71 | 72 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * angular-sticky-navigation-directive v0.0.7 3 | * (c) 2015 Dan Mindru 4 | * License: MIT 5 | * 6 | * 7 | * Thanks for contributions: 8 | * - Luciano Lattes 9 | */ 10 | 11 | angular.module('dm.stickyNav', []) 12 | .directive('stickyNav', stickyNavDirective); 13 | 14 | //Inject $window, $timeout & document so we won't have a bad time during uglyfication. 15 | stickyNavDirective.$inject = ['$window', '$timeout', '$document']; 16 | 17 | function stickyNavDirective($window, $timeout, $document){ 18 | function stickyNavLink(scope, element, attrs){ 19 | var w = angular.element($window), 20 | size = 0, 21 | top = 0, 22 | defaultStickyClass = 'ng-sticky-fixed', 23 | stickyClass = (attrs.stickyNav || defaultStickyClass), 24 | ignoreElementSize = (typeof attrs.ignoreElementSize !== 'undefined'); 25 | 26 | /* 27 | * On scroll we just check the page offset 28 | * if it's bigger than the target size we fix the controls 29 | * otherwise we display them inline 30 | */ 31 | function toggleStickyNav(){ 32 | // Do not add class if the sticky element is bigger than the document. 33 | var docHeight = Math.max($document[0].documentElement["clientHeight"], 34 | $document[0].body["scrollHeight"], 35 | $document[0].documentElement["scrollHeight"], 36 | $document[0].body["offsetHeight"], 37 | $document[0].documentElement["offsetHeight"]); 38 | 39 | if((top + size) >= docHeight){ 40 | element.removeClass(stickyClass); 41 | return; 42 | } 43 | 44 | if(!element.hasClass(stickyClass) && $window.pageYOffset > (top + (size * (ignoreElementSize ? 0 : 1)))){ 45 | element.addClass(stickyClass); 46 | } else if(element.hasClass(stickyClass) && $window.pageYOffset <= (top + (size * (ignoreElementSize ? 0 : 1)))){ 47 | element.removeClass(stickyClass); 48 | } 49 | } 50 | 51 | /* 52 | * Function with retries to set the 'top' & 'size' values when DOM is ready. 53 | */ 54 | function setInitialValues(tries){ 55 | // A sanity check, just in case we reuse this function as a handler. 56 | if(isNaN(+tries)){ 57 | tries = attrs.maxTries || 0; 58 | } 59 | 60 | if(tries >= 0){ 61 | $timeout(function(){ 62 | var topValue = element[0].getBoundingClientRect().top + $window.pageYOffset; 63 | var elHeight = element[0].clientHeight; 64 | if(topValue > 0 || elHeight > 0){ 65 | top = topValue; 66 | size = elHeight; 67 | } else { 68 | setInitialValues(tries - 1); 69 | } 70 | }, attrs.msRetryDelay || 100); 71 | } 72 | } 73 | 74 | (function activate() { 75 | setInitialValues(); 76 | 77 | /* 78 | * We update the top position -> this is for initial page load, 79 | * while elements load 80 | */ 81 | scope.$watch(function(){ 82 | return element[0].getBoundingClientRect().top + $window.pageYOffset; 83 | }, function(newValue, oldValue){ 84 | if(newValue !== oldValue && !element.hasClass(stickyClass)){ 85 | top = newValue; 86 | } 87 | }); 88 | 89 | /* 90 | * We update the size -> this is for initial page load, 91 | * while elements load 92 | */ 93 | scope.$watch(function(){ 94 | return element[0].clientHeight; 95 | }, function(newValue, oldValue){ 96 | if(newValue !== oldValue && !element.hasClass(stickyClass)){ 97 | size = newValue; 98 | } 99 | }); 100 | 101 | /* 102 | * Resizing the window displays the controls inline by default. 103 | * This is needed to calculate the correct boundingClientRect. 104 | * After the top is updated we toggle the nav, eventually 105 | * fixing the controls again if needed. 106 | */ 107 | w.bind('resize', function stickyNavResize(){ 108 | element.removeClass(stickyClass); 109 | top = element[0].getBoundingClientRect().top + $window.pageYOffset; 110 | toggleStickyNav(); 111 | }); 112 | w.bind('scroll', toggleStickyNav); 113 | })(); 114 | } 115 | 116 | return { 117 | scope: {}, 118 | restrict: 'A', 119 | link: stickyNavLink 120 | }; 121 | } 122 | -------------------------------------------------------------------------------- /sidebar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular sticky sidebar demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 89 | 90 | 91 | 92 |
93 |

 Layout with a sticky right sidebar

94 |
95 | 96 | 97 | 126 | 127 |
128 |

Page's main content

129 |

130 | See sticky nav example 131 |

132 |

133 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc sed ante nec quam euismod malesuada. Nullam ullamcorper eros mi, eu feugiat orci egestas sed. Suspendisse condimentum ultrices arcu, id varius dolor tincidunt eu. Integer a aliquet nisl. 134 |

135 |

136 | Vivamus eleifend tincidunt nisl, sagittis pharetra tortor finibus id. Donec a egestas est, ac varius ipsum. Quisque quis pulvinar metus. Maecenas semper est vulputate, volutpat libero id, mollis felis. Aliquam varius mattis dolor vel iaculis. Maecenas varius, lectus in congue gravida, erat sapien mattis nunc, at congue neque mauris vel orci. 137 |

138 | Click to add content and see ng-sticky in action... 139 | Remove added content... 140 |
141 |

142 |

143 |
144 |
145 | 146 | 147 | 148 | 174 | 175 | 176 | --------------------------------------------------------------------------------