├── .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 |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 |'elementFirstScrolledIntoView'
is fired once when the element first scrolls into view'elementScrolledIntoView'
is fired every time the element scrolls into view'elementScrolledOutOfView'
is fired every time the element is scrolled out of viewAdd 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:
$scope.$on('elementFirstScrolledIntoView', function (event, data) {
56 | if (data === 'myElementId') {
57 | // do something
58 | }
59 | });
60 |
61 | 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 |The countUp animation below is listening for 'elementScrolledIntoView' and will fire every time it's scrolled into view.
69 | 70 | 71 |The countUp animation below is listening for 'elementFirstScrolledIntoView' and will only fire once.
73 | 74 | 75 |Here's another 'elementScrolledIntoView'.
77 | 78 | 79 |