├── .gitignore ├── LICENSE.md ├── README.md ├── angular-scroll-spy.js ├── index.html └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 Jamie Perkins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-scroll-spy 2 | 3 | A simple, lightweight scroll-spy directive for angular that was built from scratch. It broadcasts events as elements are scrolled into or out of view. 4 | 5 | ### [Try the demo](http://inorganik.github.io/angular-scroll-spy/) 6 | 7 | ## Usage 8 | 9 | Add the `scroll-spy` attribute and an `id` on the element you want to receive a scroll event for. 10 | 11 | - `'elementFirstScrolledIntoView'` is fired once when the element first scrolls into view 12 | - `'elementScrolledIntoView'` is fired every time the element scrolls into view 13 | - `'elementScrolledOutOfView'` is fired every time the element is scrolled out of view 14 | 15 | Then in your controller, you can respond to events like this: 16 | 17 | ```js 18 | $scope.$on('elementFirstScrolledIntoView', function (event, data) { 19 | if (data === 'myElementId') { 20 | // do something 21 | } 22 | }); 23 | ``` 24 | -------------------------------------------------------------------------------- /angular-scroll-spy.js: -------------------------------------------------------------------------------- 1 | (function (angular) { 2 | 3 | 'use strict'; 4 | 5 | // Scroll Spy Directive 6 | // =============================== 7 | // 8 | // * **Class:** scrollSpy 9 | // * **Author:** Jamie Perkins 10 | // 11 | // $broadcast an event when an element comes into or goes out of view: 12 | // 13 | // 'elementFirstScrolledIntoView' is fired once when the element first scrolls into view 14 | // 'elementScrolledIntoView' is fired every time the element scrolls into view 15 | // 'elementScrolledOutOfView' is fired every time the element is scrolled out of view 16 | 17 | var module = angular.module('scrollSpyModule', []); 18 | 19 | module.directive('scrollSpy', ['$window', '$rootScope', '$log', function ($window, $rootScope, $log) { 20 | 21 | return { 22 | restrict: 'A', 23 | link: function ($scope, $el, $attrs) { 24 | 25 | function ScrollSpy() { 26 | //$log.debug('scrollSpy set for '+$attrs.id); 27 | 28 | var self = this, 29 | initialized = false, 30 | viewportHeight, 31 | elementHeight, 32 | topOffset, 33 | elementFirstScrolledIntoView = false, 34 | elementScrolledIntoView = false, 35 | elementScrolledOutOfView = false, 36 | doc = document.documentElement, 37 | id = $attrs.id || 'unknown element', 38 | target = document.getElementById(id), 39 | viewportShorterThanElement = false, 40 | percentOfElementNeededInView = 1; 41 | 42 | // onscroll 43 | this.determinePosition = function() { 44 | if (initialized) { 45 | var pos = (window.pageYOffset || doc.scrollTop); 46 | 47 | // element Scrolled Out Of View 48 | if (!elementScrolledOutOfView) { 49 | if (pos + viewportHeight < topOffset || pos > topOffset + elementHeight) { 50 | // $log.debug('element Scrolled Out Of View '+id); 51 | elementScrolledOutOfView = true; 52 | elementScrolledIntoView = false; 53 | $rootScope.$broadcast('elementScrolledOutOfView', id); 54 | } 55 | } 56 | if ((pos + viewportHeight >= topOffset + percentOfElementNeededInView * elementHeight && topOffset > pos) || 57 | (pos >= topOffset && viewportShorterThanElement)) { 58 | // element First Scrolled Into View 59 | if (!elementFirstScrolledIntoView) { 60 | // $log.debug('element First Scrolled Into View '+id); 61 | elementFirstScrolledIntoView = true; 62 | $rootScope.$broadcast('elementFirstScrolledIntoView', id); 63 | } 64 | // element Scrolled Into View 65 | if (!elementScrolledIntoView) { 66 | // $log.debug('element Scrolled Into View '+id); 67 | elementScrolledIntoView = true; 68 | elementScrolledOutOfView = false; 69 | $rootScope.$broadcast('elementScrolledIntoView', id); 70 | } 71 | } 72 | } 73 | } 74 | this.takeMeasurements = function() { 75 | viewportHeight = $window.innerHeight; 76 | elementHeight = target.offsetHeight; 77 | topOffset = target.offsetTop; 78 | // $log.debug('take measurements for '+$attrs.id+'- viewportHeight:'+viewportHeight+', element height: '+elementHeight+', top offset: '+topOffset); 79 | if (viewportHeight < elementHeight) viewportShorterThanElement = true; 80 | 81 | // determine position on page load 82 | initialized = true; 83 | self.determinePosition(); 84 | } 85 | // wait for dom to render so correct measurements can be taken 86 | var waitForRender = setInterval(function() { 87 | if (target.offsetHeight > 2) { // IE11 reports 2 at times... 88 | clearTimeout(waitForRender); 89 | self.takeMeasurements(); 90 | } 91 | }, 50); 92 | } 93 | 94 | var name = $attrs.id + '-scrollSpy'; 95 | $rootScope[name] = new ScrollSpy(); 96 | 97 | // global onscroll fns array 98 | if (!$rootScope.globalOnScrollFunctions) { 99 | $rootScope.globalOnScrollFunctions = []; 100 | } 101 | $rootScope.globalOnScrollFunctions.push($rootScope[name]); 102 | 103 | // set up global onscroll function that will call each fn in global onscroll fns 104 | if (!$rootScope.globalOnScroll) { 105 | $rootScope.globalOnScroll = function() { 106 | angular.forEach($rootScope.globalOnScrollFunctions, function (val, key) { 107 | val.determinePosition(); 108 | }); 109 | } 110 | // on scroll 111 | $window.onscroll = $rootScope.globalOnScroll; 112 | } 113 | } 114 | } 115 | }]); 116 | 117 | })(angular); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | angular-scroll-spy.js 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 33 | 34 | 35 | 36 | 37 | Fork me on GitHub 38 |
39 |
40 |
41 |
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
42 | 43 |
44 |
45 |

angular-scroll-spy.js  

46 |

A simple, lightweight scroll-spy directive for angular.js that was built from scratch. It broadcasts events as elements are scrolled into or out of view.

47 |

Download on Github

48 | 53 |

Add the scroll-spy attribute on the element you want to receive a scroll event for. Then in your controller, you can respond to events like this:

54 |
55 | $scope.$on('elementFirstScrolledIntoView', function (event, data) {
56 |   if (data === 'myElementId') {
57 |     // do something
58 |   }
59 | }); 60 |
61 |
62 |

The code above requires you to have an id assigned to the element you use scroll-spy with, because then you can have multiple elements on a page with scroll-spy. If you just have one, you can remove the if statement in the event handler.

63 |

For this demo, CountUp.js is being used to show a counting animation when the scroll event has fired. The angular countUp module included in that repo uses the `scroll-spy-event` attribute to fire on that event if the attribute is present.

64 |

Scroll down...

65 | 66 | 67 |
 
68 |

The countUp animation below is listening for 'elementScrolledIntoView' and will fire every time it's scrolled into view.

69 |

70 | 71 |
 
72 |

The countUp animation below is listening for 'elementFirstScrolledIntoView' and will only fire once.

73 |

74 | 75 |
 
76 |

Here's another 'elementScrolledIntoView'.

77 |

78 | 79 |
 
80 | 81 |
82 | 83 |
84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-scroll-spy", 3 | "version": "1.0.0", 4 | "description": "broadcast an event when an element comes into or goes out of view", 5 | "main": "angular-scroll-spy.js", 6 | "scripts": {}, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/inorganik/angular-scroll-spy.git" 10 | }, 11 | "keywords": [ 12 | "angular", 13 | "scrollspy" 14 | ], 15 | "author": "@inorganik", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/inorganik/angular-scroll-spy/issues" 19 | }, 20 | "homepage": "http://inorganik.github.io/angular-scroll-spy/" 21 | } 22 | --------------------------------------------------------------------------------