├── .eslintrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── README.md ├── bower.json ├── gulpfile.js ├── lib ├── jquery.waypoints.js ├── jquery.waypoints.min.js ├── noframework.waypoints.js ├── noframework.waypoints.min.js ├── shortcuts │ ├── infinite.js │ ├── infinite.min.js │ ├── inview.js │ ├── inview.min.js │ ├── sticky.js │ └── sticky.min.js ├── waypoints.debug.js ├── zepto.waypoints.js └── zepto.waypoints.min.js ├── licenses.txt ├── package.json ├── src ├── adapters │ ├── jquery-zepto-fn-extension.js │ ├── jquery.js │ ├── noframework.js │ └── zepto.js ├── context.js ├── debug.js ├── group.js ├── shortcuts │ ├── infinite.js │ ├── inview.js │ └── sticky.js └── waypoint.js ├── test ├── adapter-fn-spec.js ├── context-spec.js ├── debug-spec.js ├── fixtures │ ├── infinite.html │ ├── standard.html │ └── sticky.html ├── group-spec.js ├── infinite-spec.js ├── inview-spec.js ├── lib │ ├── coffee-script.js │ ├── jasmine-html.js │ ├── jasmine-jquery.js │ ├── jasmine.css │ ├── jasmine.js │ ├── require.js │ └── testloader.js ├── settings.js ├── sticky-spec.js └── waypoint-spec.js └── testem.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | 7 | "rules": { 8 | "brace-style": [1, "stroustrup"], 9 | "consistent-this": [1, "self"], 10 | "eqeqeq": [1, "smart"], 11 | "func-style": [1, "declaration"], 12 | "no-else-return": 1, 13 | "no-extra-parens": 1, 14 | "no-floating-decimal": 1, 15 | "no-nested-ternary": 1, 16 | "no-lonely-if": 1, 17 | "quotes": [1, "single", "avoid-escape"], 18 | "radix": 1, 19 | "semi": [1, "never"], 20 | "space-after-keywords": [1, "always"], 21 | "space-in-brackets": [1, "never"], 22 | "space-unary-word-ops": 1, 23 | "wrap-iife": 1 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .DS_Store 4 | /style.scss 5 | *.gz 6 | node_modules 7 | bower_components 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "0.10" 5 | before_script: 6 | - npm install -g bower 7 | - bower install 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v4.0.1 4 | 5 | - Improve performance of `enableAll`. (Issue #454) 6 | - Handle edge case bug where Waypoint initialization during a specific part of iOS scroll bounce would cause an immediate trigger of it. (Issue #499) 7 | - Maintain `window` Context/resize-handler even when there are only non-window-context waypoints. (Issue #442) 8 | 9 | ## v4.0.0 10 | 11 | - Allow Sticky option `wrapper` to accept false, which will not create a wrapper and instead use the preexisting parent element. (Pull #416) 12 | - Waypoints that are immediately triggered on creation because they've already passed their trigger point now run their handlers on the next animation frame. This contains Zalgo. (Issue #384) 13 | - Pass the jQuery object of items added during an Infinite page load to the `onAfterPageLoad` callback. (Pull #398) 14 | - Add `enabled` option, `enable` and `disable` methods to the Inview shortcut (Pull #406) 15 | - Make the Inview instance `this` within the callbacks, rather than the invdividual underlying waypoints. (Issue #412) 16 | - Account for changes to jQuery 3 around calling `offset` on the window. (Pull #430) 17 | - Add `context` option to Inview. (Issue #433) 18 | 19 | ## v3.1.1 20 | 21 | - Fix bad `isWindow` checks causing errors in IE8-. (Issue #372) 22 | 23 | ## v3.1.0 24 | 25 | - Add `Waypoint.disableAll` and `Waypoint.enableAll` methods. 26 | - Fix Illegal Invocation errors stemming from non-window context use of `requestAnimationFrame`. (Pull #366) 27 | - Keep disabled waypoints from triggering debug script errors. (Pull #365) 28 | - Allow Infinite Scroll items to be root elements in the AJAX response. (Pull #361) 29 | - In debug script, detect display none and fixed positioning defined in CSS. 30 | 31 | ## v3.0.1 32 | 33 | - Add semicolons to the end of built files to aid in clean concatenation. (Issue #353) 34 | 35 | ## v3.0.0 36 | 37 | - Remove hard jQuery dependency. Create builds for jQuery, Zepto, and no DOM framework. (Issue #282) 38 | - Expose `Waypoint` and `Context` classes. (Issue #281) 39 | - Add `Group` class and `group` option for grouping waypoints. Make `continuous` option work within these groups. (Issue #264) 40 | - Add Inview shortcut. (Issue #131) 41 | - Extend `continuous` option to cover refreshes. (Issue #166) 42 | - Throttle resize and scroll handlers using `requestAnimationFrame` instead of a set millisecond timeout. Fallback to the old 60 FPS `setTimeout` throttle for unsupported browsers. (Issue #242) 43 | - Add debugging script for diagnosing common problems. 44 | - Remove `triggerOnce` option. 45 | - Add `viewportWidth` utility method. 46 | - Remove all traces of CoffeeScript. 47 | 48 | ## v2.0.5 49 | 50 | - Allow sticky users to define which direction the stuck class shold be applied. (Issue #192) 51 | - Fix bug where short content on a large screen could cause the infinite shortcut to stall after the first page load. (Issue #207) 52 | - Make `unsticky` safe to use on any element. Previously it would unwrap the parent even if the element had never had `sticky` called on it or already had `unsticky` called previously. (Issue #225) 53 | - Fix bug that would cause handlers to be overwritten when trying to reuse an options object. (Issue #253) 54 | - Remove "More" link when infinite shortcut reaches last page. (Issue #260) 55 | - Fix use of `this` instead of `window`, causing Browserify to fail. (Issue #262) 56 | - Stop using deprecated jQuery `load` method. (Issue #283) 57 | 58 | ## v2.0.4 59 | 60 | - Fix enable, disable, and destroys calls not chaining the jQuery object. (Issue #244) (Thanks [@robharper](https://github.com/robharper)) 61 | - Fix destroy not unregistering internal waypoint references if underlying node has been removed from the document, causing memory leaks. (Issue #243) 62 | 63 | ## v2.0.3 64 | 65 | - Add "unsticky" function for sticky shortcut. (Issue #130) 66 | - Exit early from Infinite shortcut if no "more" link exists. (Issue #140) 67 | - Delay height evaluation of sticky shortcut wrapper. (Issue #151) 68 | - Fix errors with Infinite shortcut's parsing of HTML with jQuery 1.9+. (Issue #163) 69 | 70 | 71 | ## v2.0.2 72 | 73 | - Add AMD support. (Issue #116) 74 | - Work around iOS issue with cancelled `setTimeout` timers by not using scroll throttling on touch devices. (Issue #120) 75 | - If defined, execute `handler` option passed to sticky shortcut at the end of the stuck/unstuck change. (Issue #123) 76 | 77 | ## v2.0.1 78 | 79 | - Lower default throttle values for `scrollThrottle` and `resizeThrottle`. 80 | - Fix Issue #104: Pixel offsets written as strings are interpreted as %s. 81 | - Fix Issue #100: Work around IE not firing scroll event on document shortening by forcing a scroll check on `refresh` calls. 82 | 83 | ## v2.0.0 84 | 85 | - Rewrite Waypoints in CoffeeScript. 86 | - Add Sticky and Infinite shortcut scripts. 87 | - Allow multiple Waypoints on each element. (Issue #40) 88 | - Allow horizontal scrolling Waypoints. (Issue #14) 89 | - API additions: (#69, 83, 88) 90 | - prev, next, above, below, left, right, extendFn, enable, disable 91 | - API subtractions: 92 | - remove 93 | - Remove custom 'waypoint.reached' jQuery Event from powering the trigger. 94 | - $.waypoints now returns object with vertical+horizontal properties and HTMLElement arrays instead of jQuery object (to preserve trigger order instead of jQuery's forced source order). 95 | - Add enabled option. 96 | 97 | ## v1.1.7 98 | 99 | - Actually fix the post-load bug in Issue #28 from v1.1.3. 100 | 101 | ## v1.1.6 102 | 103 | - Fix potential memory leak by unbinding events on empty context elements. 104 | 105 | ## v1.1.5 106 | 107 | - Make plugin compatible with Browserify/RequireJS. (Thanks [@cjroebuck](https://github.com/cjroebuck)) 108 | 109 | ## v1.1.4 110 | 111 | - Add handler option to give alternate binding method. 112 | 113 | ## v1.1.3 114 | 115 | - Fix cases where waypoints are added post-load and should be triggered immediately. 116 | 117 | ## v1.1.2 118 | 119 | - Fixed error thrown by waypoints with triggerOnce option that were triggered via resize refresh. 120 | 121 | ## v1.1.1 122 | 123 | - Fixed bug in initialization where all offsets were being calculated as if set to 0 initially, causing unwarranted triggers during the subsequent refresh. 124 | - Added `onlyOnScroll`, an option for individual waypoints that disables triggers due to an offset refresh that crosses the current scroll point. (All credit to [@knuton](https://github.com/knuton) on this one.) 125 | 126 | ## v1.1 127 | 128 | - Moved the continuous option out of global settings and into the options 129 | object for individual waypoints. 130 | - Added the context option, which allows for using waypoints within any 131 | scrollable element, not just the window. 132 | 133 | ## v1.0.2 134 | 135 | - Moved scroll and resize handler bindings out of load. Should play nicer with async loaders like Head JS and LABjs. 136 | - Fixed a 1px off error when using certain % offsets. 137 | - Added unit tests. 138 | 139 | ## v1.0.1 140 | 141 | - Added $.waypoints('viewportHeight'). 142 | - Fixed iOS bug (using the new viewportHeight method). 143 | - Added offset function alias: 'bottom-in-view'. 144 | 145 | ## v1.0 146 | 147 | - Initial release. 148 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Opening an Issue 2 | 3 | The GitHub issue tracker is exclusively for opening demonstrable bugs with the library or for discussing/implementing enhancements. If you need general help with Waypoints try searching through existing closed tickets, searching through the [#jquery-waypoints](http://stackoverflow.com/questions/tagged/jquery-waypoints) tag on StackOverflow, or asking your question there using that tag. If you do ask a question on StackOverflow, please follow the guidelines for [asking a good question](http://stackoverflow.com/help/how-to-ask). 4 | 5 | If you're opening a ticket for a bug: 6 | 7 | - Give a clear explanation of the bug. 8 | - Try to provide a link to a [JSFiddle](http://jsfiddle.net/) or [CodePen](http://codepen.io/) or similar reduced test case. 9 | - If you cannot provide a reduced test case, please provide a link to a live site demonstrating your bug and include in the ticket the relevant Waypoints code. 10 | 11 | If you're interested in discussing a possible new feature: 12 | 13 | - Search closed tickets for discussions that may have already occurred. 14 | - Open a ticket and let's talk! 15 | 16 | # Pull Requests 17 | 18 | - Please send the pull request against the master branch. 19 | - Note any tickets that the pull request addresses. 20 | - Add any necessary tests (see below). 21 | - Follow the coding style of the current codebase. 22 | 23 | # Tests 24 | 25 | Tests are written in [Jasmine](http://jasmine.github.io/) and run through the [testem](https://github.com/airportyh/testem) test runner. To run them locally you'll need to: 26 | 27 | - Install, if you haven't already: [PhantomJS](http://phantomjs.org/), node, and [Bower](bower.io). 28 | - `npm install` 29 | - `bower install` 30 | 31 | You can then run the tests one time by running `npm test`, or enter TDD mode by running `npm run tdd`. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Waypoints 2 | 3 | Waypoints is a library that makes it easy to execute a function whenever you scroll to an element. ![Build Status](https://travis-ci.org/imakewebthings/waypoints.svg) 4 | 5 | ```js 6 | var waypoint = new Waypoint({ 7 | element: document.getElementById('thing'), 8 | handler: function(direction) { 9 | alert('You have scrolled to a thing') 10 | } 11 | }) 12 | ``` 13 | 14 | If you're new to Waypoints, check out the [Getting Started](http://imakewebthings.com/waypoints/guides/getting-started) guide. 15 | 16 | [Read the full documentation](http://imakewebthings.com/waypoints/api/waypoint) for more details on usage and customization. 17 | 18 | ## Shortcuts 19 | 20 | In addition to the normal Waypoints script, extensions exist to make common UI patterns just a little easier to implement: 21 | 22 | - [Infinite Scrolling](http://imakewebthings.com/waypoints/shortcuts/infinite-scroll) 23 | - [Sticky Elements](http://imakewebthings.com/waypoints/shortcuts/sticky-elements) 24 | - [Inview Detection](http://imakewebthings.com/waypoints/shortcuts/inview) 25 | 26 | 27 | ## Contributing 28 | 29 | If you want to report a Waypoints bug or contribute code to the library, please read the [Contributing Guidelines](https://github.com/imakewebthings/waypoints/blob/master/CONTRIBUTING.md). If you need help *using* Waypoints, please do not open an issue. Instead, ask the question on [Stack Overflow](http://stackoverflow.com) and tag it with #jquery-waypoints. Be sure to follow the guidelines for [asking a good question](http://stackoverflow.com/help/how-to-ask). 30 | 31 | ## License 32 | 33 | Copyright (c) 2011-2014 Caleb Troughton. Licensed under the [MIT license](https://github.com/imakewebthings/waypoints/blob/master/licenses.txt). 34 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "waypoints", 3 | "main": "lib/noframework.waypoints.js", 4 | "description": "Easily execute a function when you scroll to an element.", 5 | "ignore": [ 6 | "gulpfile.js", 7 | "package.json", 8 | "src", 9 | "test", 10 | "testem.json" 11 | ], 12 | "devDependencies": { 13 | "jquery": "~1.11.1", 14 | "lodash": "~2.4.1", 15 | "jasmine-jquery": "~1.7.0", 16 | "zepto": "~1.1.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | var eslint = require('gulp-eslint') 3 | var concat = require('gulp-concat') 4 | var uglify = require('gulp-uglify') 5 | var rename = require('gulp-rename') 6 | var header = require('gulp-header') 7 | var footer = require('gulp-footer') 8 | var tap = require('gulp-tap') 9 | var merge = require('merge-stream') 10 | var pkg = require('./package.json') 11 | var path = require('path') 12 | 13 | var jsFiles = ['src/**/*.js', 'test/**/*.js', '!test/lib/**/*.js'] 14 | var shortcutTitles = { 15 | 'infinite': 'Waypoints Infinite Scroll Shortcut', 16 | 'inview': 'Waypoints Inview Shortcut', 17 | 'sticky': 'Waypoints Sticky Element Shortcut' 18 | } 19 | 20 | function fileHeader(title) { 21 | return [ 22 | '/*!', 23 | title + ' - ' + pkg.version, 24 | 'Copyright © 2011-' + new Date().getFullYear() + ' Caleb Troughton', 25 | 'Licensed under the MIT license.', 26 | 'https://github.com/imakewebthings/waypoints/blob/master/licenses.txt', 27 | '*/\n' 28 | ].join('\n') 29 | } 30 | 31 | gulp.task('lint', function() { 32 | return gulp.src(jsFiles).pipe(eslint('.eslintrc')).pipe(eslint.format()) 33 | }) 34 | 35 | gulp.task('build-core', function() { 36 | var streams = ['noframework', 'jquery', 'zepto'].map(function(adapter) { 37 | var sources = [ 38 | 'src/waypoint.js', 39 | 'src/context.js', 40 | 'src/group.js', 41 | 'src/adapters/' + adapter + '.js' 42 | ] 43 | if (['jquery', 'zepto'].indexOf(adapter) > -1) { 44 | sources.push('src/adapters/jquery-zepto-fn-extension.js') 45 | } 46 | return gulp.src(sources) 47 | .pipe(concat(adapter + '.waypoints.js', { newLine: ';' })) 48 | .pipe(header(fileHeader('Waypoints'))) 49 | .pipe(footer(';')) 50 | .pipe(gulp.dest('lib/')) 51 | .pipe(rename(adapter + '.waypoints.min.js')) 52 | .pipe(uglify({ 53 | preserveComments: 'some' 54 | })) 55 | .pipe(gulp.dest('lib/')) 56 | }) 57 | return merge.apply(null, streams) 58 | }) 59 | 60 | gulp.task('build-shortcuts', function() { 61 | return gulp.src([ 62 | 'src/shortcuts/*.js' 63 | ]) 64 | .pipe(tap(function(file) { 65 | var title = path.basename(file.path, '.js') 66 | file.contents = Buffer.concat([ 67 | new Buffer(fileHeader(shortcutTitles[title])), 68 | file.contents 69 | ]) 70 | })) 71 | .pipe(footer(';')) 72 | .pipe(gulp.dest('lib/shortcuts/')) 73 | .pipe(rename(function(path) { 74 | path.basename += '.min' 75 | })) 76 | .pipe(uglify({ 77 | preserveComments: 'some' 78 | })) 79 | .pipe(gulp.dest('lib/shortcuts/')) 80 | }) 81 | 82 | gulp.task('build-debug', function() { 83 | return gulp.src([ 84 | 'src/debug.js' 85 | ]) 86 | .pipe(rename('waypoints.debug.js')) 87 | .pipe(header(fileHeader('Waypoints Debug'))) 88 | .pipe(footer(';')) 89 | .pipe(gulp.dest('lib/')) 90 | }) 91 | 92 | gulp.task('build', ['build-core', 'build-shortcuts', 'build-debug']) 93 | 94 | gulp.task('watch', function() { 95 | gulp.watch(jsFiles, ['lint', 'build']) 96 | }) 97 | 98 | gulp.task('default', ['lint', 'build', 'watch']) 99 | -------------------------------------------------------------------------------- /lib/jquery.waypoints.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | (function() { 8 | 'use strict' 9 | 10 | var keyCounter = 0 11 | var allWaypoints = {} 12 | 13 | /* http://imakewebthings.com/waypoints/api/waypoint */ 14 | function Waypoint(options) { 15 | if (!options) { 16 | throw new Error('No options passed to Waypoint constructor') 17 | } 18 | if (!options.element) { 19 | throw new Error('No element option passed to Waypoint constructor') 20 | } 21 | if (!options.handler) { 22 | throw new Error('No handler option passed to Waypoint constructor') 23 | } 24 | 25 | this.key = 'waypoint-' + keyCounter 26 | this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options) 27 | this.element = this.options.element 28 | this.adapter = new Waypoint.Adapter(this.element) 29 | this.callback = options.handler 30 | this.axis = this.options.horizontal ? 'horizontal' : 'vertical' 31 | this.enabled = this.options.enabled 32 | this.triggerPoint = null 33 | this.group = Waypoint.Group.findOrCreate({ 34 | name: this.options.group, 35 | axis: this.axis 36 | }) 37 | this.context = Waypoint.Context.findOrCreateByElement(this.options.context) 38 | 39 | if (Waypoint.offsetAliases[this.options.offset]) { 40 | this.options.offset = Waypoint.offsetAliases[this.options.offset] 41 | } 42 | this.group.add(this) 43 | this.context.add(this) 44 | allWaypoints[this.key] = this 45 | keyCounter += 1 46 | } 47 | 48 | /* Private */ 49 | Waypoint.prototype.queueTrigger = function(direction) { 50 | this.group.queueTrigger(this, direction) 51 | } 52 | 53 | /* Private */ 54 | Waypoint.prototype.trigger = function(args) { 55 | if (!this.enabled) { 56 | return 57 | } 58 | if (this.callback) { 59 | this.callback.apply(this, args) 60 | } 61 | } 62 | 63 | /* Public */ 64 | /* http://imakewebthings.com/waypoints/api/destroy */ 65 | Waypoint.prototype.destroy = function() { 66 | this.context.remove(this) 67 | this.group.remove(this) 68 | delete allWaypoints[this.key] 69 | } 70 | 71 | /* Public */ 72 | /* http://imakewebthings.com/waypoints/api/disable */ 73 | Waypoint.prototype.disable = function() { 74 | this.enabled = false 75 | return this 76 | } 77 | 78 | /* Public */ 79 | /* http://imakewebthings.com/waypoints/api/enable */ 80 | Waypoint.prototype.enable = function() { 81 | this.context.refresh() 82 | this.enabled = true 83 | return this 84 | } 85 | 86 | /* Public */ 87 | /* http://imakewebthings.com/waypoints/api/next */ 88 | Waypoint.prototype.next = function() { 89 | return this.group.next(this) 90 | } 91 | 92 | /* Public */ 93 | /* http://imakewebthings.com/waypoints/api/previous */ 94 | Waypoint.prototype.previous = function() { 95 | return this.group.previous(this) 96 | } 97 | 98 | /* Private */ 99 | Waypoint.invokeAll = function(method) { 100 | var allWaypointsArray = [] 101 | for (var waypointKey in allWaypoints) { 102 | allWaypointsArray.push(allWaypoints[waypointKey]) 103 | } 104 | for (var i = 0, end = allWaypointsArray.length; i < end; i++) { 105 | allWaypointsArray[i][method]() 106 | } 107 | } 108 | 109 | /* Public */ 110 | /* http://imakewebthings.com/waypoints/api/destroy-all */ 111 | Waypoint.destroyAll = function() { 112 | Waypoint.invokeAll('destroy') 113 | } 114 | 115 | /* Public */ 116 | /* http://imakewebthings.com/waypoints/api/disable-all */ 117 | Waypoint.disableAll = function() { 118 | Waypoint.invokeAll('disable') 119 | } 120 | 121 | /* Public */ 122 | /* http://imakewebthings.com/waypoints/api/enable-all */ 123 | Waypoint.enableAll = function() { 124 | Waypoint.Context.refreshAll() 125 | for (var waypointKey in allWaypoints) { 126 | allWaypoints[waypointKey].enabled = true 127 | } 128 | return this 129 | } 130 | 131 | /* Public */ 132 | /* http://imakewebthings.com/waypoints/api/refresh-all */ 133 | Waypoint.refreshAll = function() { 134 | Waypoint.Context.refreshAll() 135 | } 136 | 137 | /* Public */ 138 | /* http://imakewebthings.com/waypoints/api/viewport-height */ 139 | Waypoint.viewportHeight = function() { 140 | return window.innerHeight || document.documentElement.clientHeight 141 | } 142 | 143 | /* Public */ 144 | /* http://imakewebthings.com/waypoints/api/viewport-width */ 145 | Waypoint.viewportWidth = function() { 146 | return document.documentElement.clientWidth 147 | } 148 | 149 | Waypoint.adapters = [] 150 | 151 | Waypoint.defaults = { 152 | context: window, 153 | continuous: true, 154 | enabled: true, 155 | group: 'default', 156 | horizontal: false, 157 | offset: 0 158 | } 159 | 160 | Waypoint.offsetAliases = { 161 | 'bottom-in-view': function() { 162 | return this.context.innerHeight() - this.adapter.outerHeight() 163 | }, 164 | 'right-in-view': function() { 165 | return this.context.innerWidth() - this.adapter.outerWidth() 166 | } 167 | } 168 | 169 | window.Waypoint = Waypoint 170 | }()) 171 | ;(function() { 172 | 'use strict' 173 | 174 | function requestAnimationFrameShim(callback) { 175 | window.setTimeout(callback, 1000 / 60) 176 | } 177 | 178 | var keyCounter = 0 179 | var contexts = {} 180 | var Waypoint = window.Waypoint 181 | var oldWindowLoad = window.onload 182 | 183 | /* http://imakewebthings.com/waypoints/api/context */ 184 | function Context(element) { 185 | this.element = element 186 | this.Adapter = Waypoint.Adapter 187 | this.adapter = new this.Adapter(element) 188 | this.key = 'waypoint-context-' + keyCounter 189 | this.didScroll = false 190 | this.didResize = false 191 | this.oldScroll = { 192 | x: this.adapter.scrollLeft(), 193 | y: this.adapter.scrollTop() 194 | } 195 | this.waypoints = { 196 | vertical: {}, 197 | horizontal: {} 198 | } 199 | 200 | element.waypointContextKey = this.key 201 | contexts[element.waypointContextKey] = this 202 | keyCounter += 1 203 | if (!Waypoint.windowContext) { 204 | Waypoint.windowContext = true 205 | Waypoint.windowContext = new Context(window) 206 | } 207 | 208 | this.createThrottledScrollHandler() 209 | this.createThrottledResizeHandler() 210 | } 211 | 212 | /* Private */ 213 | Context.prototype.add = function(waypoint) { 214 | var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical' 215 | this.waypoints[axis][waypoint.key] = waypoint 216 | this.refresh() 217 | } 218 | 219 | /* Private */ 220 | Context.prototype.checkEmpty = function() { 221 | var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal) 222 | var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical) 223 | var isWindow = this.element == this.element.window 224 | if (horizontalEmpty && verticalEmpty && !isWindow) { 225 | this.adapter.off('.waypoints') 226 | delete contexts[this.key] 227 | } 228 | } 229 | 230 | /* Private */ 231 | Context.prototype.createThrottledResizeHandler = function() { 232 | var self = this 233 | 234 | function resizeHandler() { 235 | self.handleResize() 236 | self.didResize = false 237 | } 238 | 239 | this.adapter.on('resize.waypoints', function() { 240 | if (!self.didResize) { 241 | self.didResize = true 242 | Waypoint.requestAnimationFrame(resizeHandler) 243 | } 244 | }) 245 | } 246 | 247 | /* Private */ 248 | Context.prototype.createThrottledScrollHandler = function() { 249 | var self = this 250 | function scrollHandler() { 251 | self.handleScroll() 252 | self.didScroll = false 253 | } 254 | 255 | this.adapter.on('scroll.waypoints', function() { 256 | if (!self.didScroll || Waypoint.isTouch) { 257 | self.didScroll = true 258 | Waypoint.requestAnimationFrame(scrollHandler) 259 | } 260 | }) 261 | } 262 | 263 | /* Private */ 264 | Context.prototype.handleResize = function() { 265 | Waypoint.Context.refreshAll() 266 | } 267 | 268 | /* Private */ 269 | Context.prototype.handleScroll = function() { 270 | var triggeredGroups = {} 271 | var axes = { 272 | horizontal: { 273 | newScroll: this.adapter.scrollLeft(), 274 | oldScroll: this.oldScroll.x, 275 | forward: 'right', 276 | backward: 'left' 277 | }, 278 | vertical: { 279 | newScroll: this.adapter.scrollTop(), 280 | oldScroll: this.oldScroll.y, 281 | forward: 'down', 282 | backward: 'up' 283 | } 284 | } 285 | 286 | for (var axisKey in axes) { 287 | var axis = axes[axisKey] 288 | var isForward = axis.newScroll > axis.oldScroll 289 | var direction = isForward ? axis.forward : axis.backward 290 | 291 | for (var waypointKey in this.waypoints[axisKey]) { 292 | var waypoint = this.waypoints[axisKey][waypointKey] 293 | if (waypoint.triggerPoint === null) { 294 | continue 295 | } 296 | var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint 297 | var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint 298 | var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint 299 | var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint 300 | if (crossedForward || crossedBackward) { 301 | waypoint.queueTrigger(direction) 302 | triggeredGroups[waypoint.group.id] = waypoint.group 303 | } 304 | } 305 | } 306 | 307 | for (var groupKey in triggeredGroups) { 308 | triggeredGroups[groupKey].flushTriggers() 309 | } 310 | 311 | this.oldScroll = { 312 | x: axes.horizontal.newScroll, 313 | y: axes.vertical.newScroll 314 | } 315 | } 316 | 317 | /* Private */ 318 | Context.prototype.innerHeight = function() { 319 | /*eslint-disable eqeqeq */ 320 | if (this.element == this.element.window) { 321 | return Waypoint.viewportHeight() 322 | } 323 | /*eslint-enable eqeqeq */ 324 | return this.adapter.innerHeight() 325 | } 326 | 327 | /* Private */ 328 | Context.prototype.remove = function(waypoint) { 329 | delete this.waypoints[waypoint.axis][waypoint.key] 330 | this.checkEmpty() 331 | } 332 | 333 | /* Private */ 334 | Context.prototype.innerWidth = function() { 335 | /*eslint-disable eqeqeq */ 336 | if (this.element == this.element.window) { 337 | return Waypoint.viewportWidth() 338 | } 339 | /*eslint-enable eqeqeq */ 340 | return this.adapter.innerWidth() 341 | } 342 | 343 | /* Public */ 344 | /* http://imakewebthings.com/waypoints/api/context-destroy */ 345 | Context.prototype.destroy = function() { 346 | var allWaypoints = [] 347 | for (var axis in this.waypoints) { 348 | for (var waypointKey in this.waypoints[axis]) { 349 | allWaypoints.push(this.waypoints[axis][waypointKey]) 350 | } 351 | } 352 | for (var i = 0, end = allWaypoints.length; i < end; i++) { 353 | allWaypoints[i].destroy() 354 | } 355 | } 356 | 357 | /* Public */ 358 | /* http://imakewebthings.com/waypoints/api/context-refresh */ 359 | Context.prototype.refresh = function() { 360 | /*eslint-disable eqeqeq */ 361 | var isWindow = this.element == this.element.window 362 | /*eslint-enable eqeqeq */ 363 | var contextOffset = isWindow ? undefined : this.adapter.offset() 364 | var triggeredGroups = {} 365 | var axes 366 | 367 | this.handleScroll() 368 | axes = { 369 | horizontal: { 370 | contextOffset: isWindow ? 0 : contextOffset.left, 371 | contextScroll: isWindow ? 0 : this.oldScroll.x, 372 | contextDimension: this.innerWidth(), 373 | oldScroll: this.oldScroll.x, 374 | forward: 'right', 375 | backward: 'left', 376 | offsetProp: 'left' 377 | }, 378 | vertical: { 379 | contextOffset: isWindow ? 0 : contextOffset.top, 380 | contextScroll: isWindow ? 0 : this.oldScroll.y, 381 | contextDimension: this.innerHeight(), 382 | oldScroll: this.oldScroll.y, 383 | forward: 'down', 384 | backward: 'up', 385 | offsetProp: 'top' 386 | } 387 | } 388 | 389 | for (var axisKey in axes) { 390 | var axis = axes[axisKey] 391 | for (var waypointKey in this.waypoints[axisKey]) { 392 | var waypoint = this.waypoints[axisKey][waypointKey] 393 | var adjustment = waypoint.options.offset 394 | var oldTriggerPoint = waypoint.triggerPoint 395 | var elementOffset = 0 396 | var freshWaypoint = oldTriggerPoint == null 397 | var contextModifier, wasBeforeScroll, nowAfterScroll 398 | var triggeredBackward, triggeredForward 399 | 400 | if (waypoint.element !== waypoint.element.window) { 401 | elementOffset = waypoint.adapter.offset()[axis.offsetProp] 402 | } 403 | 404 | if (typeof adjustment === 'function') { 405 | adjustment = adjustment.apply(waypoint) 406 | } 407 | else if (typeof adjustment === 'string') { 408 | adjustment = parseFloat(adjustment) 409 | if (waypoint.options.offset.indexOf('%') > - 1) { 410 | adjustment = Math.ceil(axis.contextDimension * adjustment / 100) 411 | } 412 | } 413 | 414 | contextModifier = axis.contextScroll - axis.contextOffset 415 | waypoint.triggerPoint = Math.floor(elementOffset + contextModifier - adjustment) 416 | wasBeforeScroll = oldTriggerPoint < axis.oldScroll 417 | nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll 418 | triggeredBackward = wasBeforeScroll && nowAfterScroll 419 | triggeredForward = !wasBeforeScroll && !nowAfterScroll 420 | 421 | if (!freshWaypoint && triggeredBackward) { 422 | waypoint.queueTrigger(axis.backward) 423 | triggeredGroups[waypoint.group.id] = waypoint.group 424 | } 425 | else if (!freshWaypoint && triggeredForward) { 426 | waypoint.queueTrigger(axis.forward) 427 | triggeredGroups[waypoint.group.id] = waypoint.group 428 | } 429 | else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) { 430 | waypoint.queueTrigger(axis.forward) 431 | triggeredGroups[waypoint.group.id] = waypoint.group 432 | } 433 | } 434 | } 435 | 436 | Waypoint.requestAnimationFrame(function() { 437 | for (var groupKey in triggeredGroups) { 438 | triggeredGroups[groupKey].flushTriggers() 439 | } 440 | }) 441 | 442 | return this 443 | } 444 | 445 | /* Private */ 446 | Context.findOrCreateByElement = function(element) { 447 | return Context.findByElement(element) || new Context(element) 448 | } 449 | 450 | /* Private */ 451 | Context.refreshAll = function() { 452 | for (var contextId in contexts) { 453 | contexts[contextId].refresh() 454 | } 455 | } 456 | 457 | /* Public */ 458 | /* http://imakewebthings.com/waypoints/api/context-find-by-element */ 459 | Context.findByElement = function(element) { 460 | return contexts[element.waypointContextKey] 461 | } 462 | 463 | window.onload = function() { 464 | if (oldWindowLoad) { 465 | oldWindowLoad() 466 | } 467 | Context.refreshAll() 468 | } 469 | 470 | 471 | Waypoint.requestAnimationFrame = function(callback) { 472 | var requestFn = window.requestAnimationFrame || 473 | window.mozRequestAnimationFrame || 474 | window.webkitRequestAnimationFrame || 475 | requestAnimationFrameShim 476 | requestFn.call(window, callback) 477 | } 478 | Waypoint.Context = Context 479 | }()) 480 | ;(function() { 481 | 'use strict' 482 | 483 | function byTriggerPoint(a, b) { 484 | return a.triggerPoint - b.triggerPoint 485 | } 486 | 487 | function byReverseTriggerPoint(a, b) { 488 | return b.triggerPoint - a.triggerPoint 489 | } 490 | 491 | var groups = { 492 | vertical: {}, 493 | horizontal: {} 494 | } 495 | var Waypoint = window.Waypoint 496 | 497 | /* http://imakewebthings.com/waypoints/api/group */ 498 | function Group(options) { 499 | this.name = options.name 500 | this.axis = options.axis 501 | this.id = this.name + '-' + this.axis 502 | this.waypoints = [] 503 | this.clearTriggerQueues() 504 | groups[this.axis][this.name] = this 505 | } 506 | 507 | /* Private */ 508 | Group.prototype.add = function(waypoint) { 509 | this.waypoints.push(waypoint) 510 | } 511 | 512 | /* Private */ 513 | Group.prototype.clearTriggerQueues = function() { 514 | this.triggerQueues = { 515 | up: [], 516 | down: [], 517 | left: [], 518 | right: [] 519 | } 520 | } 521 | 522 | /* Private */ 523 | Group.prototype.flushTriggers = function() { 524 | for (var direction in this.triggerQueues) { 525 | var waypoints = this.triggerQueues[direction] 526 | var reverse = direction === 'up' || direction === 'left' 527 | waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint) 528 | for (var i = 0, end = waypoints.length; i < end; i += 1) { 529 | var waypoint = waypoints[i] 530 | if (waypoint.options.continuous || i === waypoints.length - 1) { 531 | waypoint.trigger([direction]) 532 | } 533 | } 534 | } 535 | this.clearTriggerQueues() 536 | } 537 | 538 | /* Private */ 539 | Group.prototype.next = function(waypoint) { 540 | this.waypoints.sort(byTriggerPoint) 541 | var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) 542 | var isLast = index === this.waypoints.length - 1 543 | return isLast ? null : this.waypoints[index + 1] 544 | } 545 | 546 | /* Private */ 547 | Group.prototype.previous = function(waypoint) { 548 | this.waypoints.sort(byTriggerPoint) 549 | var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) 550 | return index ? this.waypoints[index - 1] : null 551 | } 552 | 553 | /* Private */ 554 | Group.prototype.queueTrigger = function(waypoint, direction) { 555 | this.triggerQueues[direction].push(waypoint) 556 | } 557 | 558 | /* Private */ 559 | Group.prototype.remove = function(waypoint) { 560 | var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) 561 | if (index > -1) { 562 | this.waypoints.splice(index, 1) 563 | } 564 | } 565 | 566 | /* Public */ 567 | /* http://imakewebthings.com/waypoints/api/first */ 568 | Group.prototype.first = function() { 569 | return this.waypoints[0] 570 | } 571 | 572 | /* Public */ 573 | /* http://imakewebthings.com/waypoints/api/last */ 574 | Group.prototype.last = function() { 575 | return this.waypoints[this.waypoints.length - 1] 576 | } 577 | 578 | /* Private */ 579 | Group.findOrCreate = function(options) { 580 | return groups[options.axis][options.name] || new Group(options) 581 | } 582 | 583 | Waypoint.Group = Group 584 | }()) 585 | ;(function() { 586 | 'use strict' 587 | 588 | var $ = window.jQuery 589 | var Waypoint = window.Waypoint 590 | 591 | function JQueryAdapter(element) { 592 | this.$element = $(element) 593 | } 594 | 595 | $.each([ 596 | 'innerHeight', 597 | 'innerWidth', 598 | 'off', 599 | 'offset', 600 | 'on', 601 | 'outerHeight', 602 | 'outerWidth', 603 | 'scrollLeft', 604 | 'scrollTop' 605 | ], function(i, method) { 606 | JQueryAdapter.prototype[method] = function() { 607 | var args = Array.prototype.slice.call(arguments) 608 | return this.$element[method].apply(this.$element, args) 609 | } 610 | }) 611 | 612 | $.each([ 613 | 'extend', 614 | 'inArray', 615 | 'isEmptyObject' 616 | ], function(i, method) { 617 | JQueryAdapter[method] = $[method] 618 | }) 619 | 620 | Waypoint.adapters.push({ 621 | name: 'jquery', 622 | Adapter: JQueryAdapter 623 | }) 624 | Waypoint.Adapter = JQueryAdapter 625 | }()) 626 | ;(function() { 627 | 'use strict' 628 | 629 | var Waypoint = window.Waypoint 630 | 631 | function createExtension(framework) { 632 | return function() { 633 | var waypoints = [] 634 | var overrides = arguments[0] 635 | 636 | if (framework.isFunction(arguments[0])) { 637 | overrides = framework.extend({}, arguments[1]) 638 | overrides.handler = arguments[0] 639 | } 640 | 641 | this.each(function() { 642 | var options = framework.extend({}, overrides, { 643 | element: this 644 | }) 645 | if (typeof options.context === 'string') { 646 | options.context = framework(this).closest(options.context)[0] 647 | } 648 | waypoints.push(new Waypoint(options)) 649 | }) 650 | 651 | return waypoints 652 | } 653 | } 654 | 655 | if (window.jQuery) { 656 | window.jQuery.fn.waypoint = createExtension(window.jQuery) 657 | } 658 | if (window.Zepto) { 659 | window.Zepto.fn.waypoint = createExtension(window.Zepto) 660 | } 661 | }()) 662 | ; -------------------------------------------------------------------------------- /lib/jquery.waypoints.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | !function(){"use strict";function t(o){if(!o)throw new Error("No options passed to Waypoint constructor");if(!o.element)throw new Error("No element option passed to Waypoint constructor");if(!o.handler)throw new Error("No handler option passed to Waypoint constructor");this.key="waypoint-"+e,this.options=t.Adapter.extend({},t.defaults,o),this.element=this.options.element,this.adapter=new t.Adapter(this.element),this.callback=o.handler,this.axis=this.options.horizontal?"horizontal":"vertical",this.enabled=this.options.enabled,this.triggerPoint=null,this.group=t.Group.findOrCreate({name:this.options.group,axis:this.axis}),this.context=t.Context.findOrCreateByElement(this.options.context),t.offsetAliases[this.options.offset]&&(this.options.offset=t.offsetAliases[this.options.offset]),this.group.add(this),this.context.add(this),i[this.key]=this,e+=1}var e=0,i={};t.prototype.queueTrigger=function(t){this.group.queueTrigger(this,t)},t.prototype.trigger=function(t){this.enabled&&this.callback&&this.callback.apply(this,t)},t.prototype.destroy=function(){this.context.remove(this),this.group.remove(this),delete i[this.key]},t.prototype.disable=function(){return this.enabled=!1,this},t.prototype.enable=function(){return this.context.refresh(),this.enabled=!0,this},t.prototype.next=function(){return this.group.next(this)},t.prototype.previous=function(){return this.group.previous(this)},t.invokeAll=function(t){var e=[];for(var o in i)e.push(i[o]);for(var n=0,r=e.length;r>n;n++)e[n][t]()},t.destroyAll=function(){t.invokeAll("destroy")},t.disableAll=function(){t.invokeAll("disable")},t.enableAll=function(){t.Context.refreshAll();for(var e in i)i[e].enabled=!0;return this},t.refreshAll=function(){t.Context.refreshAll()},t.viewportHeight=function(){return window.innerHeight||document.documentElement.clientHeight},t.viewportWidth=function(){return document.documentElement.clientWidth},t.adapters=[],t.defaults={context:window,continuous:!0,enabled:!0,group:"default",horizontal:!1,offset:0},t.offsetAliases={"bottom-in-view":function(){return this.context.innerHeight()-this.adapter.outerHeight()},"right-in-view":function(){return this.context.innerWidth()-this.adapter.outerWidth()}},window.Waypoint=t}(),function(){"use strict";function t(t){window.setTimeout(t,1e3/60)}function e(t){this.element=t,this.Adapter=n.Adapter,this.adapter=new this.Adapter(t),this.key="waypoint-context-"+i,this.didScroll=!1,this.didResize=!1,this.oldScroll={x:this.adapter.scrollLeft(),y:this.adapter.scrollTop()},this.waypoints={vertical:{},horizontal:{}},t.waypointContextKey=this.key,o[t.waypointContextKey]=this,i+=1,n.windowContext||(n.windowContext=!0,n.windowContext=new e(window)),this.createThrottledScrollHandler(),this.createThrottledResizeHandler()}var i=0,o={},n=window.Waypoint,r=window.onload;e.prototype.add=function(t){var e=t.options.horizontal?"horizontal":"vertical";this.waypoints[e][t.key]=t,this.refresh()},e.prototype.checkEmpty=function(){var t=this.Adapter.isEmptyObject(this.waypoints.horizontal),e=this.Adapter.isEmptyObject(this.waypoints.vertical),i=this.element==this.element.window;t&&e&&!i&&(this.adapter.off(".waypoints"),delete o[this.key])},e.prototype.createThrottledResizeHandler=function(){function t(){e.handleResize(),e.didResize=!1}var e=this;this.adapter.on("resize.waypoints",function(){e.didResize||(e.didResize=!0,n.requestAnimationFrame(t))})},e.prototype.createThrottledScrollHandler=function(){function t(){e.handleScroll(),e.didScroll=!1}var e=this;this.adapter.on("scroll.waypoints",function(){(!e.didScroll||n.isTouch)&&(e.didScroll=!0,n.requestAnimationFrame(t))})},e.prototype.handleResize=function(){n.Context.refreshAll()},e.prototype.handleScroll=function(){var t={},e={horizontal:{newScroll:this.adapter.scrollLeft(),oldScroll:this.oldScroll.x,forward:"right",backward:"left"},vertical:{newScroll:this.adapter.scrollTop(),oldScroll:this.oldScroll.y,forward:"down",backward:"up"}};for(var i in e){var o=e[i],n=o.newScroll>o.oldScroll,r=n?o.forward:o.backward;for(var s in this.waypoints[i]){var a=this.waypoints[i][s];if(null!==a.triggerPoint){var l=o.oldScroll=a.triggerPoint,p=l&&h,u=!l&&!h;(p||u)&&(a.queueTrigger(r),t[a.group.id]=a.group)}}}for(var c in t)t[c].flushTriggers();this.oldScroll={x:e.horizontal.newScroll,y:e.vertical.newScroll}},e.prototype.innerHeight=function(){return this.element==this.element.window?n.viewportHeight():this.adapter.innerHeight()},e.prototype.remove=function(t){delete this.waypoints[t.axis][t.key],this.checkEmpty()},e.prototype.innerWidth=function(){return this.element==this.element.window?n.viewportWidth():this.adapter.innerWidth()},e.prototype.destroy=function(){var t=[];for(var e in this.waypoints)for(var i in this.waypoints[e])t.push(this.waypoints[e][i]);for(var o=0,n=t.length;n>o;o++)t[o].destroy()},e.prototype.refresh=function(){var t,e=this.element==this.element.window,i=e?void 0:this.adapter.offset(),o={};this.handleScroll(),t={horizontal:{contextOffset:e?0:i.left,contextScroll:e?0:this.oldScroll.x,contextDimension:this.innerWidth(),oldScroll:this.oldScroll.x,forward:"right",backward:"left",offsetProp:"left"},vertical:{contextOffset:e?0:i.top,contextScroll:e?0:this.oldScroll.y,contextDimension:this.innerHeight(),oldScroll:this.oldScroll.y,forward:"down",backward:"up",offsetProp:"top"}};for(var r in t){var s=t[r];for(var a in this.waypoints[r]){var l,h,p,u,c,d=this.waypoints[r][a],f=d.options.offset,w=d.triggerPoint,y=0,g=null==w;d.element!==d.element.window&&(y=d.adapter.offset()[s.offsetProp]),"function"==typeof f?f=f.apply(d):"string"==typeof f&&(f=parseFloat(f),d.options.offset.indexOf("%")>-1&&(f=Math.ceil(s.contextDimension*f/100))),l=s.contextScroll-s.contextOffset,d.triggerPoint=Math.floor(y+l-f),h=w=s.oldScroll,u=h&&p,c=!h&&!p,!g&&u?(d.queueTrigger(s.backward),o[d.group.id]=d.group):!g&&c?(d.queueTrigger(s.forward),o[d.group.id]=d.group):g&&s.oldScroll>=d.triggerPoint&&(d.queueTrigger(s.forward),o[d.group.id]=d.group)}}return n.requestAnimationFrame(function(){for(var t in o)o[t].flushTriggers()}),this},e.findOrCreateByElement=function(t){return e.findByElement(t)||new e(t)},e.refreshAll=function(){for(var t in o)o[t].refresh()},e.findByElement=function(t){return o[t.waypointContextKey]},window.onload=function(){r&&r(),e.refreshAll()},n.requestAnimationFrame=function(e){var i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||t;i.call(window,e)},n.Context=e}(),function(){"use strict";function t(t,e){return t.triggerPoint-e.triggerPoint}function e(t,e){return e.triggerPoint-t.triggerPoint}function i(t){this.name=t.name,this.axis=t.axis,this.id=this.name+"-"+this.axis,this.waypoints=[],this.clearTriggerQueues(),o[this.axis][this.name]=this}var o={vertical:{},horizontal:{}},n=window.Waypoint;i.prototype.add=function(t){this.waypoints.push(t)},i.prototype.clearTriggerQueues=function(){this.triggerQueues={up:[],down:[],left:[],right:[]}},i.prototype.flushTriggers=function(){for(var i in this.triggerQueues){var o=this.triggerQueues[i],n="up"===i||"left"===i;o.sort(n?e:t);for(var r=0,s=o.length;s>r;r+=1){var a=o[r];(a.options.continuous||r===o.length-1)&&a.trigger([i])}}this.clearTriggerQueues()},i.prototype.next=function(e){this.waypoints.sort(t);var i=n.Adapter.inArray(e,this.waypoints),o=i===this.waypoints.length-1;return o?null:this.waypoints[i+1]},i.prototype.previous=function(e){this.waypoints.sort(t);var i=n.Adapter.inArray(e,this.waypoints);return i?this.waypoints[i-1]:null},i.prototype.queueTrigger=function(t,e){this.triggerQueues[e].push(t)},i.prototype.remove=function(t){var e=n.Adapter.inArray(t,this.waypoints);e>-1&&this.waypoints.splice(e,1)},i.prototype.first=function(){return this.waypoints[0]},i.prototype.last=function(){return this.waypoints[this.waypoints.length-1]},i.findOrCreate=function(t){return o[t.axis][t.name]||new i(t)},n.Group=i}(),function(){"use strict";function t(t){this.$element=e(t)}var e=window.jQuery,i=window.Waypoint;e.each(["innerHeight","innerWidth","off","offset","on","outerHeight","outerWidth","scrollLeft","scrollTop"],function(e,i){t.prototype[i]=function(){var t=Array.prototype.slice.call(arguments);return this.$element[i].apply(this.$element,t)}}),e.each(["extend","inArray","isEmptyObject"],function(i,o){t[o]=e[o]}),i.adapters.push({name:"jquery",Adapter:t}),i.Adapter=t}(),function(){"use strict";function t(t){return function(){var i=[],o=arguments[0];return t.isFunction(arguments[0])&&(o=t.extend({},arguments[1]),o.handler=arguments[0]),this.each(function(){var n=t.extend({},o,{element:this});"string"==typeof n.context&&(n.context=t(this).closest(n.context)[0]),i.push(new e(n))}),i}}var e=window.Waypoint;window.jQuery&&(window.jQuery.fn.waypoint=t(window.jQuery)),window.Zepto&&(window.Zepto.fn.waypoint=t(window.Zepto))}(); -------------------------------------------------------------------------------- /lib/noframework.waypoints.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | !function(){"use strict";function t(n){if(!n)throw new Error("No options passed to Waypoint constructor");if(!n.element)throw new Error("No element option passed to Waypoint constructor");if(!n.handler)throw new Error("No handler option passed to Waypoint constructor");this.key="waypoint-"+e,this.options=t.Adapter.extend({},t.defaults,n),this.element=this.options.element,this.adapter=new t.Adapter(this.element),this.callback=n.handler,this.axis=this.options.horizontal?"horizontal":"vertical",this.enabled=this.options.enabled,this.triggerPoint=null,this.group=t.Group.findOrCreate({name:this.options.group,axis:this.axis}),this.context=t.Context.findOrCreateByElement(this.options.context),t.offsetAliases[this.options.offset]&&(this.options.offset=t.offsetAliases[this.options.offset]),this.group.add(this),this.context.add(this),i[this.key]=this,e+=1}var e=0,i={};t.prototype.queueTrigger=function(t){this.group.queueTrigger(this,t)},t.prototype.trigger=function(t){this.enabled&&this.callback&&this.callback.apply(this,t)},t.prototype.destroy=function(){this.context.remove(this),this.group.remove(this),delete i[this.key]},t.prototype.disable=function(){return this.enabled=!1,this},t.prototype.enable=function(){return this.context.refresh(),this.enabled=!0,this},t.prototype.next=function(){return this.group.next(this)},t.prototype.previous=function(){return this.group.previous(this)},t.invokeAll=function(t){var e=[];for(var n in i)e.push(i[n]);for(var o=0,r=e.length;r>o;o++)e[o][t]()},t.destroyAll=function(){t.invokeAll("destroy")},t.disableAll=function(){t.invokeAll("disable")},t.enableAll=function(){t.Context.refreshAll();for(var e in i)i[e].enabled=!0;return this},t.refreshAll=function(){t.Context.refreshAll()},t.viewportHeight=function(){return window.innerHeight||document.documentElement.clientHeight},t.viewportWidth=function(){return document.documentElement.clientWidth},t.adapters=[],t.defaults={context:window,continuous:!0,enabled:!0,group:"default",horizontal:!1,offset:0},t.offsetAliases={"bottom-in-view":function(){return this.context.innerHeight()-this.adapter.outerHeight()},"right-in-view":function(){return this.context.innerWidth()-this.adapter.outerWidth()}},window.Waypoint=t}(),function(){"use strict";function t(t){window.setTimeout(t,1e3/60)}function e(t){this.element=t,this.Adapter=o.Adapter,this.adapter=new this.Adapter(t),this.key="waypoint-context-"+i,this.didScroll=!1,this.didResize=!1,this.oldScroll={x:this.adapter.scrollLeft(),y:this.adapter.scrollTop()},this.waypoints={vertical:{},horizontal:{}},t.waypointContextKey=this.key,n[t.waypointContextKey]=this,i+=1,o.windowContext||(o.windowContext=!0,o.windowContext=new e(window)),this.createThrottledScrollHandler(),this.createThrottledResizeHandler()}var i=0,n={},o=window.Waypoint,r=window.onload;e.prototype.add=function(t){var e=t.options.horizontal?"horizontal":"vertical";this.waypoints[e][t.key]=t,this.refresh()},e.prototype.checkEmpty=function(){var t=this.Adapter.isEmptyObject(this.waypoints.horizontal),e=this.Adapter.isEmptyObject(this.waypoints.vertical),i=this.element==this.element.window;t&&e&&!i&&(this.adapter.off(".waypoints"),delete n[this.key])},e.prototype.createThrottledResizeHandler=function(){function t(){e.handleResize(),e.didResize=!1}var e=this;this.adapter.on("resize.waypoints",function(){e.didResize||(e.didResize=!0,o.requestAnimationFrame(t))})},e.prototype.createThrottledScrollHandler=function(){function t(){e.handleScroll(),e.didScroll=!1}var e=this;this.adapter.on("scroll.waypoints",function(){(!e.didScroll||o.isTouch)&&(e.didScroll=!0,o.requestAnimationFrame(t))})},e.prototype.handleResize=function(){o.Context.refreshAll()},e.prototype.handleScroll=function(){var t={},e={horizontal:{newScroll:this.adapter.scrollLeft(),oldScroll:this.oldScroll.x,forward:"right",backward:"left"},vertical:{newScroll:this.adapter.scrollTop(),oldScroll:this.oldScroll.y,forward:"down",backward:"up"}};for(var i in e){var n=e[i],o=n.newScroll>n.oldScroll,r=o?n.forward:n.backward;for(var s in this.waypoints[i]){var l=this.waypoints[i][s];if(null!==l.triggerPoint){var a=n.oldScroll=l.triggerPoint,p=a&&h,u=!a&&!h;(p||u)&&(l.queueTrigger(r),t[l.group.id]=l.group)}}}for(var d in t)t[d].flushTriggers();this.oldScroll={x:e.horizontal.newScroll,y:e.vertical.newScroll}},e.prototype.innerHeight=function(){return this.element==this.element.window?o.viewportHeight():this.adapter.innerHeight()},e.prototype.remove=function(t){delete this.waypoints[t.axis][t.key],this.checkEmpty()},e.prototype.innerWidth=function(){return this.element==this.element.window?o.viewportWidth():this.adapter.innerWidth()},e.prototype.destroy=function(){var t=[];for(var e in this.waypoints)for(var i in this.waypoints[e])t.push(this.waypoints[e][i]);for(var n=0,o=t.length;o>n;n++)t[n].destroy()},e.prototype.refresh=function(){var t,e=this.element==this.element.window,i=e?void 0:this.adapter.offset(),n={};this.handleScroll(),t={horizontal:{contextOffset:e?0:i.left,contextScroll:e?0:this.oldScroll.x,contextDimension:this.innerWidth(),oldScroll:this.oldScroll.x,forward:"right",backward:"left",offsetProp:"left"},vertical:{contextOffset:e?0:i.top,contextScroll:e?0:this.oldScroll.y,contextDimension:this.innerHeight(),oldScroll:this.oldScroll.y,forward:"down",backward:"up",offsetProp:"top"}};for(var r in t){var s=t[r];for(var l in this.waypoints[r]){var a,h,p,u,d,f=this.waypoints[r][l],c=f.options.offset,w=f.triggerPoint,y=0,g=null==w;f.element!==f.element.window&&(y=f.adapter.offset()[s.offsetProp]),"function"==typeof c?c=c.apply(f):"string"==typeof c&&(c=parseFloat(c),f.options.offset.indexOf("%")>-1&&(c=Math.ceil(s.contextDimension*c/100))),a=s.contextScroll-s.contextOffset,f.triggerPoint=Math.floor(y+a-c),h=w=s.oldScroll,u=h&&p,d=!h&&!p,!g&&u?(f.queueTrigger(s.backward),n[f.group.id]=f.group):!g&&d?(f.queueTrigger(s.forward),n[f.group.id]=f.group):g&&s.oldScroll>=f.triggerPoint&&(f.queueTrigger(s.forward),n[f.group.id]=f.group)}}return o.requestAnimationFrame(function(){for(var t in n)n[t].flushTriggers()}),this},e.findOrCreateByElement=function(t){return e.findByElement(t)||new e(t)},e.refreshAll=function(){for(var t in n)n[t].refresh()},e.findByElement=function(t){return n[t.waypointContextKey]},window.onload=function(){r&&r(),e.refreshAll()},o.requestAnimationFrame=function(e){var i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||t;i.call(window,e)},o.Context=e}(),function(){"use strict";function t(t,e){return t.triggerPoint-e.triggerPoint}function e(t,e){return e.triggerPoint-t.triggerPoint}function i(t){this.name=t.name,this.axis=t.axis,this.id=this.name+"-"+this.axis,this.waypoints=[],this.clearTriggerQueues(),n[this.axis][this.name]=this}var n={vertical:{},horizontal:{}},o=window.Waypoint;i.prototype.add=function(t){this.waypoints.push(t)},i.prototype.clearTriggerQueues=function(){this.triggerQueues={up:[],down:[],left:[],right:[]}},i.prototype.flushTriggers=function(){for(var i in this.triggerQueues){var n=this.triggerQueues[i],o="up"===i||"left"===i;n.sort(o?e:t);for(var r=0,s=n.length;s>r;r+=1){var l=n[r];(l.options.continuous||r===n.length-1)&&l.trigger([i])}}this.clearTriggerQueues()},i.prototype.next=function(e){this.waypoints.sort(t);var i=o.Adapter.inArray(e,this.waypoints),n=i===this.waypoints.length-1;return n?null:this.waypoints[i+1]},i.prototype.previous=function(e){this.waypoints.sort(t);var i=o.Adapter.inArray(e,this.waypoints);return i?this.waypoints[i-1]:null},i.prototype.queueTrigger=function(t,e){this.triggerQueues[e].push(t)},i.prototype.remove=function(t){var e=o.Adapter.inArray(t,this.waypoints);e>-1&&this.waypoints.splice(e,1)},i.prototype.first=function(){return this.waypoints[0]},i.prototype.last=function(){return this.waypoints[this.waypoints.length-1]},i.findOrCreate=function(t){return n[t.axis][t.name]||new i(t)},o.Group=i}(),function(){"use strict";function t(t){return t===t.window}function e(e){return t(e)?e:e.defaultView}function i(t){this.element=t,this.handlers={}}var n=window.Waypoint;i.prototype.innerHeight=function(){var e=t(this.element);return e?this.element.innerHeight:this.element.clientHeight},i.prototype.innerWidth=function(){var e=t(this.element);return e?this.element.innerWidth:this.element.clientWidth},i.prototype.off=function(t,e){function i(t,e,i){for(var n=0,o=e.length-1;o>n;n++){var r=e[n];i&&i!==r||t.removeEventListener(r)}}var n=t.split("."),o=n[0],r=n[1],s=this.element;if(r&&this.handlers[r]&&o)i(s,this.handlers[r][o],e),this.handlers[r][o]=[];else if(o)for(var l in this.handlers)i(s,this.handlers[l][o]||[],e),this.handlers[l][o]=[];else if(r&&this.handlers[r]){for(var a in this.handlers[r])i(s,this.handlers[r][a],e);this.handlers[r]={}}},i.prototype.offset=function(){if(!this.element.ownerDocument)return null;var t=this.element.ownerDocument.documentElement,i=e(this.element.ownerDocument),n={top:0,left:0};return this.element.getBoundingClientRect&&(n=this.element.getBoundingClientRect()),{top:n.top+i.pageYOffset-t.clientTop,left:n.left+i.pageXOffset-t.clientLeft}},i.prototype.on=function(t,e){var i=t.split("."),n=i[0],o=i[1]||"__default",r=this.handlers[o]=this.handlers[o]||{},s=r[n]=r[n]||[];s.push(e),this.element.addEventListener(n,e)},i.prototype.outerHeight=function(e){var i,n=this.innerHeight();return e&&!t(this.element)&&(i=window.getComputedStyle(this.element),n+=parseInt(i.marginTop,10),n+=parseInt(i.marginBottom,10)),n},i.prototype.outerWidth=function(e){var i,n=this.innerWidth();return e&&!t(this.element)&&(i=window.getComputedStyle(this.element),n+=parseInt(i.marginLeft,10),n+=parseInt(i.marginRight,10)),n},i.prototype.scrollLeft=function(){var t=e(this.element);return t?t.pageXOffset:this.element.scrollLeft},i.prototype.scrollTop=function(){var t=e(this.element);return t?t.pageYOffset:this.element.scrollTop},i.extend=function(){function t(t,e){if("object"==typeof t&&"object"==typeof e)for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t}for(var e=Array.prototype.slice.call(arguments),i=1,n=e.length;n>i;i++)t(e[0],e[i]);return e[0]},i.inArray=function(t,e,i){return null==e?-1:e.indexOf(t,i)},i.isEmptyObject=function(t){for(var e in t)return!1;return!0},n.adapters.push({name:"noframework",Adapter:i}),n.Adapter=i}(); -------------------------------------------------------------------------------- /lib/shortcuts/infinite.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints Infinite Scroll Shortcut - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | (function() { 8 | 'use strict' 9 | 10 | var $ = window.jQuery 11 | var Waypoint = window.Waypoint 12 | 13 | /* http://imakewebthings.com/waypoints/shortcuts/infinite-scroll */ 14 | function Infinite(options) { 15 | this.options = $.extend({}, Infinite.defaults, options) 16 | this.container = this.options.element 17 | if (this.options.container !== 'auto') { 18 | this.container = this.options.container 19 | } 20 | this.$container = $(this.container) 21 | this.$more = $(this.options.more) 22 | 23 | if (this.$more.length) { 24 | this.setupHandler() 25 | this.waypoint = new Waypoint(this.options) 26 | } 27 | } 28 | 29 | /* Private */ 30 | Infinite.prototype.setupHandler = function() { 31 | this.options.handler = $.proxy(function() { 32 | this.options.onBeforePageLoad() 33 | this.destroy() 34 | this.$container.addClass(this.options.loadingClass) 35 | 36 | $.get($(this.options.more).attr('href'), $.proxy(function(data) { 37 | var $data = $($.parseHTML(data)) 38 | var $newMore = $data.find(this.options.more) 39 | 40 | var $items = $data.find(this.options.items) 41 | if (!$items.length) { 42 | $items = $data.filter(this.options.items) 43 | } 44 | 45 | this.$container.append($items) 46 | this.$container.removeClass(this.options.loadingClass) 47 | 48 | if (!$newMore.length) { 49 | $newMore = $data.filter(this.options.more) 50 | } 51 | if ($newMore.length) { 52 | this.$more.replaceWith($newMore) 53 | this.$more = $newMore 54 | this.waypoint = new Waypoint(this.options) 55 | } 56 | else { 57 | this.$more.remove() 58 | } 59 | 60 | this.options.onAfterPageLoad($items) 61 | }, this)) 62 | }, this) 63 | } 64 | 65 | /* Public */ 66 | Infinite.prototype.destroy = function() { 67 | if (this.waypoint) { 68 | this.waypoint.destroy() 69 | } 70 | } 71 | 72 | Infinite.defaults = { 73 | container: 'auto', 74 | items: '.infinite-item', 75 | more: '.infinite-more-link', 76 | offset: 'bottom-in-view', 77 | loadingClass: 'infinite-loading', 78 | onBeforePageLoad: $.noop, 79 | onAfterPageLoad: $.noop 80 | } 81 | 82 | Waypoint.Infinite = Infinite 83 | }()) 84 | ; -------------------------------------------------------------------------------- /lib/shortcuts/infinite.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints Infinite Scroll Shortcut - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | !function(){"use strict";function t(n){this.options=i.extend({},t.defaults,n),this.container=this.options.element,"auto"!==this.options.container&&(this.container=this.options.container),this.$container=i(this.container),this.$more=i(this.options.more),this.$more.length&&(this.setupHandler(),this.waypoint=new o(this.options))}var i=window.jQuery,o=window.Waypoint;t.prototype.setupHandler=function(){this.options.handler=i.proxy(function(){this.options.onBeforePageLoad(),this.destroy(),this.$container.addClass(this.options.loadingClass),i.get(i(this.options.more).attr("href"),i.proxy(function(t){var n=i(i.parseHTML(t)),e=n.find(this.options.more),s=n.find(this.options.items);s.length||(s=n.filter(this.options.items)),this.$container.append(s),this.$container.removeClass(this.options.loadingClass),e.length||(e=n.filter(this.options.more)),e.length?(this.$more.replaceWith(e),this.$more=e,this.waypoint=new o(this.options)):this.$more.remove(),this.options.onAfterPageLoad(s)},this))},this)},t.prototype.destroy=function(){this.waypoint&&this.waypoint.destroy()},t.defaults={container:"auto",items:".infinite-item",more:".infinite-more-link",offset:"bottom-in-view",loadingClass:"infinite-loading",onBeforePageLoad:i.noop,onAfterPageLoad:i.noop},o.Infinite=t}(); -------------------------------------------------------------------------------- /lib/shortcuts/inview.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints Inview Shortcut - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | (function() { 8 | 'use strict' 9 | 10 | function noop() {} 11 | 12 | var Waypoint = window.Waypoint 13 | 14 | /* http://imakewebthings.com/waypoints/shortcuts/inview */ 15 | function Inview(options) { 16 | this.options = Waypoint.Adapter.extend({}, Inview.defaults, options) 17 | this.axis = this.options.horizontal ? 'horizontal' : 'vertical' 18 | this.waypoints = [] 19 | this.element = this.options.element 20 | this.createWaypoints() 21 | } 22 | 23 | /* Private */ 24 | Inview.prototype.createWaypoints = function() { 25 | var configs = { 26 | vertical: [{ 27 | down: 'enter', 28 | up: 'exited', 29 | offset: '100%' 30 | }, { 31 | down: 'entered', 32 | up: 'exit', 33 | offset: 'bottom-in-view' 34 | }, { 35 | down: 'exit', 36 | up: 'entered', 37 | offset: 0 38 | }, { 39 | down: 'exited', 40 | up: 'enter', 41 | offset: function() { 42 | return -this.adapter.outerHeight() 43 | } 44 | }], 45 | horizontal: [{ 46 | right: 'enter', 47 | left: 'exited', 48 | offset: '100%' 49 | }, { 50 | right: 'entered', 51 | left: 'exit', 52 | offset: 'right-in-view' 53 | }, { 54 | right: 'exit', 55 | left: 'entered', 56 | offset: 0 57 | }, { 58 | right: 'exited', 59 | left: 'enter', 60 | offset: function() { 61 | return -this.adapter.outerWidth() 62 | } 63 | }] 64 | } 65 | 66 | for (var i = 0, end = configs[this.axis].length; i < end; i++) { 67 | var config = configs[this.axis][i] 68 | this.createWaypoint(config) 69 | } 70 | } 71 | 72 | /* Private */ 73 | Inview.prototype.createWaypoint = function(config) { 74 | var self = this 75 | this.waypoints.push(new Waypoint({ 76 | context: this.options.context, 77 | element: this.options.element, 78 | enabled: this.options.enabled, 79 | handler: (function(config) { 80 | return function(direction) { 81 | self.options[config[direction]].call(self, direction) 82 | } 83 | }(config)), 84 | offset: config.offset, 85 | horizontal: this.options.horizontal 86 | })) 87 | } 88 | 89 | /* Public */ 90 | Inview.prototype.destroy = function() { 91 | for (var i = 0, end = this.waypoints.length; i < end; i++) { 92 | this.waypoints[i].destroy() 93 | } 94 | this.waypoints = [] 95 | } 96 | 97 | Inview.prototype.disable = function() { 98 | for (var i = 0, end = this.waypoints.length; i < end; i++) { 99 | this.waypoints[i].disable() 100 | } 101 | } 102 | 103 | Inview.prototype.enable = function() { 104 | for (var i = 0, end = this.waypoints.length; i < end; i++) { 105 | this.waypoints[i].enable() 106 | } 107 | } 108 | 109 | Inview.defaults = { 110 | context: window, 111 | enabled: true, 112 | enter: noop, 113 | entered: noop, 114 | exit: noop, 115 | exited: noop 116 | } 117 | 118 | Waypoint.Inview = Inview 119 | }()) 120 | ; -------------------------------------------------------------------------------- /lib/shortcuts/inview.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints Inview Shortcut - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | !function(){"use strict";function t(){}function e(t){this.options=i.Adapter.extend({},e.defaults,t),this.axis=this.options.horizontal?"horizontal":"vertical",this.waypoints=[],this.element=this.options.element,this.createWaypoints()}var i=window.Waypoint;e.prototype.createWaypoints=function(){for(var t={vertical:[{down:"enter",up:"exited",offset:"100%"},{down:"entered",up:"exit",offset:"bottom-in-view"},{down:"exit",up:"entered",offset:0},{down:"exited",up:"enter",offset:function(){return-this.adapter.outerHeight()}}],horizontal:[{right:"enter",left:"exited",offset:"100%"},{right:"entered",left:"exit",offset:"right-in-view"},{right:"exit",left:"entered",offset:0},{right:"exited",left:"enter",offset:function(){return-this.adapter.outerWidth()}}]},e=0,i=t[this.axis].length;i>e;e++){var n=t[this.axis][e];this.createWaypoint(n)}},e.prototype.createWaypoint=function(t){var e=this;this.waypoints.push(new i({context:this.options.context,element:this.options.element,enabled:this.options.enabled,handler:function(t){return function(i){e.options[t[i]].call(e,i)}}(t),offset:t.offset,horizontal:this.options.horizontal}))},e.prototype.destroy=function(){for(var t=0,e=this.waypoints.length;e>t;t++)this.waypoints[t].destroy();this.waypoints=[]},e.prototype.disable=function(){for(var t=0,e=this.waypoints.length;e>t;t++)this.waypoints[t].disable()},e.prototype.enable=function(){for(var t=0,e=this.waypoints.length;e>t;t++)this.waypoints[t].enable()},e.defaults={context:window,enabled:!0,enter:t,entered:t,exit:t,exited:t},i.Inview=e}(); -------------------------------------------------------------------------------- /lib/shortcuts/sticky.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints Sticky Element Shortcut - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | (function() { 8 | 'use strict' 9 | 10 | var $ = window.jQuery 11 | var Waypoint = window.Waypoint 12 | 13 | /* http://imakewebthings.com/waypoints/shortcuts/sticky-elements */ 14 | function Sticky(options) { 15 | this.options = $.extend({}, Waypoint.defaults, Sticky.defaults, options) 16 | this.element = this.options.element 17 | this.$element = $(this.element) 18 | this.createWrapper() 19 | this.createWaypoint() 20 | } 21 | 22 | /* Private */ 23 | Sticky.prototype.createWaypoint = function() { 24 | var originalHandler = this.options.handler 25 | 26 | this.waypoint = new Waypoint($.extend({}, this.options, { 27 | element: this.wrapper, 28 | handler: $.proxy(function(direction) { 29 | var shouldBeStuck = this.options.direction.indexOf(direction) > -1 30 | var wrapperHeight = shouldBeStuck ? this.$element.outerHeight(true) : '' 31 | 32 | this.$wrapper.height(wrapperHeight) 33 | this.$element.toggleClass(this.options.stuckClass, shouldBeStuck) 34 | 35 | if (originalHandler) { 36 | originalHandler.call(this, direction) 37 | } 38 | }, this) 39 | })) 40 | } 41 | 42 | /* Private */ 43 | Sticky.prototype.createWrapper = function() { 44 | if (this.options.wrapper) { 45 | this.$element.wrap(this.options.wrapper) 46 | } 47 | this.$wrapper = this.$element.parent() 48 | this.wrapper = this.$wrapper[0] 49 | } 50 | 51 | /* Public */ 52 | Sticky.prototype.destroy = function() { 53 | if (this.$element.parent()[0] === this.wrapper) { 54 | this.waypoint.destroy() 55 | this.$element.removeClass(this.options.stuckClass) 56 | if (this.options.wrapper) { 57 | this.$element.unwrap() 58 | } 59 | } 60 | } 61 | 62 | Sticky.defaults = { 63 | wrapper: '
', 64 | stuckClass: 'stuck', 65 | direction: 'down right' 66 | } 67 | 68 | Waypoint.Sticky = Sticky 69 | }()) 70 | ; -------------------------------------------------------------------------------- /lib/shortcuts/sticky.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints Sticky Element Shortcut - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | !function(){"use strict";function t(s){this.options=e.extend({},i.defaults,t.defaults,s),this.element=this.options.element,this.$element=e(this.element),this.createWrapper(),this.createWaypoint()}var e=window.jQuery,i=window.Waypoint;t.prototype.createWaypoint=function(){var t=this.options.handler;this.waypoint=new i(e.extend({},this.options,{element:this.wrapper,handler:e.proxy(function(e){var i=this.options.direction.indexOf(e)>-1,s=i?this.$element.outerHeight(!0):"";this.$wrapper.height(s),this.$element.toggleClass(this.options.stuckClass,i),t&&t.call(this,e)},this)}))},t.prototype.createWrapper=function(){this.options.wrapper&&this.$element.wrap(this.options.wrapper),this.$wrapper=this.$element.parent(),this.wrapper=this.$wrapper[0]},t.prototype.destroy=function(){this.$element.parent()[0]===this.wrapper&&(this.waypoint.destroy(),this.$element.removeClass(this.options.stuckClass),this.options.wrapper&&this.$element.unwrap())},t.defaults={wrapper:'
',stuckClass:"stuck",direction:"down right"},i.Sticky=t}(); -------------------------------------------------------------------------------- /lib/waypoints.debug.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints Debug - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | (function() { 8 | 'use strict' 9 | 10 | var displayNoneMessage = [ 11 | 'You have a Waypoint element with display none. For more information on ', 12 | 'why this is a bad idea read ', 13 | 'http://imakewebthings.com/waypoints/guides/debugging/#display-none' 14 | ].join('') 15 | var fixedMessage = [ 16 | 'You have a Waypoint element with fixed positioning. For more ', 17 | 'information on why this is a bad idea read ', 18 | 'http://imakewebthings.com/waypoints/guides/debugging/#fixed-position' 19 | ].join('') 20 | 21 | function checkWaypointStyles() { 22 | var originalRefresh = window.Waypoint.Context.prototype.refresh 23 | 24 | window.Waypoint.Context.prototype.refresh = function() { 25 | for (var axis in this.waypoints) { 26 | for (var key in this.waypoints[axis]) { 27 | var waypoint = this.waypoints[axis][key] 28 | var style = window.getComputedStyle(waypoint.element) 29 | if (!waypoint.enabled) { 30 | continue 31 | } 32 | if (style && style.display === 'none') { 33 | console.error(displayNoneMessage) 34 | } 35 | if (style && style.position === 'fixed') { 36 | console.error(fixedMessage) 37 | } 38 | } 39 | } 40 | return originalRefresh.call(this) 41 | } 42 | } 43 | 44 | checkWaypointStyles() 45 | }()) 46 | ; -------------------------------------------------------------------------------- /lib/zepto.waypoints.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | (function() { 8 | 'use strict' 9 | 10 | var keyCounter = 0 11 | var allWaypoints = {} 12 | 13 | /* http://imakewebthings.com/waypoints/api/waypoint */ 14 | function Waypoint(options) { 15 | if (!options) { 16 | throw new Error('No options passed to Waypoint constructor') 17 | } 18 | if (!options.element) { 19 | throw new Error('No element option passed to Waypoint constructor') 20 | } 21 | if (!options.handler) { 22 | throw new Error('No handler option passed to Waypoint constructor') 23 | } 24 | 25 | this.key = 'waypoint-' + keyCounter 26 | this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options) 27 | this.element = this.options.element 28 | this.adapter = new Waypoint.Adapter(this.element) 29 | this.callback = options.handler 30 | this.axis = this.options.horizontal ? 'horizontal' : 'vertical' 31 | this.enabled = this.options.enabled 32 | this.triggerPoint = null 33 | this.group = Waypoint.Group.findOrCreate({ 34 | name: this.options.group, 35 | axis: this.axis 36 | }) 37 | this.context = Waypoint.Context.findOrCreateByElement(this.options.context) 38 | 39 | if (Waypoint.offsetAliases[this.options.offset]) { 40 | this.options.offset = Waypoint.offsetAliases[this.options.offset] 41 | } 42 | this.group.add(this) 43 | this.context.add(this) 44 | allWaypoints[this.key] = this 45 | keyCounter += 1 46 | } 47 | 48 | /* Private */ 49 | Waypoint.prototype.queueTrigger = function(direction) { 50 | this.group.queueTrigger(this, direction) 51 | } 52 | 53 | /* Private */ 54 | Waypoint.prototype.trigger = function(args) { 55 | if (!this.enabled) { 56 | return 57 | } 58 | if (this.callback) { 59 | this.callback.apply(this, args) 60 | } 61 | } 62 | 63 | /* Public */ 64 | /* http://imakewebthings.com/waypoints/api/destroy */ 65 | Waypoint.prototype.destroy = function() { 66 | this.context.remove(this) 67 | this.group.remove(this) 68 | delete allWaypoints[this.key] 69 | } 70 | 71 | /* Public */ 72 | /* http://imakewebthings.com/waypoints/api/disable */ 73 | Waypoint.prototype.disable = function() { 74 | this.enabled = false 75 | return this 76 | } 77 | 78 | /* Public */ 79 | /* http://imakewebthings.com/waypoints/api/enable */ 80 | Waypoint.prototype.enable = function() { 81 | this.context.refresh() 82 | this.enabled = true 83 | return this 84 | } 85 | 86 | /* Public */ 87 | /* http://imakewebthings.com/waypoints/api/next */ 88 | Waypoint.prototype.next = function() { 89 | return this.group.next(this) 90 | } 91 | 92 | /* Public */ 93 | /* http://imakewebthings.com/waypoints/api/previous */ 94 | Waypoint.prototype.previous = function() { 95 | return this.group.previous(this) 96 | } 97 | 98 | /* Private */ 99 | Waypoint.invokeAll = function(method) { 100 | var allWaypointsArray = [] 101 | for (var waypointKey in allWaypoints) { 102 | allWaypointsArray.push(allWaypoints[waypointKey]) 103 | } 104 | for (var i = 0, end = allWaypointsArray.length; i < end; i++) { 105 | allWaypointsArray[i][method]() 106 | } 107 | } 108 | 109 | /* Public */ 110 | /* http://imakewebthings.com/waypoints/api/destroy-all */ 111 | Waypoint.destroyAll = function() { 112 | Waypoint.invokeAll('destroy') 113 | } 114 | 115 | /* Public */ 116 | /* http://imakewebthings.com/waypoints/api/disable-all */ 117 | Waypoint.disableAll = function() { 118 | Waypoint.invokeAll('disable') 119 | } 120 | 121 | /* Public */ 122 | /* http://imakewebthings.com/waypoints/api/enable-all */ 123 | Waypoint.enableAll = function() { 124 | Waypoint.Context.refreshAll() 125 | for (var waypointKey in allWaypoints) { 126 | allWaypoints[waypointKey].enabled = true 127 | } 128 | return this 129 | } 130 | 131 | /* Public */ 132 | /* http://imakewebthings.com/waypoints/api/refresh-all */ 133 | Waypoint.refreshAll = function() { 134 | Waypoint.Context.refreshAll() 135 | } 136 | 137 | /* Public */ 138 | /* http://imakewebthings.com/waypoints/api/viewport-height */ 139 | Waypoint.viewportHeight = function() { 140 | return window.innerHeight || document.documentElement.clientHeight 141 | } 142 | 143 | /* Public */ 144 | /* http://imakewebthings.com/waypoints/api/viewport-width */ 145 | Waypoint.viewportWidth = function() { 146 | return document.documentElement.clientWidth 147 | } 148 | 149 | Waypoint.adapters = [] 150 | 151 | Waypoint.defaults = { 152 | context: window, 153 | continuous: true, 154 | enabled: true, 155 | group: 'default', 156 | horizontal: false, 157 | offset: 0 158 | } 159 | 160 | Waypoint.offsetAliases = { 161 | 'bottom-in-view': function() { 162 | return this.context.innerHeight() - this.adapter.outerHeight() 163 | }, 164 | 'right-in-view': function() { 165 | return this.context.innerWidth() - this.adapter.outerWidth() 166 | } 167 | } 168 | 169 | window.Waypoint = Waypoint 170 | }()) 171 | ;(function() { 172 | 'use strict' 173 | 174 | function requestAnimationFrameShim(callback) { 175 | window.setTimeout(callback, 1000 / 60) 176 | } 177 | 178 | var keyCounter = 0 179 | var contexts = {} 180 | var Waypoint = window.Waypoint 181 | var oldWindowLoad = window.onload 182 | 183 | /* http://imakewebthings.com/waypoints/api/context */ 184 | function Context(element) { 185 | this.element = element 186 | this.Adapter = Waypoint.Adapter 187 | this.adapter = new this.Adapter(element) 188 | this.key = 'waypoint-context-' + keyCounter 189 | this.didScroll = false 190 | this.didResize = false 191 | this.oldScroll = { 192 | x: this.adapter.scrollLeft(), 193 | y: this.adapter.scrollTop() 194 | } 195 | this.waypoints = { 196 | vertical: {}, 197 | horizontal: {} 198 | } 199 | 200 | element.waypointContextKey = this.key 201 | contexts[element.waypointContextKey] = this 202 | keyCounter += 1 203 | if (!Waypoint.windowContext) { 204 | Waypoint.windowContext = true 205 | Waypoint.windowContext = new Context(window) 206 | } 207 | 208 | this.createThrottledScrollHandler() 209 | this.createThrottledResizeHandler() 210 | } 211 | 212 | /* Private */ 213 | Context.prototype.add = function(waypoint) { 214 | var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical' 215 | this.waypoints[axis][waypoint.key] = waypoint 216 | this.refresh() 217 | } 218 | 219 | /* Private */ 220 | Context.prototype.checkEmpty = function() { 221 | var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal) 222 | var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical) 223 | var isWindow = this.element == this.element.window 224 | if (horizontalEmpty && verticalEmpty && !isWindow) { 225 | this.adapter.off('.waypoints') 226 | delete contexts[this.key] 227 | } 228 | } 229 | 230 | /* Private */ 231 | Context.prototype.createThrottledResizeHandler = function() { 232 | var self = this 233 | 234 | function resizeHandler() { 235 | self.handleResize() 236 | self.didResize = false 237 | } 238 | 239 | this.adapter.on('resize.waypoints', function() { 240 | if (!self.didResize) { 241 | self.didResize = true 242 | Waypoint.requestAnimationFrame(resizeHandler) 243 | } 244 | }) 245 | } 246 | 247 | /* Private */ 248 | Context.prototype.createThrottledScrollHandler = function() { 249 | var self = this 250 | function scrollHandler() { 251 | self.handleScroll() 252 | self.didScroll = false 253 | } 254 | 255 | this.adapter.on('scroll.waypoints', function() { 256 | if (!self.didScroll || Waypoint.isTouch) { 257 | self.didScroll = true 258 | Waypoint.requestAnimationFrame(scrollHandler) 259 | } 260 | }) 261 | } 262 | 263 | /* Private */ 264 | Context.prototype.handleResize = function() { 265 | Waypoint.Context.refreshAll() 266 | } 267 | 268 | /* Private */ 269 | Context.prototype.handleScroll = function() { 270 | var triggeredGroups = {} 271 | var axes = { 272 | horizontal: { 273 | newScroll: this.adapter.scrollLeft(), 274 | oldScroll: this.oldScroll.x, 275 | forward: 'right', 276 | backward: 'left' 277 | }, 278 | vertical: { 279 | newScroll: this.adapter.scrollTop(), 280 | oldScroll: this.oldScroll.y, 281 | forward: 'down', 282 | backward: 'up' 283 | } 284 | } 285 | 286 | for (var axisKey in axes) { 287 | var axis = axes[axisKey] 288 | var isForward = axis.newScroll > axis.oldScroll 289 | var direction = isForward ? axis.forward : axis.backward 290 | 291 | for (var waypointKey in this.waypoints[axisKey]) { 292 | var waypoint = this.waypoints[axisKey][waypointKey] 293 | if (waypoint.triggerPoint === null) { 294 | continue 295 | } 296 | var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint 297 | var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint 298 | var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint 299 | var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint 300 | if (crossedForward || crossedBackward) { 301 | waypoint.queueTrigger(direction) 302 | triggeredGroups[waypoint.group.id] = waypoint.group 303 | } 304 | } 305 | } 306 | 307 | for (var groupKey in triggeredGroups) { 308 | triggeredGroups[groupKey].flushTriggers() 309 | } 310 | 311 | this.oldScroll = { 312 | x: axes.horizontal.newScroll, 313 | y: axes.vertical.newScroll 314 | } 315 | } 316 | 317 | /* Private */ 318 | Context.prototype.innerHeight = function() { 319 | /*eslint-disable eqeqeq */ 320 | if (this.element == this.element.window) { 321 | return Waypoint.viewportHeight() 322 | } 323 | /*eslint-enable eqeqeq */ 324 | return this.adapter.innerHeight() 325 | } 326 | 327 | /* Private */ 328 | Context.prototype.remove = function(waypoint) { 329 | delete this.waypoints[waypoint.axis][waypoint.key] 330 | this.checkEmpty() 331 | } 332 | 333 | /* Private */ 334 | Context.prototype.innerWidth = function() { 335 | /*eslint-disable eqeqeq */ 336 | if (this.element == this.element.window) { 337 | return Waypoint.viewportWidth() 338 | } 339 | /*eslint-enable eqeqeq */ 340 | return this.adapter.innerWidth() 341 | } 342 | 343 | /* Public */ 344 | /* http://imakewebthings.com/waypoints/api/context-destroy */ 345 | Context.prototype.destroy = function() { 346 | var allWaypoints = [] 347 | for (var axis in this.waypoints) { 348 | for (var waypointKey in this.waypoints[axis]) { 349 | allWaypoints.push(this.waypoints[axis][waypointKey]) 350 | } 351 | } 352 | for (var i = 0, end = allWaypoints.length; i < end; i++) { 353 | allWaypoints[i].destroy() 354 | } 355 | } 356 | 357 | /* Public */ 358 | /* http://imakewebthings.com/waypoints/api/context-refresh */ 359 | Context.prototype.refresh = function() { 360 | /*eslint-disable eqeqeq */ 361 | var isWindow = this.element == this.element.window 362 | /*eslint-enable eqeqeq */ 363 | var contextOffset = isWindow ? undefined : this.adapter.offset() 364 | var triggeredGroups = {} 365 | var axes 366 | 367 | this.handleScroll() 368 | axes = { 369 | horizontal: { 370 | contextOffset: isWindow ? 0 : contextOffset.left, 371 | contextScroll: isWindow ? 0 : this.oldScroll.x, 372 | contextDimension: this.innerWidth(), 373 | oldScroll: this.oldScroll.x, 374 | forward: 'right', 375 | backward: 'left', 376 | offsetProp: 'left' 377 | }, 378 | vertical: { 379 | contextOffset: isWindow ? 0 : contextOffset.top, 380 | contextScroll: isWindow ? 0 : this.oldScroll.y, 381 | contextDimension: this.innerHeight(), 382 | oldScroll: this.oldScroll.y, 383 | forward: 'down', 384 | backward: 'up', 385 | offsetProp: 'top' 386 | } 387 | } 388 | 389 | for (var axisKey in axes) { 390 | var axis = axes[axisKey] 391 | for (var waypointKey in this.waypoints[axisKey]) { 392 | var waypoint = this.waypoints[axisKey][waypointKey] 393 | var adjustment = waypoint.options.offset 394 | var oldTriggerPoint = waypoint.triggerPoint 395 | var elementOffset = 0 396 | var freshWaypoint = oldTriggerPoint == null 397 | var contextModifier, wasBeforeScroll, nowAfterScroll 398 | var triggeredBackward, triggeredForward 399 | 400 | if (waypoint.element !== waypoint.element.window) { 401 | elementOffset = waypoint.adapter.offset()[axis.offsetProp] 402 | } 403 | 404 | if (typeof adjustment === 'function') { 405 | adjustment = adjustment.apply(waypoint) 406 | } 407 | else if (typeof adjustment === 'string') { 408 | adjustment = parseFloat(adjustment) 409 | if (waypoint.options.offset.indexOf('%') > - 1) { 410 | adjustment = Math.ceil(axis.contextDimension * adjustment / 100) 411 | } 412 | } 413 | 414 | contextModifier = axis.contextScroll - axis.contextOffset 415 | waypoint.triggerPoint = Math.floor(elementOffset + contextModifier - adjustment) 416 | wasBeforeScroll = oldTriggerPoint < axis.oldScroll 417 | nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll 418 | triggeredBackward = wasBeforeScroll && nowAfterScroll 419 | triggeredForward = !wasBeforeScroll && !nowAfterScroll 420 | 421 | if (!freshWaypoint && triggeredBackward) { 422 | waypoint.queueTrigger(axis.backward) 423 | triggeredGroups[waypoint.group.id] = waypoint.group 424 | } 425 | else if (!freshWaypoint && triggeredForward) { 426 | waypoint.queueTrigger(axis.forward) 427 | triggeredGroups[waypoint.group.id] = waypoint.group 428 | } 429 | else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) { 430 | waypoint.queueTrigger(axis.forward) 431 | triggeredGroups[waypoint.group.id] = waypoint.group 432 | } 433 | } 434 | } 435 | 436 | Waypoint.requestAnimationFrame(function() { 437 | for (var groupKey in triggeredGroups) { 438 | triggeredGroups[groupKey].flushTriggers() 439 | } 440 | }) 441 | 442 | return this 443 | } 444 | 445 | /* Private */ 446 | Context.findOrCreateByElement = function(element) { 447 | return Context.findByElement(element) || new Context(element) 448 | } 449 | 450 | /* Private */ 451 | Context.refreshAll = function() { 452 | for (var contextId in contexts) { 453 | contexts[contextId].refresh() 454 | } 455 | } 456 | 457 | /* Public */ 458 | /* http://imakewebthings.com/waypoints/api/context-find-by-element */ 459 | Context.findByElement = function(element) { 460 | return contexts[element.waypointContextKey] 461 | } 462 | 463 | window.onload = function() { 464 | if (oldWindowLoad) { 465 | oldWindowLoad() 466 | } 467 | Context.refreshAll() 468 | } 469 | 470 | 471 | Waypoint.requestAnimationFrame = function(callback) { 472 | var requestFn = window.requestAnimationFrame || 473 | window.mozRequestAnimationFrame || 474 | window.webkitRequestAnimationFrame || 475 | requestAnimationFrameShim 476 | requestFn.call(window, callback) 477 | } 478 | Waypoint.Context = Context 479 | }()) 480 | ;(function() { 481 | 'use strict' 482 | 483 | function byTriggerPoint(a, b) { 484 | return a.triggerPoint - b.triggerPoint 485 | } 486 | 487 | function byReverseTriggerPoint(a, b) { 488 | return b.triggerPoint - a.triggerPoint 489 | } 490 | 491 | var groups = { 492 | vertical: {}, 493 | horizontal: {} 494 | } 495 | var Waypoint = window.Waypoint 496 | 497 | /* http://imakewebthings.com/waypoints/api/group */ 498 | function Group(options) { 499 | this.name = options.name 500 | this.axis = options.axis 501 | this.id = this.name + '-' + this.axis 502 | this.waypoints = [] 503 | this.clearTriggerQueues() 504 | groups[this.axis][this.name] = this 505 | } 506 | 507 | /* Private */ 508 | Group.prototype.add = function(waypoint) { 509 | this.waypoints.push(waypoint) 510 | } 511 | 512 | /* Private */ 513 | Group.prototype.clearTriggerQueues = function() { 514 | this.triggerQueues = { 515 | up: [], 516 | down: [], 517 | left: [], 518 | right: [] 519 | } 520 | } 521 | 522 | /* Private */ 523 | Group.prototype.flushTriggers = function() { 524 | for (var direction in this.triggerQueues) { 525 | var waypoints = this.triggerQueues[direction] 526 | var reverse = direction === 'up' || direction === 'left' 527 | waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint) 528 | for (var i = 0, end = waypoints.length; i < end; i += 1) { 529 | var waypoint = waypoints[i] 530 | if (waypoint.options.continuous || i === waypoints.length - 1) { 531 | waypoint.trigger([direction]) 532 | } 533 | } 534 | } 535 | this.clearTriggerQueues() 536 | } 537 | 538 | /* Private */ 539 | Group.prototype.next = function(waypoint) { 540 | this.waypoints.sort(byTriggerPoint) 541 | var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) 542 | var isLast = index === this.waypoints.length - 1 543 | return isLast ? null : this.waypoints[index + 1] 544 | } 545 | 546 | /* Private */ 547 | Group.prototype.previous = function(waypoint) { 548 | this.waypoints.sort(byTriggerPoint) 549 | var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) 550 | return index ? this.waypoints[index - 1] : null 551 | } 552 | 553 | /* Private */ 554 | Group.prototype.queueTrigger = function(waypoint, direction) { 555 | this.triggerQueues[direction].push(waypoint) 556 | } 557 | 558 | /* Private */ 559 | Group.prototype.remove = function(waypoint) { 560 | var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) 561 | if (index > -1) { 562 | this.waypoints.splice(index, 1) 563 | } 564 | } 565 | 566 | /* Public */ 567 | /* http://imakewebthings.com/waypoints/api/first */ 568 | Group.prototype.first = function() { 569 | return this.waypoints[0] 570 | } 571 | 572 | /* Public */ 573 | /* http://imakewebthings.com/waypoints/api/last */ 574 | Group.prototype.last = function() { 575 | return this.waypoints[this.waypoints.length - 1] 576 | } 577 | 578 | /* Private */ 579 | Group.findOrCreate = function(options) { 580 | return groups[options.axis][options.name] || new Group(options) 581 | } 582 | 583 | Waypoint.Group = Group 584 | }()) 585 | ;(function() { 586 | 'use strict' 587 | 588 | var $ = window.Zepto 589 | var Waypoint = window.Waypoint 590 | 591 | function ZeptoAdapter(element) { 592 | this.element = element 593 | this.$element = $(element) 594 | } 595 | 596 | $.each([ 597 | 'off', 598 | 'on', 599 | 'scrollLeft', 600 | 'scrollTop' 601 | ], function(i, method) { 602 | ZeptoAdapter.prototype[method] = function() { 603 | var args = Array.prototype.slice.call(arguments) 604 | return this.$element[method].apply(this.$element, args) 605 | } 606 | }) 607 | 608 | ZeptoAdapter.prototype.offset = function() { 609 | if (this.element !== this.element.window) { 610 | return this.$element.offset() 611 | } 612 | } 613 | 614 | // Adapted from https://gist.github.com/wheresrhys/5823198 615 | $.each([ 616 | 'width', 617 | 'height' 618 | ], function(i, dimension) { 619 | function createDimensionMethod($element, includeBorder) { 620 | return function(includeMargin) { 621 | var $element = this.$element 622 | var size = $element[dimension]() 623 | var sides = { 624 | width: ['left', 'right'], 625 | height: ['top', 'bottom'] 626 | } 627 | 628 | $.each(sides[dimension], function(i, side) { 629 | size += parseInt($element.css('padding-' + side), 10) 630 | if (includeBorder) { 631 | size += parseInt($element.css('border-' + side + '-width'), 10) 632 | } 633 | if (includeMargin) { 634 | size += parseInt($element.css('margin-' + side), 10) 635 | } 636 | }) 637 | return size 638 | } 639 | } 640 | 641 | var innerMethod = $.camelCase('inner-' + dimension) 642 | var outerMethod = $.camelCase('outer-' + dimension) 643 | 644 | ZeptoAdapter.prototype[innerMethod] = createDimensionMethod(false) 645 | ZeptoAdapter.prototype[outerMethod] = createDimensionMethod(true) 646 | }) 647 | 648 | $.each([ 649 | 'extend', 650 | 'inArray' 651 | ], function(i, method) { 652 | ZeptoAdapter[method] = $[method] 653 | }) 654 | 655 | ZeptoAdapter.isEmptyObject = function(obj) { 656 | /* eslint no-unused-vars: 0 */ 657 | for (var name in obj) { 658 | return false 659 | } 660 | return true 661 | } 662 | 663 | Waypoint.adapters.push({ 664 | name: 'zepto', 665 | Adapter: ZeptoAdapter 666 | }) 667 | Waypoint.Adapter = ZeptoAdapter 668 | }()) 669 | ;(function() { 670 | 'use strict' 671 | 672 | var Waypoint = window.Waypoint 673 | 674 | function createExtension(framework) { 675 | return function() { 676 | var waypoints = [] 677 | var overrides = arguments[0] 678 | 679 | if (framework.isFunction(arguments[0])) { 680 | overrides = framework.extend({}, arguments[1]) 681 | overrides.handler = arguments[0] 682 | } 683 | 684 | this.each(function() { 685 | var options = framework.extend({}, overrides, { 686 | element: this 687 | }) 688 | if (typeof options.context === 'string') { 689 | options.context = framework(this).closest(options.context)[0] 690 | } 691 | waypoints.push(new Waypoint(options)) 692 | }) 693 | 694 | return waypoints 695 | } 696 | } 697 | 698 | if (window.jQuery) { 699 | window.jQuery.fn.waypoint = createExtension(window.jQuery) 700 | } 701 | if (window.Zepto) { 702 | window.Zepto.fn.waypoint = createExtension(window.Zepto) 703 | } 704 | }()) 705 | ; -------------------------------------------------------------------------------- /lib/zepto.waypoints.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Waypoints - 4.0.1 3 | Copyright © 2011-2016 Caleb Troughton 4 | Licensed under the MIT license. 5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt 6 | */ 7 | !function(){"use strict";function t(o){if(!o)throw new Error("No options passed to Waypoint constructor");if(!o.element)throw new Error("No element option passed to Waypoint constructor");if(!o.handler)throw new Error("No handler option passed to Waypoint constructor");this.key="waypoint-"+e,this.options=t.Adapter.extend({},t.defaults,o),this.element=this.options.element,this.adapter=new t.Adapter(this.element),this.callback=o.handler,this.axis=this.options.horizontal?"horizontal":"vertical",this.enabled=this.options.enabled,this.triggerPoint=null,this.group=t.Group.findOrCreate({name:this.options.group,axis:this.axis}),this.context=t.Context.findOrCreateByElement(this.options.context),t.offsetAliases[this.options.offset]&&(this.options.offset=t.offsetAliases[this.options.offset]),this.group.add(this),this.context.add(this),i[this.key]=this,e+=1}var e=0,i={};t.prototype.queueTrigger=function(t){this.group.queueTrigger(this,t)},t.prototype.trigger=function(t){this.enabled&&this.callback&&this.callback.apply(this,t)},t.prototype.destroy=function(){this.context.remove(this),this.group.remove(this),delete i[this.key]},t.prototype.disable=function(){return this.enabled=!1,this},t.prototype.enable=function(){return this.context.refresh(),this.enabled=!0,this},t.prototype.next=function(){return this.group.next(this)},t.prototype.previous=function(){return this.group.previous(this)},t.invokeAll=function(t){var e=[];for(var o in i)e.push(i[o]);for(var n=0,r=e.length;r>n;n++)e[n][t]()},t.destroyAll=function(){t.invokeAll("destroy")},t.disableAll=function(){t.invokeAll("disable")},t.enableAll=function(){t.Context.refreshAll();for(var e in i)i[e].enabled=!0;return this},t.refreshAll=function(){t.Context.refreshAll()},t.viewportHeight=function(){return window.innerHeight||document.documentElement.clientHeight},t.viewportWidth=function(){return document.documentElement.clientWidth},t.adapters=[],t.defaults={context:window,continuous:!0,enabled:!0,group:"default",horizontal:!1,offset:0},t.offsetAliases={"bottom-in-view":function(){return this.context.innerHeight()-this.adapter.outerHeight()},"right-in-view":function(){return this.context.innerWidth()-this.adapter.outerWidth()}},window.Waypoint=t}(),function(){"use strict";function t(t){window.setTimeout(t,1e3/60)}function e(t){this.element=t,this.Adapter=n.Adapter,this.adapter=new this.Adapter(t),this.key="waypoint-context-"+i,this.didScroll=!1,this.didResize=!1,this.oldScroll={x:this.adapter.scrollLeft(),y:this.adapter.scrollTop()},this.waypoints={vertical:{},horizontal:{}},t.waypointContextKey=this.key,o[t.waypointContextKey]=this,i+=1,n.windowContext||(n.windowContext=!0,n.windowContext=new e(window)),this.createThrottledScrollHandler(),this.createThrottledResizeHandler()}var i=0,o={},n=window.Waypoint,r=window.onload;e.prototype.add=function(t){var e=t.options.horizontal?"horizontal":"vertical";this.waypoints[e][t.key]=t,this.refresh()},e.prototype.checkEmpty=function(){var t=this.Adapter.isEmptyObject(this.waypoints.horizontal),e=this.Adapter.isEmptyObject(this.waypoints.vertical),i=this.element==this.element.window;t&&e&&!i&&(this.adapter.off(".waypoints"),delete o[this.key])},e.prototype.createThrottledResizeHandler=function(){function t(){e.handleResize(),e.didResize=!1}var e=this;this.adapter.on("resize.waypoints",function(){e.didResize||(e.didResize=!0,n.requestAnimationFrame(t))})},e.prototype.createThrottledScrollHandler=function(){function t(){e.handleScroll(),e.didScroll=!1}var e=this;this.adapter.on("scroll.waypoints",function(){(!e.didScroll||n.isTouch)&&(e.didScroll=!0,n.requestAnimationFrame(t))})},e.prototype.handleResize=function(){n.Context.refreshAll()},e.prototype.handleScroll=function(){var t={},e={horizontal:{newScroll:this.adapter.scrollLeft(),oldScroll:this.oldScroll.x,forward:"right",backward:"left"},vertical:{newScroll:this.adapter.scrollTop(),oldScroll:this.oldScroll.y,forward:"down",backward:"up"}};for(var i in e){var o=e[i],n=o.newScroll>o.oldScroll,r=n?o.forward:o.backward;for(var s in this.waypoints[i]){var a=this.waypoints[i][s];if(null!==a.triggerPoint){var l=o.oldScroll=a.triggerPoint,p=l&&h,u=!l&&!h;(p||u)&&(a.queueTrigger(r),t[a.group.id]=a.group)}}}for(var c in t)t[c].flushTriggers();this.oldScroll={x:e.horizontal.newScroll,y:e.vertical.newScroll}},e.prototype.innerHeight=function(){return this.element==this.element.window?n.viewportHeight():this.adapter.innerHeight()},e.prototype.remove=function(t){delete this.waypoints[t.axis][t.key],this.checkEmpty()},e.prototype.innerWidth=function(){return this.element==this.element.window?n.viewportWidth():this.adapter.innerWidth()},e.prototype.destroy=function(){var t=[];for(var e in this.waypoints)for(var i in this.waypoints[e])t.push(this.waypoints[e][i]);for(var o=0,n=t.length;n>o;o++)t[o].destroy()},e.prototype.refresh=function(){var t,e=this.element==this.element.window,i=e?void 0:this.adapter.offset(),o={};this.handleScroll(),t={horizontal:{contextOffset:e?0:i.left,contextScroll:e?0:this.oldScroll.x,contextDimension:this.innerWidth(),oldScroll:this.oldScroll.x,forward:"right",backward:"left",offsetProp:"left"},vertical:{contextOffset:e?0:i.top,contextScroll:e?0:this.oldScroll.y,contextDimension:this.innerHeight(),oldScroll:this.oldScroll.y,forward:"down",backward:"up",offsetProp:"top"}};for(var r in t){var s=t[r];for(var a in this.waypoints[r]){var l,h,p,u,c,d=this.waypoints[r][a],f=d.options.offset,w=d.triggerPoint,y=0,g=null==w;d.element!==d.element.window&&(y=d.adapter.offset()[s.offsetProp]),"function"==typeof f?f=f.apply(d):"string"==typeof f&&(f=parseFloat(f),d.options.offset.indexOf("%")>-1&&(f=Math.ceil(s.contextDimension*f/100))),l=s.contextScroll-s.contextOffset,d.triggerPoint=Math.floor(y+l-f),h=w=s.oldScroll,u=h&&p,c=!h&&!p,!g&&u?(d.queueTrigger(s.backward),o[d.group.id]=d.group):!g&&c?(d.queueTrigger(s.forward),o[d.group.id]=d.group):g&&s.oldScroll>=d.triggerPoint&&(d.queueTrigger(s.forward),o[d.group.id]=d.group)}}return n.requestAnimationFrame(function(){for(var t in o)o[t].flushTriggers()}),this},e.findOrCreateByElement=function(t){return e.findByElement(t)||new e(t)},e.refreshAll=function(){for(var t in o)o[t].refresh()},e.findByElement=function(t){return o[t.waypointContextKey]},window.onload=function(){r&&r(),e.refreshAll()},n.requestAnimationFrame=function(e){var i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||t;i.call(window,e)},n.Context=e}(),function(){"use strict";function t(t,e){return t.triggerPoint-e.triggerPoint}function e(t,e){return e.triggerPoint-t.triggerPoint}function i(t){this.name=t.name,this.axis=t.axis,this.id=this.name+"-"+this.axis,this.waypoints=[],this.clearTriggerQueues(),o[this.axis][this.name]=this}var o={vertical:{},horizontal:{}},n=window.Waypoint;i.prototype.add=function(t){this.waypoints.push(t)},i.prototype.clearTriggerQueues=function(){this.triggerQueues={up:[],down:[],left:[],right:[]}},i.prototype.flushTriggers=function(){for(var i in this.triggerQueues){var o=this.triggerQueues[i],n="up"===i||"left"===i;o.sort(n?e:t);for(var r=0,s=o.length;s>r;r+=1){var a=o[r];(a.options.continuous||r===o.length-1)&&a.trigger([i])}}this.clearTriggerQueues()},i.prototype.next=function(e){this.waypoints.sort(t);var i=n.Adapter.inArray(e,this.waypoints),o=i===this.waypoints.length-1;return o?null:this.waypoints[i+1]},i.prototype.previous=function(e){this.waypoints.sort(t);var i=n.Adapter.inArray(e,this.waypoints);return i?this.waypoints[i-1]:null},i.prototype.queueTrigger=function(t,e){this.triggerQueues[e].push(t)},i.prototype.remove=function(t){var e=n.Adapter.inArray(t,this.waypoints);e>-1&&this.waypoints.splice(e,1)},i.prototype.first=function(){return this.waypoints[0]},i.prototype.last=function(){return this.waypoints[this.waypoints.length-1]},i.findOrCreate=function(t){return o[t.axis][t.name]||new i(t)},n.Group=i}(),function(){"use strict";function t(t){this.element=t,this.$element=e(t)}var e=window.Zepto,i=window.Waypoint;e.each(["off","on","scrollLeft","scrollTop"],function(e,i){t.prototype[i]=function(){var t=Array.prototype.slice.call(arguments);return this.$element[i].apply(this.$element,t)}}),t.prototype.offset=function(){return this.element!==this.element.window?this.$element.offset():void 0},e.each(["width","height"],function(i,o){function n(t,i){return function(t){var n=this.$element,r=n[o](),s={width:["left","right"],height:["top","bottom"]};return e.each(s[o],function(e,o){r+=parseInt(n.css("padding-"+o),10),i&&(r+=parseInt(n.css("border-"+o+"-width"),10)),t&&(r+=parseInt(n.css("margin-"+o),10))}),r}}var r=e.camelCase("inner-"+o),s=e.camelCase("outer-"+o);t.prototype[r]=n(!1),t.prototype[s]=n(!0)}),e.each(["extend","inArray"],function(i,o){t[o]=e[o]}),t.isEmptyObject=function(t){for(var e in t)return!1;return!0},i.adapters.push({name:"zepto",Adapter:t}),i.Adapter=t}(),function(){"use strict";function t(t){return function(){var i=[],o=arguments[0];return t.isFunction(arguments[0])&&(o=t.extend({},arguments[1]),o.handler=arguments[0]),this.each(function(){var n=t.extend({},o,{element:this});"string"==typeof n.context&&(n.context=t(this).closest(n.context)[0]),i.push(new e(n))}),i}}var e=window.Waypoint;window.jQuery&&(window.jQuery.fn.waypoint=t(window.jQuery)),window.Zepto&&(window.Zepto.fn.waypoint=t(window.Zepto))}(); -------------------------------------------------------------------------------- /licenses.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012 Caleb Troughton 2 | 3 | ----------------------------------------------------------------------- 4 | 5 | The MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "waypoints", 3 | "version": "4.0.1", 4 | "author": "Caleb Troughton (http://imakewebthings.com)", 5 | "description": "Easily execute a function when you scroll to an element", 6 | "scripts": { 7 | "build": "gulp build", 8 | "start": "gulp", 9 | "test": "testem ci", 10 | "tdd": "testem", 11 | "watch": "gulp watch" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/imakewebthings/waypoints.git" 16 | }, 17 | "keywords": [ 18 | "scroll" 19 | ], 20 | "devDependencies": { 21 | "eslint": "^0.6.2", 22 | "gulp": "^3.6.2", 23 | "gulp-concat": "^2.2.0", 24 | "gulp-eslint": "^0.1.7", 25 | "gulp-footer": "^1.0.5", 26 | "gulp-header": "^1.0.2", 27 | "gulp-rename": "^1.2.0", 28 | "gulp-tap": "^0.1.1", 29 | "gulp-uglify": "^0.3.1", 30 | "merge-stream": "^0.1.1", 31 | "testem": "0.6.24" 32 | }, 33 | "license": "MIT" 34 | } 35 | -------------------------------------------------------------------------------- /src/adapters/jquery-zepto-fn-extension.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict' 3 | 4 | var Waypoint = window.Waypoint 5 | 6 | function createExtension(framework) { 7 | return function() { 8 | var waypoints = [] 9 | var overrides = arguments[0] 10 | 11 | if (framework.isFunction(arguments[0])) { 12 | overrides = framework.extend({}, arguments[1]) 13 | overrides.handler = arguments[0] 14 | } 15 | 16 | this.each(function() { 17 | var options = framework.extend({}, overrides, { 18 | element: this 19 | }) 20 | if (typeof options.context === 'string') { 21 | options.context = framework(this).closest(options.context)[0] 22 | } 23 | waypoints.push(new Waypoint(options)) 24 | }) 25 | 26 | return waypoints 27 | } 28 | } 29 | 30 | if (window.jQuery) { 31 | window.jQuery.fn.waypoint = createExtension(window.jQuery) 32 | } 33 | if (window.Zepto) { 34 | window.Zepto.fn.waypoint = createExtension(window.Zepto) 35 | } 36 | }()) 37 | -------------------------------------------------------------------------------- /src/adapters/jquery.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict' 3 | 4 | var $ = window.jQuery 5 | var Waypoint = window.Waypoint 6 | 7 | function JQueryAdapter(element) { 8 | this.$element = $(element) 9 | } 10 | 11 | $.each([ 12 | 'innerHeight', 13 | 'innerWidth', 14 | 'off', 15 | 'offset', 16 | 'on', 17 | 'outerHeight', 18 | 'outerWidth', 19 | 'scrollLeft', 20 | 'scrollTop' 21 | ], function(i, method) { 22 | JQueryAdapter.prototype[method] = function() { 23 | var args = Array.prototype.slice.call(arguments) 24 | return this.$element[method].apply(this.$element, args) 25 | } 26 | }) 27 | 28 | $.each([ 29 | 'extend', 30 | 'inArray', 31 | 'isEmptyObject' 32 | ], function(i, method) { 33 | JQueryAdapter[method] = $[method] 34 | }) 35 | 36 | Waypoint.adapters.push({ 37 | name: 'jquery', 38 | Adapter: JQueryAdapter 39 | }) 40 | Waypoint.Adapter = JQueryAdapter 41 | }()) 42 | -------------------------------------------------------------------------------- /src/adapters/noframework.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict' 3 | 4 | var Waypoint = window.Waypoint 5 | 6 | function isWindow(element) { 7 | return element === element.window 8 | } 9 | 10 | function getWindow(element) { 11 | if (isWindow(element)) { 12 | return element 13 | } 14 | return element.defaultView 15 | } 16 | 17 | function NoFrameworkAdapter(element) { 18 | this.element = element 19 | this.handlers = {} 20 | } 21 | 22 | NoFrameworkAdapter.prototype.innerHeight = function() { 23 | var isWin = isWindow(this.element) 24 | return isWin ? this.element.innerHeight : this.element.clientHeight 25 | } 26 | 27 | NoFrameworkAdapter.prototype.innerWidth = function() { 28 | var isWin = isWindow(this.element) 29 | return isWin ? this.element.innerWidth : this.element.clientWidth 30 | } 31 | 32 | NoFrameworkAdapter.prototype.off = function(event, handler) { 33 | function removeListeners(element, listeners, handler) { 34 | for (var i = 0, end = listeners.length - 1; i < end; i++) { 35 | var listener = listeners[i] 36 | if (!handler || handler === listener) { 37 | element.removeEventListener(listener) 38 | } 39 | } 40 | } 41 | 42 | var eventParts = event.split('.') 43 | var eventType = eventParts[0] 44 | var namespace = eventParts[1] 45 | var element = this.element 46 | 47 | if (namespace && this.handlers[namespace] && eventType) { 48 | removeListeners(element, this.handlers[namespace][eventType], handler) 49 | this.handlers[namespace][eventType] = [] 50 | } 51 | else if (eventType) { 52 | for (var ns in this.handlers) { 53 | removeListeners(element, this.handlers[ns][eventType] || [], handler) 54 | this.handlers[ns][eventType] = [] 55 | } 56 | } 57 | else if (namespace && this.handlers[namespace]) { 58 | for (var type in this.handlers[namespace]) { 59 | removeListeners(element, this.handlers[namespace][type], handler) 60 | } 61 | this.handlers[namespace] = {} 62 | } 63 | } 64 | 65 | /* Adapted from jQuery 1.x offset() */ 66 | NoFrameworkAdapter.prototype.offset = function() { 67 | if (!this.element.ownerDocument) { 68 | return null 69 | } 70 | 71 | var documentElement = this.element.ownerDocument.documentElement 72 | var win = getWindow(this.element.ownerDocument) 73 | var rect = { 74 | top: 0, 75 | left: 0 76 | } 77 | 78 | if (this.element.getBoundingClientRect) { 79 | rect = this.element.getBoundingClientRect() 80 | } 81 | 82 | return { 83 | top: rect.top + win.pageYOffset - documentElement.clientTop, 84 | left: rect.left + win.pageXOffset - documentElement.clientLeft 85 | } 86 | } 87 | 88 | NoFrameworkAdapter.prototype.on = function(event, handler) { 89 | var eventParts = event.split('.') 90 | var eventType = eventParts[0] 91 | var namespace = eventParts[1] || '__default' 92 | var nsHandlers = this.handlers[namespace] = this.handlers[namespace] || {} 93 | var nsTypeList = nsHandlers[eventType] = nsHandlers[eventType] || [] 94 | 95 | nsTypeList.push(handler) 96 | this.element.addEventListener(eventType, handler) 97 | } 98 | 99 | NoFrameworkAdapter.prototype.outerHeight = function(includeMargin) { 100 | var height = this.innerHeight() 101 | var computedStyle 102 | 103 | if (includeMargin && !isWindow(this.element)) { 104 | computedStyle = window.getComputedStyle(this.element) 105 | height += parseInt(computedStyle.marginTop, 10) 106 | height += parseInt(computedStyle.marginBottom, 10) 107 | } 108 | 109 | return height 110 | } 111 | 112 | NoFrameworkAdapter.prototype.outerWidth = function(includeMargin) { 113 | var width = this.innerWidth() 114 | var computedStyle 115 | 116 | if (includeMargin && !isWindow(this.element)) { 117 | computedStyle = window.getComputedStyle(this.element) 118 | width += parseInt(computedStyle.marginLeft, 10) 119 | width += parseInt(computedStyle.marginRight, 10) 120 | } 121 | 122 | return width 123 | } 124 | 125 | NoFrameworkAdapter.prototype.scrollLeft = function() { 126 | var win = getWindow(this.element) 127 | return win ? win.pageXOffset : this.element.scrollLeft 128 | } 129 | 130 | NoFrameworkAdapter.prototype.scrollTop = function() { 131 | var win = getWindow(this.element) 132 | return win ? win.pageYOffset : this.element.scrollTop 133 | } 134 | 135 | NoFrameworkAdapter.extend = function() { 136 | var args = Array.prototype.slice.call(arguments) 137 | 138 | function merge(target, obj) { 139 | if (typeof target === 'object' && typeof obj === 'object') { 140 | for (var key in obj) { 141 | if (obj.hasOwnProperty(key)) { 142 | target[key] = obj[key] 143 | } 144 | } 145 | } 146 | 147 | return target 148 | } 149 | 150 | for (var i = 1, end = args.length; i < end; i++) { 151 | merge(args[0], args[i]) 152 | } 153 | return args[0] 154 | } 155 | 156 | NoFrameworkAdapter.inArray = function(element, array, i) { 157 | return array == null ? -1 : array.indexOf(element, i) 158 | } 159 | 160 | NoFrameworkAdapter.isEmptyObject = function(obj) { 161 | /* eslint no-unused-vars: 0 */ 162 | for (var name in obj) { 163 | return false 164 | } 165 | return true 166 | } 167 | 168 | Waypoint.adapters.push({ 169 | name: 'noframework', 170 | Adapter: NoFrameworkAdapter 171 | }) 172 | Waypoint.Adapter = NoFrameworkAdapter 173 | }()) 174 | -------------------------------------------------------------------------------- /src/adapters/zepto.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict' 3 | 4 | var $ = window.Zepto 5 | var Waypoint = window.Waypoint 6 | 7 | function ZeptoAdapter(element) { 8 | this.element = element 9 | this.$element = $(element) 10 | } 11 | 12 | $.each([ 13 | 'off', 14 | 'on', 15 | 'scrollLeft', 16 | 'scrollTop' 17 | ], function(i, method) { 18 | ZeptoAdapter.prototype[method] = function() { 19 | var args = Array.prototype.slice.call(arguments) 20 | return this.$element[method].apply(this.$element, args) 21 | } 22 | }) 23 | 24 | ZeptoAdapter.prototype.offset = function() { 25 | if (this.element !== this.element.window) { 26 | return this.$element.offset() 27 | } 28 | } 29 | 30 | // Adapted from https://gist.github.com/wheresrhys/5823198 31 | $.each([ 32 | 'width', 33 | 'height' 34 | ], function(i, dimension) { 35 | function createDimensionMethod($element, includeBorder) { 36 | return function(includeMargin) { 37 | var $element = this.$element 38 | var size = $element[dimension]() 39 | var sides = { 40 | width: ['left', 'right'], 41 | height: ['top', 'bottom'] 42 | } 43 | 44 | $.each(sides[dimension], function(i, side) { 45 | size += parseInt($element.css('padding-' + side), 10) 46 | if (includeBorder) { 47 | size += parseInt($element.css('border-' + side + '-width'), 10) 48 | } 49 | if (includeMargin) { 50 | size += parseInt($element.css('margin-' + side), 10) 51 | } 52 | }) 53 | return size 54 | } 55 | } 56 | 57 | var innerMethod = $.camelCase('inner-' + dimension) 58 | var outerMethod = $.camelCase('outer-' + dimension) 59 | 60 | ZeptoAdapter.prototype[innerMethod] = createDimensionMethod(false) 61 | ZeptoAdapter.prototype[outerMethod] = createDimensionMethod(true) 62 | }) 63 | 64 | $.each([ 65 | 'extend', 66 | 'inArray' 67 | ], function(i, method) { 68 | ZeptoAdapter[method] = $[method] 69 | }) 70 | 71 | ZeptoAdapter.isEmptyObject = function(obj) { 72 | /* eslint no-unused-vars: 0 */ 73 | for (var name in obj) { 74 | return false 75 | } 76 | return true 77 | } 78 | 79 | Waypoint.adapters.push({ 80 | name: 'zepto', 81 | Adapter: ZeptoAdapter 82 | }) 83 | Waypoint.Adapter = ZeptoAdapter 84 | }()) 85 | -------------------------------------------------------------------------------- /src/context.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict' 3 | 4 | function requestAnimationFrameShim(callback) { 5 | window.setTimeout(callback, 1000 / 60) 6 | } 7 | 8 | var keyCounter = 0 9 | var contexts = {} 10 | var Waypoint = window.Waypoint 11 | var oldWindowLoad = window.onload 12 | 13 | /* http://imakewebthings.com/waypoints/api/context */ 14 | function Context(element) { 15 | this.element = element 16 | this.Adapter = Waypoint.Adapter 17 | this.adapter = new this.Adapter(element) 18 | this.key = 'waypoint-context-' + keyCounter 19 | this.didScroll = false 20 | this.didResize = false 21 | this.oldScroll = { 22 | x: this.adapter.scrollLeft(), 23 | y: this.adapter.scrollTop() 24 | } 25 | this.waypoints = { 26 | vertical: {}, 27 | horizontal: {} 28 | } 29 | 30 | element.waypointContextKey = this.key 31 | contexts[element.waypointContextKey] = this 32 | keyCounter += 1 33 | if (!Waypoint.windowContext) { 34 | Waypoint.windowContext = true 35 | Waypoint.windowContext = new Context(window) 36 | } 37 | 38 | this.createThrottledScrollHandler() 39 | this.createThrottledResizeHandler() 40 | } 41 | 42 | /* Private */ 43 | Context.prototype.add = function(waypoint) { 44 | var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical' 45 | this.waypoints[axis][waypoint.key] = waypoint 46 | this.refresh() 47 | } 48 | 49 | /* Private */ 50 | Context.prototype.checkEmpty = function() { 51 | var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal) 52 | var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical) 53 | var isWindow = this.element == this.element.window 54 | if (horizontalEmpty && verticalEmpty && !isWindow) { 55 | this.adapter.off('.waypoints') 56 | delete contexts[this.key] 57 | } 58 | } 59 | 60 | /* Private */ 61 | Context.prototype.createThrottledResizeHandler = function() { 62 | var self = this 63 | 64 | function resizeHandler() { 65 | self.handleResize() 66 | self.didResize = false 67 | } 68 | 69 | this.adapter.on('resize.waypoints', function() { 70 | if (!self.didResize) { 71 | self.didResize = true 72 | Waypoint.requestAnimationFrame(resizeHandler) 73 | } 74 | }) 75 | } 76 | 77 | /* Private */ 78 | Context.prototype.createThrottledScrollHandler = function() { 79 | var self = this 80 | function scrollHandler() { 81 | self.handleScroll() 82 | self.didScroll = false 83 | } 84 | 85 | this.adapter.on('scroll.waypoints', function() { 86 | if (!self.didScroll || Waypoint.isTouch) { 87 | self.didScroll = true 88 | Waypoint.requestAnimationFrame(scrollHandler) 89 | } 90 | }) 91 | } 92 | 93 | /* Private */ 94 | Context.prototype.handleResize = function() { 95 | Waypoint.Context.refreshAll() 96 | } 97 | 98 | /* Private */ 99 | Context.prototype.handleScroll = function() { 100 | var triggeredGroups = {} 101 | var axes = { 102 | horizontal: { 103 | newScroll: this.adapter.scrollLeft(), 104 | oldScroll: this.oldScroll.x, 105 | forward: 'right', 106 | backward: 'left' 107 | }, 108 | vertical: { 109 | newScroll: this.adapter.scrollTop(), 110 | oldScroll: this.oldScroll.y, 111 | forward: 'down', 112 | backward: 'up' 113 | } 114 | } 115 | 116 | for (var axisKey in axes) { 117 | var axis = axes[axisKey] 118 | var isForward = axis.newScroll > axis.oldScroll 119 | var direction = isForward ? axis.forward : axis.backward 120 | 121 | for (var waypointKey in this.waypoints[axisKey]) { 122 | var waypoint = this.waypoints[axisKey][waypointKey] 123 | if (waypoint.triggerPoint === null) { 124 | continue 125 | } 126 | var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint 127 | var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint 128 | var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint 129 | var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint 130 | if (crossedForward || crossedBackward) { 131 | waypoint.queueTrigger(direction) 132 | triggeredGroups[waypoint.group.id] = waypoint.group 133 | } 134 | } 135 | } 136 | 137 | for (var groupKey in triggeredGroups) { 138 | triggeredGroups[groupKey].flushTriggers() 139 | } 140 | 141 | this.oldScroll = { 142 | x: axes.horizontal.newScroll, 143 | y: axes.vertical.newScroll 144 | } 145 | } 146 | 147 | /* Private */ 148 | Context.prototype.innerHeight = function() { 149 | /*eslint-disable eqeqeq */ 150 | if (this.element == this.element.window) { 151 | return Waypoint.viewportHeight() 152 | } 153 | /*eslint-enable eqeqeq */ 154 | return this.adapter.innerHeight() 155 | } 156 | 157 | /* Private */ 158 | Context.prototype.remove = function(waypoint) { 159 | delete this.waypoints[waypoint.axis][waypoint.key] 160 | this.checkEmpty() 161 | } 162 | 163 | /* Private */ 164 | Context.prototype.innerWidth = function() { 165 | /*eslint-disable eqeqeq */ 166 | if (this.element == this.element.window) { 167 | return Waypoint.viewportWidth() 168 | } 169 | /*eslint-enable eqeqeq */ 170 | return this.adapter.innerWidth() 171 | } 172 | 173 | /* Public */ 174 | /* http://imakewebthings.com/waypoints/api/context-destroy */ 175 | Context.prototype.destroy = function() { 176 | var allWaypoints = [] 177 | for (var axis in this.waypoints) { 178 | for (var waypointKey in this.waypoints[axis]) { 179 | allWaypoints.push(this.waypoints[axis][waypointKey]) 180 | } 181 | } 182 | for (var i = 0, end = allWaypoints.length; i < end; i++) { 183 | allWaypoints[i].destroy() 184 | } 185 | } 186 | 187 | /* Public */ 188 | /* http://imakewebthings.com/waypoints/api/context-refresh */ 189 | Context.prototype.refresh = function() { 190 | /*eslint-disable eqeqeq */ 191 | var isWindow = this.element == this.element.window 192 | /*eslint-enable eqeqeq */ 193 | var contextOffset = isWindow ? undefined : this.adapter.offset() 194 | var triggeredGroups = {} 195 | var axes 196 | 197 | this.handleScroll() 198 | axes = { 199 | horizontal: { 200 | contextOffset: isWindow ? 0 : contextOffset.left, 201 | contextScroll: isWindow ? 0 : this.oldScroll.x, 202 | contextDimension: this.innerWidth(), 203 | oldScroll: this.oldScroll.x, 204 | forward: 'right', 205 | backward: 'left', 206 | offsetProp: 'left' 207 | }, 208 | vertical: { 209 | contextOffset: isWindow ? 0 : contextOffset.top, 210 | contextScroll: isWindow ? 0 : this.oldScroll.y, 211 | contextDimension: this.innerHeight(), 212 | oldScroll: this.oldScroll.y, 213 | forward: 'down', 214 | backward: 'up', 215 | offsetProp: 'top' 216 | } 217 | } 218 | 219 | for (var axisKey in axes) { 220 | var axis = axes[axisKey] 221 | for (var waypointKey in this.waypoints[axisKey]) { 222 | var waypoint = this.waypoints[axisKey][waypointKey] 223 | var adjustment = waypoint.options.offset 224 | var oldTriggerPoint = waypoint.triggerPoint 225 | var elementOffset = 0 226 | var freshWaypoint = oldTriggerPoint == null 227 | var contextModifier, wasBeforeScroll, nowAfterScroll 228 | var triggeredBackward, triggeredForward 229 | 230 | if (waypoint.element !== waypoint.element.window) { 231 | elementOffset = waypoint.adapter.offset()[axis.offsetProp] 232 | } 233 | 234 | if (typeof adjustment === 'function') { 235 | adjustment = adjustment.apply(waypoint) 236 | } 237 | else if (typeof adjustment === 'string') { 238 | adjustment = parseFloat(adjustment) 239 | if (waypoint.options.offset.indexOf('%') > - 1) { 240 | adjustment = Math.ceil(axis.contextDimension * adjustment / 100) 241 | } 242 | } 243 | 244 | contextModifier = axis.contextScroll - axis.contextOffset 245 | waypoint.triggerPoint = Math.floor(elementOffset + contextModifier - adjustment) 246 | wasBeforeScroll = oldTriggerPoint < axis.oldScroll 247 | nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll 248 | triggeredBackward = wasBeforeScroll && nowAfterScroll 249 | triggeredForward = !wasBeforeScroll && !nowAfterScroll 250 | 251 | if (!freshWaypoint && triggeredBackward) { 252 | waypoint.queueTrigger(axis.backward) 253 | triggeredGroups[waypoint.group.id] = waypoint.group 254 | } 255 | else if (!freshWaypoint && triggeredForward) { 256 | waypoint.queueTrigger(axis.forward) 257 | triggeredGroups[waypoint.group.id] = waypoint.group 258 | } 259 | else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) { 260 | waypoint.queueTrigger(axis.forward) 261 | triggeredGroups[waypoint.group.id] = waypoint.group 262 | } 263 | } 264 | } 265 | 266 | Waypoint.requestAnimationFrame(function() { 267 | for (var groupKey in triggeredGroups) { 268 | triggeredGroups[groupKey].flushTriggers() 269 | } 270 | }) 271 | 272 | return this 273 | } 274 | 275 | /* Private */ 276 | Context.findOrCreateByElement = function(element) { 277 | return Context.findByElement(element) || new Context(element) 278 | } 279 | 280 | /* Private */ 281 | Context.refreshAll = function() { 282 | for (var contextId in contexts) { 283 | contexts[contextId].refresh() 284 | } 285 | } 286 | 287 | /* Public */ 288 | /* http://imakewebthings.com/waypoints/api/context-find-by-element */ 289 | Context.findByElement = function(element) { 290 | return contexts[element.waypointContextKey] 291 | } 292 | 293 | window.onload = function() { 294 | if (oldWindowLoad) { 295 | oldWindowLoad() 296 | } 297 | Context.refreshAll() 298 | } 299 | 300 | 301 | Waypoint.requestAnimationFrame = function(callback) { 302 | var requestFn = window.requestAnimationFrame || 303 | window.mozRequestAnimationFrame || 304 | window.webkitRequestAnimationFrame || 305 | requestAnimationFrameShim 306 | requestFn.call(window, callback) 307 | } 308 | Waypoint.Context = Context 309 | }()) 310 | -------------------------------------------------------------------------------- /src/debug.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict' 3 | 4 | var displayNoneMessage = [ 5 | 'You have a Waypoint element with display none. For more information on ', 6 | 'why this is a bad idea read ', 7 | 'http://imakewebthings.com/waypoints/guides/debugging/#display-none' 8 | ].join('') 9 | var fixedMessage = [ 10 | 'You have a Waypoint element with fixed positioning. For more ', 11 | 'information on why this is a bad idea read ', 12 | 'http://imakewebthings.com/waypoints/guides/debugging/#fixed-position' 13 | ].join('') 14 | 15 | function checkWaypointStyles() { 16 | var originalRefresh = window.Waypoint.Context.prototype.refresh 17 | 18 | window.Waypoint.Context.prototype.refresh = function() { 19 | for (var axis in this.waypoints) { 20 | for (var key in this.waypoints[axis]) { 21 | var waypoint = this.waypoints[axis][key] 22 | var style = window.getComputedStyle(waypoint.element) 23 | if (!waypoint.enabled) { 24 | continue 25 | } 26 | if (style && style.display === 'none') { 27 | console.error(displayNoneMessage) 28 | } 29 | if (style && style.position === 'fixed') { 30 | console.error(fixedMessage) 31 | } 32 | } 33 | } 34 | return originalRefresh.call(this) 35 | } 36 | } 37 | 38 | checkWaypointStyles() 39 | }()) 40 | -------------------------------------------------------------------------------- /src/group.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict' 3 | 4 | function byTriggerPoint(a, b) { 5 | return a.triggerPoint - b.triggerPoint 6 | } 7 | 8 | function byReverseTriggerPoint(a, b) { 9 | return b.triggerPoint - a.triggerPoint 10 | } 11 | 12 | var groups = { 13 | vertical: {}, 14 | horizontal: {} 15 | } 16 | var Waypoint = window.Waypoint 17 | 18 | /* http://imakewebthings.com/waypoints/api/group */ 19 | function Group(options) { 20 | this.name = options.name 21 | this.axis = options.axis 22 | this.id = this.name + '-' + this.axis 23 | this.waypoints = [] 24 | this.clearTriggerQueues() 25 | groups[this.axis][this.name] = this 26 | } 27 | 28 | /* Private */ 29 | Group.prototype.add = function(waypoint) { 30 | this.waypoints.push(waypoint) 31 | } 32 | 33 | /* Private */ 34 | Group.prototype.clearTriggerQueues = function() { 35 | this.triggerQueues = { 36 | up: [], 37 | down: [], 38 | left: [], 39 | right: [] 40 | } 41 | } 42 | 43 | /* Private */ 44 | Group.prototype.flushTriggers = function() { 45 | for (var direction in this.triggerQueues) { 46 | var waypoints = this.triggerQueues[direction] 47 | var reverse = direction === 'up' || direction === 'left' 48 | waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint) 49 | for (var i = 0, end = waypoints.length; i < end; i += 1) { 50 | var waypoint = waypoints[i] 51 | if (waypoint.options.continuous || i === waypoints.length - 1) { 52 | waypoint.trigger([direction]) 53 | } 54 | } 55 | } 56 | this.clearTriggerQueues() 57 | } 58 | 59 | /* Private */ 60 | Group.prototype.next = function(waypoint) { 61 | this.waypoints.sort(byTriggerPoint) 62 | var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) 63 | var isLast = index === this.waypoints.length - 1 64 | return isLast ? null : this.waypoints[index + 1] 65 | } 66 | 67 | /* Private */ 68 | Group.prototype.previous = function(waypoint) { 69 | this.waypoints.sort(byTriggerPoint) 70 | var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) 71 | return index ? this.waypoints[index - 1] : null 72 | } 73 | 74 | /* Private */ 75 | Group.prototype.queueTrigger = function(waypoint, direction) { 76 | this.triggerQueues[direction].push(waypoint) 77 | } 78 | 79 | /* Private */ 80 | Group.prototype.remove = function(waypoint) { 81 | var index = Waypoint.Adapter.inArray(waypoint, this.waypoints) 82 | if (index > -1) { 83 | this.waypoints.splice(index, 1) 84 | } 85 | } 86 | 87 | /* Public */ 88 | /* http://imakewebthings.com/waypoints/api/first */ 89 | Group.prototype.first = function() { 90 | return this.waypoints[0] 91 | } 92 | 93 | /* Public */ 94 | /* http://imakewebthings.com/waypoints/api/last */ 95 | Group.prototype.last = function() { 96 | return this.waypoints[this.waypoints.length - 1] 97 | } 98 | 99 | /* Private */ 100 | Group.findOrCreate = function(options) { 101 | return groups[options.axis][options.name] || new Group(options) 102 | } 103 | 104 | Waypoint.Group = Group 105 | }()) 106 | -------------------------------------------------------------------------------- /src/shortcuts/infinite.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict' 3 | 4 | var $ = window.jQuery 5 | var Waypoint = window.Waypoint 6 | 7 | /* http://imakewebthings.com/waypoints/shortcuts/infinite-scroll */ 8 | function Infinite(options) { 9 | this.options = $.extend({}, Infinite.defaults, options) 10 | this.container = this.options.element 11 | if (this.options.container !== 'auto') { 12 | this.container = this.options.container 13 | } 14 | this.$container = $(this.container) 15 | this.$more = $(this.options.more) 16 | 17 | if (this.$more.length) { 18 | this.setupHandler() 19 | this.waypoint = new Waypoint(this.options) 20 | } 21 | } 22 | 23 | /* Private */ 24 | Infinite.prototype.setupHandler = function() { 25 | this.options.handler = $.proxy(function() { 26 | this.options.onBeforePageLoad() 27 | this.destroy() 28 | this.$container.addClass(this.options.loadingClass) 29 | 30 | $.get($(this.options.more).attr('href'), $.proxy(function(data) { 31 | var $data = $($.parseHTML(data)) 32 | var $newMore = $data.find(this.options.more) 33 | 34 | var $items = $data.find(this.options.items) 35 | if (!$items.length) { 36 | $items = $data.filter(this.options.items) 37 | } 38 | 39 | this.$container.append($items) 40 | this.$container.removeClass(this.options.loadingClass) 41 | 42 | if (!$newMore.length) { 43 | $newMore = $data.filter(this.options.more) 44 | } 45 | if ($newMore.length) { 46 | this.$more.replaceWith($newMore) 47 | this.$more = $newMore 48 | this.waypoint = new Waypoint(this.options) 49 | } 50 | else { 51 | this.$more.remove() 52 | } 53 | 54 | this.options.onAfterPageLoad($items) 55 | }, this)) 56 | }, this) 57 | } 58 | 59 | /* Public */ 60 | Infinite.prototype.destroy = function() { 61 | if (this.waypoint) { 62 | this.waypoint.destroy() 63 | } 64 | } 65 | 66 | Infinite.defaults = { 67 | container: 'auto', 68 | items: '.infinite-item', 69 | more: '.infinite-more-link', 70 | offset: 'bottom-in-view', 71 | loadingClass: 'infinite-loading', 72 | onBeforePageLoad: $.noop, 73 | onAfterPageLoad: $.noop 74 | } 75 | 76 | Waypoint.Infinite = Infinite 77 | }()) 78 | -------------------------------------------------------------------------------- /src/shortcuts/inview.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict' 3 | 4 | function noop() {} 5 | 6 | var Waypoint = window.Waypoint 7 | 8 | /* http://imakewebthings.com/waypoints/shortcuts/inview */ 9 | function Inview(options) { 10 | this.options = Waypoint.Adapter.extend({}, Inview.defaults, options) 11 | this.axis = this.options.horizontal ? 'horizontal' : 'vertical' 12 | this.waypoints = [] 13 | this.element = this.options.element 14 | this.createWaypoints() 15 | } 16 | 17 | /* Private */ 18 | Inview.prototype.createWaypoints = function() { 19 | var configs = { 20 | vertical: [{ 21 | down: 'enter', 22 | up: 'exited', 23 | offset: '100%' 24 | }, { 25 | down: 'entered', 26 | up: 'exit', 27 | offset: 'bottom-in-view' 28 | }, { 29 | down: 'exit', 30 | up: 'entered', 31 | offset: 0 32 | }, { 33 | down: 'exited', 34 | up: 'enter', 35 | offset: function() { 36 | return -this.adapter.outerHeight() 37 | } 38 | }], 39 | horizontal: [{ 40 | right: 'enter', 41 | left: 'exited', 42 | offset: '100%' 43 | }, { 44 | right: 'entered', 45 | left: 'exit', 46 | offset: 'right-in-view' 47 | }, { 48 | right: 'exit', 49 | left: 'entered', 50 | offset: 0 51 | }, { 52 | right: 'exited', 53 | left: 'enter', 54 | offset: function() { 55 | return -this.adapter.outerWidth() 56 | } 57 | }] 58 | } 59 | 60 | for (var i = 0, end = configs[this.axis].length; i < end; i++) { 61 | var config = configs[this.axis][i] 62 | this.createWaypoint(config) 63 | } 64 | } 65 | 66 | /* Private */ 67 | Inview.prototype.createWaypoint = function(config) { 68 | var self = this 69 | this.waypoints.push(new Waypoint({ 70 | context: this.options.context, 71 | element: this.options.element, 72 | enabled: this.options.enabled, 73 | handler: (function(config) { 74 | return function(direction) { 75 | self.options[config[direction]].call(self, direction) 76 | } 77 | }(config)), 78 | offset: config.offset, 79 | horizontal: this.options.horizontal 80 | })) 81 | } 82 | 83 | /* Public */ 84 | Inview.prototype.destroy = function() { 85 | for (var i = 0, end = this.waypoints.length; i < end; i++) { 86 | this.waypoints[i].destroy() 87 | } 88 | this.waypoints = [] 89 | } 90 | 91 | Inview.prototype.disable = function() { 92 | for (var i = 0, end = this.waypoints.length; i < end; i++) { 93 | this.waypoints[i].disable() 94 | } 95 | } 96 | 97 | Inview.prototype.enable = function() { 98 | for (var i = 0, end = this.waypoints.length; i < end; i++) { 99 | this.waypoints[i].enable() 100 | } 101 | } 102 | 103 | Inview.defaults = { 104 | context: window, 105 | enabled: true, 106 | enter: noop, 107 | entered: noop, 108 | exit: noop, 109 | exited: noop 110 | } 111 | 112 | Waypoint.Inview = Inview 113 | }()) 114 | -------------------------------------------------------------------------------- /src/shortcuts/sticky.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict' 3 | 4 | var $ = window.jQuery 5 | var Waypoint = window.Waypoint 6 | 7 | /* http://imakewebthings.com/waypoints/shortcuts/sticky-elements */ 8 | function Sticky(options) { 9 | this.options = $.extend({}, Waypoint.defaults, Sticky.defaults, options) 10 | this.element = this.options.element 11 | this.$element = $(this.element) 12 | this.createWrapper() 13 | this.createWaypoint() 14 | } 15 | 16 | /* Private */ 17 | Sticky.prototype.createWaypoint = function() { 18 | var originalHandler = this.options.handler 19 | 20 | this.waypoint = new Waypoint($.extend({}, this.options, { 21 | element: this.wrapper, 22 | handler: $.proxy(function(direction) { 23 | var shouldBeStuck = this.options.direction.indexOf(direction) > -1 24 | var wrapperHeight = shouldBeStuck ? this.$element.outerHeight(true) : '' 25 | 26 | this.$wrapper.height(wrapperHeight) 27 | this.$element.toggleClass(this.options.stuckClass, shouldBeStuck) 28 | 29 | if (originalHandler) { 30 | originalHandler.call(this, direction) 31 | } 32 | }, this) 33 | })) 34 | } 35 | 36 | /* Private */ 37 | Sticky.prototype.createWrapper = function() { 38 | if (this.options.wrapper) { 39 | this.$element.wrap(this.options.wrapper) 40 | } 41 | this.$wrapper = this.$element.parent() 42 | this.wrapper = this.$wrapper[0] 43 | } 44 | 45 | /* Public */ 46 | Sticky.prototype.destroy = function() { 47 | if (this.$element.parent()[0] === this.wrapper) { 48 | this.waypoint.destroy() 49 | this.$element.removeClass(this.options.stuckClass) 50 | if (this.options.wrapper) { 51 | this.$element.unwrap() 52 | } 53 | } 54 | } 55 | 56 | Sticky.defaults = { 57 | wrapper: '
', 58 | stuckClass: 'stuck', 59 | direction: 'down right' 60 | } 61 | 62 | Waypoint.Sticky = Sticky 63 | }()) 64 | -------------------------------------------------------------------------------- /src/waypoint.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict' 3 | 4 | var keyCounter = 0 5 | var allWaypoints = {} 6 | 7 | /* http://imakewebthings.com/waypoints/api/waypoint */ 8 | function Waypoint(options) { 9 | if (!options) { 10 | throw new Error('No options passed to Waypoint constructor') 11 | } 12 | if (!options.element) { 13 | throw new Error('No element option passed to Waypoint constructor') 14 | } 15 | if (!options.handler) { 16 | throw new Error('No handler option passed to Waypoint constructor') 17 | } 18 | 19 | this.key = 'waypoint-' + keyCounter 20 | this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options) 21 | this.element = this.options.element 22 | this.adapter = new Waypoint.Adapter(this.element) 23 | this.callback = options.handler 24 | this.axis = this.options.horizontal ? 'horizontal' : 'vertical' 25 | this.enabled = this.options.enabled 26 | this.triggerPoint = null 27 | this.group = Waypoint.Group.findOrCreate({ 28 | name: this.options.group, 29 | axis: this.axis 30 | }) 31 | this.context = Waypoint.Context.findOrCreateByElement(this.options.context) 32 | 33 | if (Waypoint.offsetAliases[this.options.offset]) { 34 | this.options.offset = Waypoint.offsetAliases[this.options.offset] 35 | } 36 | this.group.add(this) 37 | this.context.add(this) 38 | allWaypoints[this.key] = this 39 | keyCounter += 1 40 | } 41 | 42 | /* Private */ 43 | Waypoint.prototype.queueTrigger = function(direction) { 44 | this.group.queueTrigger(this, direction) 45 | } 46 | 47 | /* Private */ 48 | Waypoint.prototype.trigger = function(args) { 49 | if (!this.enabled) { 50 | return 51 | } 52 | if (this.callback) { 53 | this.callback.apply(this, args) 54 | } 55 | } 56 | 57 | /* Public */ 58 | /* http://imakewebthings.com/waypoints/api/destroy */ 59 | Waypoint.prototype.destroy = function() { 60 | this.context.remove(this) 61 | this.group.remove(this) 62 | delete allWaypoints[this.key] 63 | } 64 | 65 | /* Public */ 66 | /* http://imakewebthings.com/waypoints/api/disable */ 67 | Waypoint.prototype.disable = function() { 68 | this.enabled = false 69 | return this 70 | } 71 | 72 | /* Public */ 73 | /* http://imakewebthings.com/waypoints/api/enable */ 74 | Waypoint.prototype.enable = function() { 75 | this.context.refresh() 76 | this.enabled = true 77 | return this 78 | } 79 | 80 | /* Public */ 81 | /* http://imakewebthings.com/waypoints/api/next */ 82 | Waypoint.prototype.next = function() { 83 | return this.group.next(this) 84 | } 85 | 86 | /* Public */ 87 | /* http://imakewebthings.com/waypoints/api/previous */ 88 | Waypoint.prototype.previous = function() { 89 | return this.group.previous(this) 90 | } 91 | 92 | /* Private */ 93 | Waypoint.invokeAll = function(method) { 94 | var allWaypointsArray = [] 95 | for (var waypointKey in allWaypoints) { 96 | allWaypointsArray.push(allWaypoints[waypointKey]) 97 | } 98 | for (var i = 0, end = allWaypointsArray.length; i < end; i++) { 99 | allWaypointsArray[i][method]() 100 | } 101 | } 102 | 103 | /* Public */ 104 | /* http://imakewebthings.com/waypoints/api/destroy-all */ 105 | Waypoint.destroyAll = function() { 106 | Waypoint.invokeAll('destroy') 107 | } 108 | 109 | /* Public */ 110 | /* http://imakewebthings.com/waypoints/api/disable-all */ 111 | Waypoint.disableAll = function() { 112 | Waypoint.invokeAll('disable') 113 | } 114 | 115 | /* Public */ 116 | /* http://imakewebthings.com/waypoints/api/enable-all */ 117 | Waypoint.enableAll = function() { 118 | Waypoint.Context.refreshAll() 119 | for (var waypointKey in allWaypoints) { 120 | allWaypoints[waypointKey].enabled = true 121 | } 122 | return this 123 | } 124 | 125 | /* Public */ 126 | /* http://imakewebthings.com/waypoints/api/refresh-all */ 127 | Waypoint.refreshAll = function() { 128 | Waypoint.Context.refreshAll() 129 | } 130 | 131 | /* Public */ 132 | /* http://imakewebthings.com/waypoints/api/viewport-height */ 133 | Waypoint.viewportHeight = function() { 134 | return window.innerHeight || document.documentElement.clientHeight 135 | } 136 | 137 | /* Public */ 138 | /* http://imakewebthings.com/waypoints/api/viewport-width */ 139 | Waypoint.viewportWidth = function() { 140 | return document.documentElement.clientWidth 141 | } 142 | 143 | Waypoint.adapters = [] 144 | 145 | Waypoint.defaults = { 146 | context: window, 147 | continuous: true, 148 | enabled: true, 149 | group: 'default', 150 | horizontal: false, 151 | offset: 0 152 | } 153 | 154 | Waypoint.offsetAliases = { 155 | 'bottom-in-view': function() { 156 | return this.context.innerHeight() - this.adapter.outerHeight() 157 | }, 158 | 'right-in-view': function() { 159 | return this.context.innerWidth() - this.adapter.outerWidth() 160 | } 161 | } 162 | 163 | window.Waypoint = Waypoint 164 | }()) 165 | -------------------------------------------------------------------------------- /test/adapter-fn-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global 3 | * describe, it, beforeEach, afterEach, expect, spyOn, waits, runs, 4 | * waitsFor, loadFixtures, Waypoint 5 | */ 6 | 7 | window.jQuery.each(window.jQuery.grep(Waypoint.adapters, function(adapter) { 8 | return 'jquery zepto'.indexOf(adapter.name) > -1 9 | }), function(i, adapter) { 10 | describe('$.fn extension for ' + adapter.name + ':', function() { 11 | var $, waypoints 12 | 13 | beforeEach(function() { 14 | $ = adapter.name === 'jquery' ? window.jQuery : window.Zepto 15 | Waypoint.Adapter = adapter.Adapter 16 | loadFixtures('standard.html') 17 | }) 18 | 19 | afterEach(function() { 20 | $.each(waypoints, function(i, waypoint) { 21 | waypoint.destroy() 22 | }) 23 | }) 24 | 25 | describe('waypoint initialization', function() { 26 | it('uses the subject elements as the element option', function() { 27 | waypoints = $('.nearsame').waypoint(function() {}) 28 | expect(waypoints[0].element.id).toEqual('near1') 29 | expect(waypoints[1].element.id).toEqual('near2') 30 | }) 31 | 32 | it('returns an array of Waypoint instances', function() { 33 | waypoints = $('.nearsame').waypoint(function() {}) 34 | expect($.isArray(waypoints)).toBeTruthy() 35 | expect(waypoints.length).toEqual(2) 36 | }) 37 | 38 | it('can take the handler as the first parameter', function() { 39 | function handler() {} 40 | waypoints = $('#near1').waypoint(handler) 41 | expect(waypoints[0].callback).toBe(handler) 42 | }) 43 | }) 44 | 45 | describe('context option', function() { 46 | it('can be given a string selector', function() { 47 | waypoints = $('#inner3').waypoint({ 48 | context: '#bottom', 49 | handler: function() {} 50 | }) 51 | expect(waypoints[0].context.element).toBe($('#bottom')[0]) 52 | }) 53 | }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /test/context-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global 3 | * describe, it, beforeEach, afterEach, expect, spyOn, runs, 4 | * loadFixtures, Waypoint 5 | */ 6 | 7 | window.jQuery.each(Waypoint.adapters, function(i, adapter) { 8 | describe(adapter.name + ' adapter:', function() { 9 | describe('Waypoint.Context', function() { 10 | var $ = window.jQuery 11 | var currentDirection, $scroller, waypoint, $target, context, skipDestroy 12 | 13 | function setDirection(direction) { 14 | currentDirection = direction 15 | } 16 | 17 | beforeEach(function() { 18 | Waypoint.Adapter = adapter.Adapter 19 | loadFixtures('standard.html') 20 | $scroller = $(window) 21 | currentDirection = null 22 | skipDestroy = false 23 | $target = $('#same1') 24 | waypoint = new Waypoint({ 25 | element: $target[0], 26 | handler: setDirection 27 | }) 28 | context = waypoint.context 29 | }) 30 | 31 | afterEach(function() { 32 | if (!skipDestroy) { 33 | waypoint.destroy() 34 | } 35 | $scroller.scrollTop(0).scrollLeft(0) 36 | }) 37 | 38 | describe('#refresh', function() { 39 | it('updates trigger point of waypoints', function() { 40 | var top = $target.offset().top 41 | $target.css({ 42 | top: top + 1 43 | }) 44 | context.refresh() 45 | expect(waypoint.triggerPoint).toEqual(top + 1) 46 | }) 47 | 48 | it('triggers down direction if waypoint crosses upwards', function() { 49 | $target.css('top', '-1px') 50 | context.refresh() 51 | expect(currentDirection).toEqual('down') 52 | }) 53 | 54 | it('triggers up direction if waypoint crosses downwards', function() { 55 | $target.css('top', '-1px') 56 | context.refresh() 57 | $target.css('top', '0px') 58 | context.refresh() 59 | expect(currentDirection).toEqual('up') 60 | }) 61 | 62 | it('returns the same context instance for chaining', function() { 63 | expect(context.refresh()).toEqual(context) 64 | }) 65 | }) 66 | 67 | describe('#destroy', function() { 68 | it('prevents further waypoint triggers', function() { 69 | skipDestroy = true 70 | context.destroy() 71 | $scroller.scrollTop($target.offset().top) 72 | expect(currentDirection).toBeNull() 73 | }) 74 | }) 75 | 76 | describe('Waypoint.Context.refreshAll()', function() { 77 | it('calls refresh on all contexts', function() { 78 | var secondWaypoint = new Waypoint({ 79 | element: $('#inner3')[0], 80 | context: $('#bottom')[0], 81 | handler: function() {} 82 | }) 83 | var secondContext = secondWaypoint.context 84 | spyOn(context, 'refresh') 85 | spyOn(secondContext, 'refresh') 86 | Waypoint.Context.refreshAll() 87 | expect(context.refresh).toHaveBeenCalled() 88 | expect(secondContext.refresh).toHaveBeenCalled() 89 | secondWaypoint.destroy() 90 | }) 91 | }) 92 | }) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /test/debug-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* global 4 | * describe, it, beforeEach, afterEach, expect, spyOn, 5 | * loadFixtures, Waypoint 6 | */ 7 | 8 | describe('Waypoints debug script', function() { 9 | var waypoint, element 10 | 11 | beforeEach(function() { 12 | loadFixtures('standard.html') 13 | }) 14 | 15 | afterEach(function() { 16 | waypoint.destroy() 17 | }) 18 | 19 | describe('display none detection', function() { 20 | beforeEach(function() { 21 | element = document.getElementById('same1') 22 | waypoint = new Waypoint({ 23 | element: element, 24 | handler: function() {} 25 | }) 26 | element.style.display = 'none' 27 | }) 28 | 29 | it('logs a console error', function() { 30 | spyOn(console, 'error') 31 | waypoint.context.refresh() 32 | expect(console.error).toHaveBeenCalled() 33 | }) 34 | }) 35 | 36 | describe('display fixed positioning detection', function() { 37 | beforeEach(function() { 38 | element = document.getElementById('same1') 39 | waypoint = new Waypoint({ 40 | element: element, 41 | handler: function() {} 42 | }) 43 | element.style.position = 'fixed' 44 | }) 45 | 46 | it('logs a console error', function() { 47 | spyOn(console, 'error') 48 | waypoint.context.refresh() 49 | expect(console.error).toHaveBeenCalled() 50 | }) 51 | }) 52 | 53 | 54 | describe('fixed position detection', function() { 55 | 56 | }) 57 | 58 | describe('respect waypoint disabling', function() { 59 | beforeEach(function() { 60 | element = document.getElementById('same1') 61 | waypoint = new Waypoint({ 62 | element: element, 63 | handler: function() {} 64 | }) 65 | element.style.display = 'none' 66 | waypoint.disable() 67 | }) 68 | 69 | it('does not log a console error', function() { 70 | spyOn(console, 'error') 71 | waypoint.context.refresh() 72 | expect(console.error.calls.length).toEqual(0) 73 | }) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /test/fixtures/infinite.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | More 10 |
11 | 12 | 25 | -------------------------------------------------------------------------------- /test/fixtures/standard.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | 17 | 81 | -------------------------------------------------------------------------------- /test/fixtures/sticky.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | -------------------------------------------------------------------------------- /test/group-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global 3 | * describe, it, beforeEach, afterEach, expect, spyOn, 4 | * loadFixtures, Waypoint 5 | */ 6 | 7 | window.jQuery.each(Waypoint.adapters, function(i, adapter) { 8 | describe(adapter.name + ' adapter:', function() { 9 | describe('Waypoint.Group', function() { 10 | var $ = window.jQuery 11 | var firstWaypoint, secondWaypoint, customGroupWaypoint, defaultGroup 12 | 13 | beforeEach(function() { 14 | Waypoint.Adapter = adapter.Adapter 15 | loadFixtures('standard.html') 16 | firstWaypoint = new Waypoint({ 17 | element: $('#same1')[0], 18 | handler: function() {} 19 | }) 20 | secondWaypoint = new Waypoint({ 21 | element: $('#near1')[0], 22 | handler: function() {} 23 | }) 24 | customGroupWaypoint = new Waypoint({ 25 | element: $('#same2')[0], 26 | group: 'custom', 27 | handler: function() {} 28 | }) 29 | defaultGroup = firstWaypoint.group 30 | }) 31 | 32 | afterEach(function() { 33 | firstWaypoint.destroy() 34 | secondWaypoint.destroy() 35 | customGroupWaypoint.destroy() 36 | }) 37 | 38 | describe('#previous(waypoint)', function() { 39 | it('returns previous waypoint based on trigger point', function() { 40 | expect(defaultGroup.previous(secondWaypoint)).toEqual(firstWaypoint) 41 | }) 42 | 43 | it('returns null if on the first waypoint in the group', function() { 44 | expect(defaultGroup.previous(firstWaypoint)).toBeNull() 45 | }) 46 | }) 47 | 48 | describe('#next(waypoint)', function() { 49 | it('returns next waypoint based on trigger point', function() { 50 | expect(defaultGroup.next(firstWaypoint)).toEqual(secondWaypoint) 51 | }) 52 | 53 | it('returns null if on the last waypoint in the group', function() { 54 | expect(defaultGroup.next(secondWaypoint)).toBeNull() 55 | }) 56 | }) 57 | 58 | describe('#first()', function() { 59 | it('returns the first waypoint based on trigger point', function() { 60 | expect(defaultGroup.first()).toEqual(firstWaypoint) 61 | }) 62 | }) 63 | 64 | describe('#last()', function() { 65 | it('returns the first waypoint based on trigger point', function() { 66 | expect(defaultGroup.last()).toEqual(secondWaypoint) 67 | }) 68 | }) 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /test/infinite-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global 3 | * describe, it, beforeEach, afterEach, expect, spyOn, runs, 4 | * waitsFor, loadFixtures, Waypoint, jasmine 5 | */ 6 | 7 | describe('Waypoint Sticky Shortcut', function() { 8 | var $ = window.jQuery 9 | var $scroller = $(window) 10 | var $container, $items, $more, waypoint, beforeSpy, afterSpy 11 | 12 | beforeEach(function() { 13 | loadFixtures('infinite.html') 14 | $items = $('.infinite-item') 15 | $container = $('.infinite-container') 16 | $more = $('.infinite-more-link') 17 | beforeSpy = jasmine.createSpy('on before callback') 18 | afterSpy = jasmine.createSpy('on after callback') 19 | }) 20 | 21 | afterEach(function() { 22 | waypoint.destroy() 23 | $scroller.scrollTop(0) 24 | }) 25 | 26 | it('returns an instance of the Waypoint.Infinite class', function() { 27 | waypoint = new Waypoint.Infinite({ 28 | element: $container[0] 29 | }) 30 | expect(waypoint instanceof Waypoint.Infinite).toBeTruthy() 31 | }) 32 | 33 | describe('loading new pages', function() { 34 | beforeEach(function() { 35 | runs(function() { 36 | waypoint = new Waypoint.Infinite({ 37 | element: $container[0], 38 | onBeforePageLoad: beforeSpy, 39 | onAfterPageLoad: afterSpy 40 | }) 41 | $scroller.scrollTop(Waypoint.viewportHeight() - $container.height()) 42 | }) 43 | waitsFor(function() { 44 | return $('.infinite-container > .infinite-item').length > $items.length 45 | }, 'new items to load') 46 | }) 47 | 48 | it('replaces the more link with the new more link', function() { 49 | expect($more[0]).not.toEqual($('.infinite-more-link')[0]) 50 | }) 51 | 52 | it('fires the before callback', function() { 53 | expect(beforeSpy.callCount).toBeTruthy() 54 | }) 55 | 56 | it('fires the after callback', function() { 57 | expect(afterSpy.callCount).toBeTruthy() 58 | expect(afterSpy).toHaveBeenCalledWith(jasmine.any(Object)) 59 | }) 60 | }) 61 | 62 | describe('when there is no more link on initialization', function() { 63 | beforeEach(function() { 64 | $more.remove() 65 | waypoint = new Waypoint.Infinite({ 66 | element: $container[0] 67 | }) 68 | }) 69 | 70 | it('does not create the waypoint', function() { 71 | expect(waypoint.waypoint).toBeFalsy() 72 | }) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /test/inview-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global 3 | * describe, it, beforeEach, afterEach, expect, spyOn, waits, runs, 4 | * waitsFor, loadFixtures, Waypoint 5 | */ 6 | 7 | window.jQuery.each(Waypoint.adapters, function(i, adapter) { 8 | describe(adapter.name + ' adapter:', function() { 9 | describe('Waypoints Inview Shortcut', function() { 10 | var $ = window.jQuery 11 | var standard = 50 12 | var $scroller = $(window) 13 | var $target, waypoint, hits, callbackContext 14 | 15 | function setsTrue(key) { 16 | return function() { 17 | callbackContext = this 18 | hits[key] = true 19 | } 20 | } 21 | 22 | function toBeTrue(key) { 23 | return function() { 24 | return hits[key] 25 | } 26 | } 27 | 28 | beforeEach(function() { 29 | Waypoint.Adapter = adapter.Adapter 30 | loadFixtures('standard.html') 31 | $target = $('#near2') 32 | hits = {} 33 | }) 34 | 35 | afterEach(function() { 36 | waypoint.destroy() 37 | $scroller.scrollTop(0).scrollLeft(0) 38 | }) 39 | 40 | describe('vertical', function() { 41 | beforeEach(function() { 42 | waypoint = new Waypoint.Inview({ 43 | element: $target[0], 44 | enter: setsTrue('enter'), 45 | entered: setsTrue('entered'), 46 | exit: setsTrue('exit'), 47 | exited: setsTrue('exited') 48 | }) 49 | }) 50 | 51 | describe('enter callback', function() { 52 | it('triggers when element starts entering from below', function() { 53 | runs(function() { 54 | var top = $target.offset().top 55 | $scroller.scrollTop(top - Waypoint.viewportHeight()) 56 | }) 57 | waitsFor(toBeTrue('enter'), 'enter to trigger') 58 | runs(function() { 59 | expect(callbackContext).toEqual(waypoint) 60 | }) 61 | }) 62 | 63 | it('triggers when element starts entering from above', function() { 64 | runs(function() { 65 | $scroller.scrollTop($target.offset().top + $target.outerHeight()) 66 | }) 67 | waits(standard) 68 | runs(function() { 69 | hits.enter = false 70 | $scroller.scrollTop($scroller.scrollTop() - 1) 71 | }) 72 | waitsFor(toBeTrue('enter'), 'enter to trigger') 73 | }) 74 | }) 75 | 76 | describe('entered callback', function() { 77 | it('triggers when element finishes entering from below', function() { 78 | runs(function() { 79 | var top = $target.offset().top 80 | var viewportHeight = Waypoint.viewportHeight() 81 | var elementHeight = $target.outerHeight() 82 | $scroller.scrollTop(top - viewportHeight + elementHeight) 83 | }) 84 | waitsFor(toBeTrue('entered'), 'entered to trigger') 85 | runs(function() { 86 | expect(callbackContext).toEqual(waypoint) 87 | }) 88 | }) 89 | 90 | it('triggers when element finishes entering from above', function() { 91 | runs(function() { 92 | $scroller.scrollTop($target.offset().top) 93 | }) 94 | waits(standard) 95 | runs(function() { 96 | hits.entered = false 97 | $scroller.scrollTop($scroller.scrollTop() - 1) 98 | }) 99 | waitsFor(toBeTrue('entered'), 'entered to trigger') 100 | }) 101 | }) 102 | 103 | describe('exit callback', function() { 104 | it('triggers when element starts leaving below', function() { 105 | runs(function() { 106 | var top = $target.offset().top 107 | var viewportHeight = Waypoint.viewportHeight() 108 | var elementHeight = $target.outerHeight() 109 | $scroller.scrollTop(top - viewportHeight + elementHeight) 110 | }) 111 | waits(standard) 112 | runs(function() { 113 | expect(hits.exit).toBeFalsy() 114 | $scroller.scrollTop($scroller.scrollTop() - 1) 115 | }) 116 | waitsFor(toBeTrue('exit'), 'exit to trigger') 117 | }) 118 | 119 | it('triggers when element starts leaving above', function() { 120 | runs(function() { 121 | $scroller.scrollTop($target.offset().top) 122 | }) 123 | waitsFor(toBeTrue('exit'), 'exit to trigger') 124 | runs(function() { 125 | expect(callbackContext).toEqual(waypoint) 126 | }) 127 | }) 128 | }) 129 | 130 | describe('exited callback', function() { 131 | it('triggers when element finishes exiting below', function() { 132 | runs(function() { 133 | var top = $target.offset().top 134 | $scroller.scrollTop(top - Waypoint.viewportHeight()) 135 | }) 136 | waits(standard) 137 | runs(function() { 138 | $scroller.scrollTop($scroller.scrollTop() - 1) 139 | }) 140 | waitsFor(toBeTrue('exited'), 'exited to trigger') 141 | }) 142 | 143 | it('triggers when element finishes exiting above', function() { 144 | runs(function() { 145 | $scroller.scrollTop($target.offset().top + $target.outerHeight()) 146 | }) 147 | waitsFor(toBeTrue('exited'), 'exited to trigger') 148 | runs(function() { 149 | expect(callbackContext).toEqual(waypoint) 150 | }) 151 | }) 152 | }) 153 | }) 154 | 155 | describe('horizontal', function() { 156 | beforeEach(function() { 157 | waypoint = new Waypoint.Inview({ 158 | horizontal: true, 159 | element: $target[0], 160 | enter: setsTrue('enter'), 161 | entered: setsTrue('entered'), 162 | exit: setsTrue('exit'), 163 | exited: setsTrue('exited') 164 | }) 165 | }) 166 | 167 | describe('enter callback', function() { 168 | it('triggers when element starts entering from right', function() { 169 | runs(function() { 170 | $scroller.scrollLeft($target.offset().left - $scroller.width()) 171 | }) 172 | waitsFor(toBeTrue('enter'), 'enter to trigger') 173 | }) 174 | 175 | it('triggers when element starts entering from left', function() { 176 | runs(function() { 177 | var left = $target.offset().left 178 | $scroller.scrollLeft(left + $target.outerWidth()) 179 | }) 180 | waits(standard) 181 | runs(function() { 182 | hits.enter = false 183 | $scroller.scrollLeft($scroller.scrollLeft() - 1) 184 | }) 185 | waitsFor(toBeTrue('enter'), 'enter to trigger') 186 | }) 187 | }) 188 | 189 | describe('entered callback', function() { 190 | it('triggers when element finishes entering from right', function() { 191 | runs(function() { 192 | var left = $target.offset().left 193 | var viewportWidth = $scroller.width() 194 | var elementWidth = $target.outerWidth() 195 | $scroller.scrollLeft(left - viewportWidth + elementWidth) 196 | }) 197 | waitsFor(toBeTrue('entered'), 'entered to trigger') 198 | }) 199 | 200 | it('triggers when element finishes entering from left', function() { 201 | runs(function() { 202 | $scroller.scrollLeft($target.offset().left) 203 | }) 204 | waits(standard) 205 | runs(function() { 206 | hits.entered = false 207 | $scroller.scrollLeft($scroller.scrollLeft() - 1) 208 | }) 209 | waitsFor(toBeTrue('entered'), 'entered to trigger') 210 | }) 211 | }) 212 | 213 | describe('exit callback', function() { 214 | it('triggers when element starts leaving on the right', function() { 215 | runs(function() { 216 | var left = $target.offset().left 217 | var viewportWidth = $scroller.width() 218 | var elementWidth = $target.outerWidth() 219 | $scroller.scrollLeft(left - viewportWidth + elementWidth) 220 | }) 221 | waits(standard) 222 | runs(function() { 223 | expect(hits.exit).toBeFalsy() 224 | $scroller.scrollLeft($scroller.scrollLeft() - 1) 225 | }) 226 | waitsFor(toBeTrue('exit'), 'exit to trigger') 227 | }) 228 | 229 | it('triggers when element starts leaving on the left', function() { 230 | runs(function() { 231 | $scroller.scrollLeft($target.offset().left) 232 | }) 233 | waitsFor(toBeTrue('exit'), 'exit to trigger') 234 | }) 235 | }) 236 | 237 | describe('exited callback', function() { 238 | it('triggers when element finishes exiting to the right', function() { 239 | runs(function() { 240 | var left = $target.offset().left 241 | $scroller.scrollLeft(left - $scroller.width()) 242 | }) 243 | waitsFor(toBeTrue('enter'), 'enter to trigger') 244 | runs(function() { 245 | $scroller.scrollLeft($scroller.scrollLeft() - 1) 246 | }) 247 | waitsFor(toBeTrue('exited'), 'exited to trigger') 248 | }) 249 | 250 | it('triggers when element finishes exiting to the left', function() { 251 | runs(function() { 252 | var left = $target.offset().left 253 | $scroller.scrollLeft(left + $target.outerWidth()) 254 | }) 255 | waitsFor(toBeTrue('exited'), 'exited to trigger') 256 | }) 257 | }) 258 | }) 259 | 260 | describe('disabled', function() { 261 | beforeEach(function() { 262 | waypoint = new Waypoint.Inview({ 263 | element: $target[0], 264 | enabled: false, 265 | enter: setsTrue('enter'), 266 | entered: setsTrue('entered'), 267 | exit: setsTrue('exit'), 268 | exited: setsTrue('exited') 269 | }) 270 | }) 271 | 272 | it('starts disabled', function() { 273 | $.each(waypoint.waypoints, function(i, wp) { 274 | expect(wp.enabled).toEqual(false) 275 | }) 276 | }) 277 | 278 | describe('#enable', function() { 279 | it('enables all waypoints', function() { 280 | waypoint.enable() 281 | $.each(waypoint.waypoints, function(i, wp) { 282 | expect(wp.enabled).toEqual(true) 283 | }) 284 | }) 285 | }) 286 | 287 | describe('#disable', function() { 288 | it('disables all waypoints', function() { 289 | waypoint.enable() 290 | waypoint.disable() 291 | $.each(waypoint.waypoints, function(i, wp) { 292 | expect(wp.enabled).toEqual(false) 293 | }) 294 | }) 295 | }) 296 | }) 297 | }) 298 | }) 299 | }) 300 | -------------------------------------------------------------------------------- /test/lib/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporterHelpers = {}; 2 | 3 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { 4 | var el = document.createElement(type); 5 | 6 | for (var i = 2; i < arguments.length; i++) { 7 | var child = arguments[i]; 8 | 9 | if (typeof child === 'string') { 10 | el.appendChild(document.createTextNode(child)); 11 | } else { 12 | if (child) { 13 | el.appendChild(child); 14 | } 15 | } 16 | } 17 | 18 | for (var attr in attrs) { 19 | if (attr == "className") { 20 | el[attr] = attrs[attr]; 21 | } else { 22 | el.setAttribute(attr, attrs[attr]); 23 | } 24 | } 25 | 26 | return el; 27 | }; 28 | 29 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { 30 | var results = child.results(); 31 | var status = results.passed() ? 'passed' : 'failed'; 32 | if (results.skipped) { 33 | status = 'skipped'; 34 | } 35 | 36 | return status; 37 | }; 38 | 39 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { 40 | var parentDiv = this.dom.summary; 41 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; 42 | var parent = child[parentSuite]; 43 | 44 | if (parent) { 45 | if (typeof this.views.suites[parent.id] == 'undefined') { 46 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); 47 | } 48 | parentDiv = this.views.suites[parent.id].element; 49 | } 50 | 51 | parentDiv.appendChild(childElement); 52 | }; 53 | 54 | 55 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { 56 | for(var fn in jasmine.HtmlReporterHelpers) { 57 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; 58 | } 59 | }; 60 | 61 | jasmine.HtmlReporter = function(_doc) { 62 | var self = this; 63 | var doc = _doc || window.document; 64 | 65 | var reporterView; 66 | 67 | var dom = {}; 68 | 69 | // Jasmine Reporter Public Interface 70 | self.logRunningSpecs = false; 71 | 72 | self.reportRunnerStarting = function(runner) { 73 | var specs = runner.specs() || []; 74 | 75 | if (specs.length == 0) { 76 | return; 77 | } 78 | 79 | createReporterDom(runner.env.versionString()); 80 | doc.body.appendChild(dom.reporter); 81 | setExceptionHandling(); 82 | 83 | reporterView = new jasmine.HtmlReporter.ReporterView(dom); 84 | reporterView.addSpecs(specs, self.specFilter); 85 | }; 86 | 87 | self.reportRunnerResults = function(runner) { 88 | reporterView && reporterView.complete(); 89 | }; 90 | 91 | self.reportSuiteResults = function(suite) { 92 | reporterView.suiteComplete(suite); 93 | }; 94 | 95 | self.reportSpecStarting = function(spec) { 96 | if (self.logRunningSpecs) { 97 | self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 98 | } 99 | }; 100 | 101 | self.reportSpecResults = function(spec) { 102 | reporterView.specComplete(spec); 103 | }; 104 | 105 | self.log = function() { 106 | var console = jasmine.getGlobal().console; 107 | if (console && console.log) { 108 | if (console.log.apply) { 109 | console.log.apply(console, arguments); 110 | } else { 111 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 112 | } 113 | } 114 | }; 115 | 116 | self.specFilter = function(spec) { 117 | if (!focusedSpecName()) { 118 | return true; 119 | } 120 | 121 | return spec.getFullName().indexOf(focusedSpecName()) === 0; 122 | }; 123 | 124 | return self; 125 | 126 | function focusedSpecName() { 127 | var specName; 128 | 129 | (function memoizeFocusedSpec() { 130 | if (specName) { 131 | return; 132 | } 133 | 134 | var paramMap = []; 135 | var params = jasmine.HtmlReporter.parameters(doc); 136 | 137 | for (var i = 0; i < params.length; i++) { 138 | var p = params[i].split('='); 139 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 140 | } 141 | 142 | specName = paramMap.spec; 143 | })(); 144 | 145 | return specName; 146 | } 147 | 148 | function createReporterDom(version) { 149 | dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, 150 | dom.banner = self.createDom('div', { className: 'banner' }, 151 | self.createDom('span', { className: 'title' }, "Jasmine "), 152 | self.createDom('span', { className: 'version' }, version)), 153 | 154 | dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), 155 | dom.alert = self.createDom('div', {className: 'alert'}, 156 | self.createDom('span', { className: 'exceptions' }, 157 | self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'), 158 | self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), 159 | dom.results = self.createDom('div', {className: 'results'}, 160 | dom.summary = self.createDom('div', { className: 'summary' }), 161 | dom.details = self.createDom('div', { id: 'details' })) 162 | ); 163 | } 164 | 165 | function noTryCatch() { 166 | return window.location.search.match(/catch=false/); 167 | } 168 | 169 | function searchWithCatch() { 170 | var params = jasmine.HtmlReporter.parameters(window.document); 171 | var removed = false; 172 | var i = 0; 173 | 174 | while (!removed && i < params.length) { 175 | if (params[i].match(/catch=/)) { 176 | params.splice(i, 1); 177 | removed = true; 178 | } 179 | i++; 180 | } 181 | if (jasmine.CATCH_EXCEPTIONS) { 182 | params.push("catch=false"); 183 | } 184 | 185 | return params.join("&"); 186 | } 187 | 188 | function setExceptionHandling() { 189 | var chxCatch = document.getElementById('no_try_catch'); 190 | 191 | if (noTryCatch()) { 192 | chxCatch.setAttribute('checked', true); 193 | jasmine.CATCH_EXCEPTIONS = false; 194 | } 195 | chxCatch.onclick = function() { 196 | window.location.search = searchWithCatch(); 197 | }; 198 | } 199 | }; 200 | jasmine.HtmlReporter.parameters = function(doc) { 201 | var paramStr = doc.location.search.substring(1); 202 | var params = []; 203 | 204 | if (paramStr.length > 0) { 205 | params = paramStr.split('&'); 206 | } 207 | return params; 208 | } 209 | jasmine.HtmlReporter.sectionLink = function(sectionName) { 210 | var link = '?'; 211 | var params = []; 212 | 213 | if (sectionName) { 214 | params.push('spec=' + encodeURIComponent(sectionName)); 215 | } 216 | if (!jasmine.CATCH_EXCEPTIONS) { 217 | params.push("catch=false"); 218 | } 219 | if (params.length > 0) { 220 | link += params.join("&"); 221 | } 222 | 223 | return link; 224 | }; 225 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); 226 | jasmine.HtmlReporter.ReporterView = function(dom) { 227 | this.startedAt = new Date(); 228 | this.runningSpecCount = 0; 229 | this.completeSpecCount = 0; 230 | this.passedCount = 0; 231 | this.failedCount = 0; 232 | this.skippedCount = 0; 233 | 234 | this.createResultsMenu = function() { 235 | this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, 236 | this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), 237 | ' | ', 238 | this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); 239 | 240 | this.summaryMenuItem.onclick = function() { 241 | dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); 242 | }; 243 | 244 | this.detailsMenuItem.onclick = function() { 245 | showDetails(); 246 | }; 247 | }; 248 | 249 | this.addSpecs = function(specs, specFilter) { 250 | this.totalSpecCount = specs.length; 251 | 252 | this.views = { 253 | specs: {}, 254 | suites: {} 255 | }; 256 | 257 | for (var i = 0; i < specs.length; i++) { 258 | var spec = specs[i]; 259 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); 260 | if (specFilter(spec)) { 261 | this.runningSpecCount++; 262 | } 263 | } 264 | }; 265 | 266 | this.specComplete = function(spec) { 267 | this.completeSpecCount++; 268 | 269 | if (isUndefined(this.views.specs[spec.id])) { 270 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); 271 | } 272 | 273 | var specView = this.views.specs[spec.id]; 274 | 275 | switch (specView.status()) { 276 | case 'passed': 277 | this.passedCount++; 278 | break; 279 | 280 | case 'failed': 281 | this.failedCount++; 282 | break; 283 | 284 | case 'skipped': 285 | this.skippedCount++; 286 | break; 287 | } 288 | 289 | specView.refresh(); 290 | this.refresh(); 291 | }; 292 | 293 | this.suiteComplete = function(suite) { 294 | var suiteView = this.views.suites[suite.id]; 295 | if (isUndefined(suiteView)) { 296 | return; 297 | } 298 | suiteView.refresh(); 299 | }; 300 | 301 | this.refresh = function() { 302 | 303 | if (isUndefined(this.resultsMenu)) { 304 | this.createResultsMenu(); 305 | } 306 | 307 | // currently running UI 308 | if (isUndefined(this.runningAlert)) { 309 | this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); 310 | dom.alert.appendChild(this.runningAlert); 311 | } 312 | this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); 313 | 314 | // skipped specs UI 315 | if (isUndefined(this.skippedAlert)) { 316 | this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); 317 | } 318 | 319 | this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 320 | 321 | if (this.skippedCount === 1 && isDefined(dom.alert)) { 322 | dom.alert.appendChild(this.skippedAlert); 323 | } 324 | 325 | // passing specs UI 326 | if (isUndefined(this.passedAlert)) { 327 | this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); 328 | } 329 | this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); 330 | 331 | // failing specs UI 332 | if (isUndefined(this.failedAlert)) { 333 | this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); 334 | } 335 | this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); 336 | 337 | if (this.failedCount === 1 && isDefined(dom.alert)) { 338 | dom.alert.appendChild(this.failedAlert); 339 | dom.alert.appendChild(this.resultsMenu); 340 | } 341 | 342 | // summary info 343 | this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); 344 | this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; 345 | }; 346 | 347 | this.complete = function() { 348 | dom.alert.removeChild(this.runningAlert); 349 | 350 | this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 351 | 352 | if (this.failedCount === 0) { 353 | dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); 354 | } else { 355 | showDetails(); 356 | } 357 | 358 | dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); 359 | }; 360 | 361 | return this; 362 | 363 | function showDetails() { 364 | if (dom.reporter.className.search(/showDetails/) === -1) { 365 | dom.reporter.className += " showDetails"; 366 | } 367 | } 368 | 369 | function isUndefined(obj) { 370 | return typeof obj === 'undefined'; 371 | } 372 | 373 | function isDefined(obj) { 374 | return !isUndefined(obj); 375 | } 376 | 377 | function specPluralizedFor(count) { 378 | var str = count + " spec"; 379 | if (count > 1) { 380 | str += "s" 381 | } 382 | return str; 383 | } 384 | 385 | }; 386 | 387 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); 388 | 389 | 390 | jasmine.HtmlReporter.SpecView = function(spec, dom, views) { 391 | this.spec = spec; 392 | this.dom = dom; 393 | this.views = views; 394 | 395 | this.symbol = this.createDom('li', { className: 'pending' }); 396 | this.dom.symbolSummary.appendChild(this.symbol); 397 | 398 | this.summary = this.createDom('div', { className: 'specSummary' }, 399 | this.createDom('a', { 400 | className: 'description', 401 | href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), 402 | title: this.spec.getFullName() 403 | }, this.spec.description) 404 | ); 405 | 406 | this.detail = this.createDom('div', { className: 'specDetail' }, 407 | this.createDom('a', { 408 | className: 'description', 409 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 410 | title: this.spec.getFullName() 411 | }, this.spec.getFullName()) 412 | ); 413 | }; 414 | 415 | jasmine.HtmlReporter.SpecView.prototype.status = function() { 416 | return this.getSpecStatus(this.spec); 417 | }; 418 | 419 | jasmine.HtmlReporter.SpecView.prototype.refresh = function() { 420 | this.symbol.className = this.status(); 421 | 422 | switch (this.status()) { 423 | case 'skipped': 424 | break; 425 | 426 | case 'passed': 427 | this.appendSummaryToSuiteDiv(); 428 | break; 429 | 430 | case 'failed': 431 | this.appendSummaryToSuiteDiv(); 432 | this.appendFailureDetail(); 433 | break; 434 | } 435 | }; 436 | 437 | jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { 438 | this.summary.className += ' ' + this.status(); 439 | this.appendToSummary(this.spec, this.summary); 440 | }; 441 | 442 | jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { 443 | this.detail.className += ' ' + this.status(); 444 | 445 | var resultItems = this.spec.results().getItems(); 446 | var messagesDiv = this.createDom('div', { className: 'messages' }); 447 | 448 | for (var i = 0; i < resultItems.length; i++) { 449 | var result = resultItems[i]; 450 | 451 | if (result.type == 'log') { 452 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 453 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 454 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 455 | 456 | if (result.trace.stack) { 457 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 458 | } 459 | } 460 | } 461 | 462 | if (messagesDiv.childNodes.length > 0) { 463 | this.detail.appendChild(messagesDiv); 464 | this.dom.details.appendChild(this.detail); 465 | } 466 | }; 467 | 468 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { 469 | this.suite = suite; 470 | this.dom = dom; 471 | this.views = views; 472 | 473 | this.element = this.createDom('div', { className: 'suite' }, 474 | this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) 475 | ); 476 | 477 | this.appendToSummary(this.suite, this.element); 478 | }; 479 | 480 | jasmine.HtmlReporter.SuiteView.prototype.status = function() { 481 | return this.getSpecStatus(this.suite); 482 | }; 483 | 484 | jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { 485 | this.element.className += " " + this.status(); 486 | }; 487 | 488 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); 489 | 490 | /* @deprecated Use jasmine.HtmlReporter instead 491 | */ 492 | jasmine.TrivialReporter = function(doc) { 493 | this.document = doc || document; 494 | this.suiteDivs = {}; 495 | this.logRunningSpecs = false; 496 | }; 497 | 498 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 499 | var el = document.createElement(type); 500 | 501 | for (var i = 2; i < arguments.length; i++) { 502 | var child = arguments[i]; 503 | 504 | if (typeof child === 'string') { 505 | el.appendChild(document.createTextNode(child)); 506 | } else { 507 | if (child) { el.appendChild(child); } 508 | } 509 | } 510 | 511 | for (var attr in attrs) { 512 | if (attr == "className") { 513 | el[attr] = attrs[attr]; 514 | } else { 515 | el.setAttribute(attr, attrs[attr]); 516 | } 517 | } 518 | 519 | return el; 520 | }; 521 | 522 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 523 | var showPassed, showSkipped; 524 | 525 | this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, 526 | this.createDom('div', { className: 'banner' }, 527 | this.createDom('div', { className: 'logo' }, 528 | this.createDom('span', { className: 'title' }, "Jasmine"), 529 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 530 | this.createDom('div', { className: 'options' }, 531 | "Show ", 532 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 533 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 534 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 535 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 536 | ) 537 | ), 538 | 539 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 540 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 541 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 542 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 543 | ); 544 | 545 | this.document.body.appendChild(this.outerDiv); 546 | 547 | var suites = runner.suites(); 548 | for (var i = 0; i < suites.length; i++) { 549 | var suite = suites[i]; 550 | var suiteDiv = this.createDom('div', { className: 'suite' }, 551 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 552 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 553 | this.suiteDivs[suite.id] = suiteDiv; 554 | var parentDiv = this.outerDiv; 555 | if (suite.parentSuite) { 556 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 557 | } 558 | parentDiv.appendChild(suiteDiv); 559 | } 560 | 561 | this.startedAt = new Date(); 562 | 563 | var self = this; 564 | showPassed.onclick = function(evt) { 565 | if (showPassed.checked) { 566 | self.outerDiv.className += ' show-passed'; 567 | } else { 568 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 569 | } 570 | }; 571 | 572 | showSkipped.onclick = function(evt) { 573 | if (showSkipped.checked) { 574 | self.outerDiv.className += ' show-skipped'; 575 | } else { 576 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 577 | } 578 | }; 579 | }; 580 | 581 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 582 | var results = runner.results(); 583 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 584 | this.runnerDiv.setAttribute("class", className); 585 | //do it twice for IE 586 | this.runnerDiv.setAttribute("className", className); 587 | var specs = runner.specs(); 588 | var specCount = 0; 589 | for (var i = 0; i < specs.length; i++) { 590 | if (this.specFilter(specs[i])) { 591 | specCount++; 592 | } 593 | } 594 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 595 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 596 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 597 | 598 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 599 | }; 600 | 601 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 602 | var results = suite.results(); 603 | var status = results.passed() ? 'passed' : 'failed'; 604 | if (results.totalCount === 0) { // todo: change this to check results.skipped 605 | status = 'skipped'; 606 | } 607 | this.suiteDivs[suite.id].className += " " + status; 608 | }; 609 | 610 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 611 | if (this.logRunningSpecs) { 612 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 613 | } 614 | }; 615 | 616 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 617 | var results = spec.results(); 618 | var status = results.passed() ? 'passed' : 'failed'; 619 | if (results.skipped) { 620 | status = 'skipped'; 621 | } 622 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 623 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 624 | this.createDom('a', { 625 | className: 'description', 626 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 627 | title: spec.getFullName() 628 | }, spec.description)); 629 | 630 | 631 | var resultItems = results.getItems(); 632 | var messagesDiv = this.createDom('div', { className: 'messages' }); 633 | for (var i = 0; i < resultItems.length; i++) { 634 | var result = resultItems[i]; 635 | 636 | if (result.type == 'log') { 637 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 638 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 639 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 640 | 641 | if (result.trace.stack) { 642 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 643 | } 644 | } 645 | } 646 | 647 | if (messagesDiv.childNodes.length > 0) { 648 | specDiv.appendChild(messagesDiv); 649 | } 650 | 651 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 652 | }; 653 | 654 | jasmine.TrivialReporter.prototype.log = function() { 655 | var console = jasmine.getGlobal().console; 656 | if (console && console.log) { 657 | if (console.log.apply) { 658 | console.log.apply(console, arguments); 659 | } else { 660 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 661 | } 662 | } 663 | }; 664 | 665 | jasmine.TrivialReporter.prototype.getLocation = function() { 666 | return this.document.location; 667 | }; 668 | 669 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 670 | var paramMap = {}; 671 | var params = this.getLocation().search.substring(1).split('&'); 672 | for (var i = 0; i < params.length; i++) { 673 | var p = params[i].split('='); 674 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 675 | } 676 | 677 | if (!paramMap.spec) { 678 | return true; 679 | } 680 | return spec.getFullName().indexOf(paramMap.spec) === 0; 681 | }; 682 | -------------------------------------------------------------------------------- /test/lib/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 24 | #HTMLReporter .runningAlert { background-color: #666666; } 25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 28 | #HTMLReporter .passingAlert { background-color: #a6b779; } 29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 30 | #HTMLReporter .failingAlert { background-color: #cf867e; } 31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 32 | #HTMLReporter .results { margin-top: 14px; } 33 | #HTMLReporter #details { display: none; } 34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 38 | #HTMLReporter.showDetails .summary { display: none; } 39 | #HTMLReporter.showDetails #details { display: block; } 40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 41 | #HTMLReporter .summary { margin-top: 14px; } 42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 45 | #HTMLReporter .description + .suite { margin-top: 0; } 46 | #HTMLReporter .suite { margin-top: 14px; } 47 | #HTMLReporter .suite a { color: #333333; } 48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 51 | #HTMLReporter .resultMessage span.result { display: block; } 52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 53 | 54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 61 | #TrivialReporter .runner.running { background-color: yellow; } 62 | #TrivialReporter .options { text-align: right; font-size: .8em; } 63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 64 | #TrivialReporter .suite .suite { margin: 5px; } 65 | #TrivialReporter .suite.passed { background-color: #dfd; } 66 | #TrivialReporter .suite.failed { background-color: #fdd; } 67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 71 | #TrivialReporter .spec.skipped { background-color: #bbb; } 72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 73 | #TrivialReporter .passed { background-color: #cfc; display: none; } 74 | #TrivialReporter .failed { background-color: #fbb; } 75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 77 | #TrivialReporter .resultMessage .mismatch { color: black; } 78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 83 | -------------------------------------------------------------------------------- /test/lib/testloader.js: -------------------------------------------------------------------------------- 1 | function testLoader(){ 2 | var jasmineEnv = jasmine.getEnv(); 3 | jasmineEnv.updateInterval = 1000; 4 | 5 | var htmlReporter = new jasmine.HtmlReporter(); 6 | 7 | jasmineEnv.addReporter(htmlReporter); 8 | 9 | jasmineEnv.specFilter = function(spec) { 10 | return htmlReporter.specFilter(spec); 11 | }; 12 | 13 | var currentWindowOnload = window.onload; 14 | 15 | window.onload = function() { 16 | var count = 0; 17 | var loadCoffee = function(files) { 18 | for (var i = 0, len = files.length; i < len; i++) { 19 | count++; 20 | CoffeeScript.load(files[i], function() { 21 | count--; 22 | if (!count) { 23 | jasmine.getFixtures().fixturesPath = 'fixtures'; 24 | execJasmine(); 25 | } 26 | }); 27 | } 28 | }; 29 | 30 | if (currentWindowOnload) { 31 | currentWindowOnload(); 32 | } 33 | loadCoffee([ 34 | 'waypoints.coffee', 35 | 'infinite.coffee', 36 | 'sticky.coffee' 37 | ]); 38 | }; 39 | 40 | function execJasmine() { 41 | jasmineEnv.execute(); 42 | } 43 | 44 | if (document.readyState === 'complete'){ 45 | window.onload(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/settings.js: -------------------------------------------------------------------------------- 1 | /* global jasmine, Waypoint */ 2 | 3 | 'use strict' 4 | 5 | jasmine.getFixtures().fixturesPath = 'test/fixtures' 6 | jasmine.getEnv().defaultTimeoutInterval = 1000 7 | Waypoint.requestAnimationFrame = function(callback) { 8 | callback() 9 | } 10 | -------------------------------------------------------------------------------- /test/sticky-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global 3 | * describe, it, beforeEach, afterEach, expect, spyOn, waits, runs, 4 | * waitsFor, loadFixtures, Waypoint, jasmine 5 | */ 6 | 7 | describe('Waypoint Sticky Shortcut', function() { 8 | var $ = window.jQuery 9 | var $scroller = $(window) 10 | var $sticky, waypoint, handlerSpy 11 | 12 | beforeEach(function() { 13 | loadFixtures('sticky.html') 14 | $sticky = $('.sticky') 15 | }) 16 | 17 | describe('with default options', function() { 18 | beforeEach(function() { 19 | handlerSpy = jasmine.createSpy('on handler') 20 | waypoint = new Waypoint.Sticky({ 21 | element: $sticky[0], 22 | handler: handlerSpy 23 | }) 24 | }) 25 | 26 | afterEach(function() { 27 | if (waypoint) { 28 | waypoint.destroy() 29 | } 30 | $scroller.scrollTop(0) 31 | }) 32 | 33 | describe('on init', function() { 34 | afterEach(function() { 35 | waypoint.destroy() 36 | }) 37 | 38 | it('returns an instance of the Waypoint.Sticky class', function() { 39 | expect(waypoint instanceof Waypoint.Sticky).toBeTruthy() 40 | }) 41 | 42 | it('wraps the sticky element on init', function() { 43 | expect($sticky.parent()).toHaveClass('sticky-wrapper') 44 | }) 45 | 46 | describe('when sticky element is scrolled to', function() { 47 | beforeEach(function() { 48 | runs(function() { 49 | $scroller.scrollTop($sticky.offset().top) 50 | }) 51 | waitsFor(function() { 52 | return $sticky.hasClass('stuck') 53 | }, 'stuck class to apply') 54 | }) 55 | 56 | it('adds/removes stuck class', function() { 57 | runs(function() { 58 | $scroller.scrollTop($scroller.scrollTop() - 1) 59 | }) 60 | waitsFor(function() { 61 | return !$sticky.hasClass('stuck') 62 | }) 63 | }) 64 | 65 | it('gives the wrapper the same height as the sticky element', function() { 66 | expect($sticky.parent().height()).toEqual($sticky.outerHeight()) 67 | }) 68 | 69 | it('executes handler option after stuck class applied', function() { 70 | expect(handlerSpy).toHaveBeenCalled() 71 | }) 72 | }) 73 | }) 74 | 75 | describe('#destroy', function() { 76 | beforeEach(function() { 77 | runs(function() { 78 | $scroller.scrollTop($sticky.offset().top) 79 | }) 80 | waitsFor(function() { 81 | return handlerSpy.callCount 82 | }) 83 | runs(function() { 84 | waypoint.destroy() 85 | }) 86 | }) 87 | 88 | it('unwraps the sticky element', function() { 89 | expect($sticky.parent()).not.toHaveClass('sticky-wrapper') 90 | }) 91 | 92 | it('removes the stuck class', function() { 93 | expect($sticky).not.toHaveClass('stuck') 94 | }) 95 | }) 96 | }) 97 | 98 | describe('with wrapper false', function() { 99 | beforeEach(function() { 100 | waypoint = new Waypoint.Sticky({ 101 | element: $sticky[0], 102 | handler: handlerSpy, 103 | wrapper: false 104 | }) 105 | }) 106 | 107 | it('does not wrap the sticky element', function() { 108 | expect($sticky.parent()).not.toHaveClass('sticky-wrapper') 109 | }) 110 | 111 | it('does not unwrap on destroy', function() { 112 | var parent = waypoint.wrapper 113 | waypoint.destroy() 114 | expect(parent).toBe(waypoint.wrapper) 115 | }) 116 | }) 117 | }) 118 | -------------------------------------------------------------------------------- /test/waypoint-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global 3 | * describe, it, beforeEach, afterEach, expect, spyOn, waits, runs, 4 | * waitsFor, loadFixtures, Waypoint 5 | */ 6 | 7 | window.jQuery.each(Waypoint.adapters, function(i, adapter) { 8 | describe(adapter.name + ' adapter: ', function() { 9 | describe('Waypoint', function() { 10 | var $ = window.jQuery 11 | var standard = 50 12 | var hit, $scroller, waypoint, $target, returnValue 13 | 14 | function setHitTrue() { 15 | hit = true 16 | } 17 | 18 | function hitToBeTrue() { 19 | return hit 20 | } 21 | 22 | beforeEach(function() { 23 | Waypoint.Adapter = adapter.Adapter 24 | loadFixtures('standard.html') 25 | $scroller = $(window) 26 | hit = false 27 | waypoint = null 28 | }) 29 | 30 | afterEach(function() { 31 | Waypoint.destroyAll() 32 | $scroller.scrollTop(0).scrollLeft(0) 33 | }) 34 | 35 | describe('new Waypoint()', function() { 36 | it('errors out', function() { 37 | expect(function() { 38 | waypoint = new Waypoint() 39 | }).toThrow() 40 | }) 41 | }) 42 | 43 | describe('new Waypoint(options)', function() { 44 | it('returns an instance of the Waypoint class', function() { 45 | waypoint = new Waypoint({ 46 | element: document.getElementById('same1'), 47 | handler: function() {} 48 | }) 49 | expect(waypoint instanceof Waypoint).toBeTruthy() 50 | }) 51 | 52 | it('requires the element option', function() { 53 | expect(function() { 54 | waypoint = new Waypoint({ 55 | handler: function() {} 56 | }) 57 | }).toThrow() 58 | }) 59 | 60 | it('requires the handler option', function() { 61 | expect(function() { 62 | waypoint = new Waypoint({ 63 | element: document.getElementById('same1') 64 | }) 65 | }).toThrow() 66 | }) 67 | 68 | it('triggers down on new already-reached waypoints', function() { 69 | runs(function() { 70 | $target = $('#same2') 71 | $scroller.scrollTop($target.offset().top + 1) 72 | waypoint = new Waypoint({ 73 | element: $target[0], 74 | handler: function(direction) { 75 | hit = direction === 'down' 76 | } 77 | }) 78 | }) 79 | waitsFor(hitToBeTrue, 'callback to trigger') 80 | }) 81 | }) 82 | 83 | describe('handler option', function() { 84 | var currentDirection 85 | 86 | beforeEach(function() { 87 | $target = $('#same1') 88 | currentDirection = null 89 | waypoint = new Waypoint({ 90 | element: $target[0], 91 | handler: function(direction) { 92 | currentDirection = direction 93 | } 94 | }) 95 | }) 96 | 97 | it('triggers with direction parameter', function() { 98 | runs(function() { 99 | $scroller.scrollTop($target.offset().top) 100 | }) 101 | waitsFor(function() { 102 | return currentDirection === 'down' 103 | }, 'down to trigger') 104 | runs(function() { 105 | $scroller.scrollTop($target.offset().top - 1) 106 | }) 107 | waitsFor(function() { 108 | return currentDirection === 'up' 109 | }, 'up to trigger') 110 | }) 111 | }) 112 | 113 | describe('offset option', function() { 114 | beforeEach(function() { 115 | $target = $('#same1') 116 | }) 117 | 118 | it('takes a px offset', function(){ 119 | runs(function() { 120 | waypoint = new Waypoint({ 121 | element: $target[0], 122 | handler: setHitTrue, 123 | offset: 50 124 | }) 125 | $scroller.scrollTop($target.offset().top - 51) 126 | expect(hit).toBeFalsy() 127 | $scroller.scrollTop($target.offset().top - 50) 128 | }) 129 | waitsFor(hitToBeTrue, 'callback to trigger') 130 | }) 131 | 132 | it('takes a % offset', function() { 133 | var trigger = $target.offset().top - Waypoint.viewportHeight() * 0.37 134 | runs(function() { 135 | waypoint = new Waypoint({ 136 | element: $target[0], 137 | handler: setHitTrue, 138 | offset: '37%' 139 | }) 140 | $scroller.scrollTop(trigger - 1) 141 | expect(hit).toBeFalsy() 142 | $scroller.scrollTop(trigger) 143 | }) 144 | waitsFor(hitToBeTrue, 'callback to trigger') 145 | }) 146 | 147 | it('takes a function offset', function() { 148 | runs(function() { 149 | waypoint = new Waypoint({ 150 | element: $target[0], 151 | handler: setHitTrue, 152 | offset: function() { 153 | return -$(this.element).height() 154 | } 155 | }) 156 | $scroller.scrollTop($target.offset().top + $target.height() - 1) 157 | expect(hit).toBeFalsy() 158 | $scroller.scrollTop($target.offset().top + $target.height()) 159 | }) 160 | waitsFor(hitToBeTrue, 'callback to trigger') 161 | }) 162 | 163 | it('takes a bottom-in-view function alias', function() { 164 | var top = $target.offset().top 165 | var height = $target.outerHeight() 166 | var windowHeight = Waypoint.viewportHeight() 167 | var inview = top + height - windowHeight 168 | runs(function() { 169 | waypoint = new Waypoint({ 170 | element: $target[0], 171 | handler: setHitTrue, 172 | offset: 'bottom-in-view' 173 | }) 174 | $scroller.scrollTop(inview - 1) 175 | expect(hit).toBeFalsy() 176 | $scroller.scrollTop(inview) 177 | }) 178 | waitsFor(hitToBeTrue, 'callback to trigger') 179 | }) 180 | }) 181 | 182 | describe('context option', function() { 183 | beforeEach(function() { 184 | $scroller = $('#bottom') 185 | $target = $('#inner3') 186 | }) 187 | 188 | it('works with px offset', function() { 189 | waypoint = new Waypoint({ 190 | element: $target[0], 191 | handler: setHitTrue, 192 | context: $scroller[0], 193 | offset: 10 194 | }) 195 | runs(function() { 196 | $scroller.scrollTop(189) 197 | expect(hit).toBeFalsy() 198 | $scroller.scrollTop(190) 199 | }) 200 | waitsFor(hitToBeTrue, 'callback to trigger') 201 | }) 202 | 203 | it('works with % offset', function() { 204 | waypoint = new Waypoint({ 205 | element: $target[0], 206 | handler: setHitTrue, 207 | context: $scroller[0], 208 | offset: '100%' 209 | }) 210 | runs(function() { 211 | $scroller.scrollTop(149) 212 | expect(hit).toBeFalsy() 213 | $scroller.scrollTop(150) 214 | }) 215 | waitsFor(hitToBeTrue, 'callback to trigger') 216 | }) 217 | 218 | it('works with function offset', function() { 219 | waypoint = new Waypoint({ 220 | element: $target[0], 221 | handler: setHitTrue, 222 | context: $scroller[0], 223 | offset: function() { 224 | return $(this.element).height() / 2 225 | } 226 | }) 227 | runs(function() { 228 | $scroller.scrollTop(149) 229 | expect(hit).toBeFalsy() 230 | $scroller.scrollTop(150) 231 | }) 232 | waitsFor(hitToBeTrue, 'callback to trigger') 233 | }) 234 | 235 | it('works with bottom-in-view offset alias', function() { 236 | waypoint = new Waypoint({ 237 | element: $target[0], 238 | handler: setHitTrue, 239 | context: $scroller[0], 240 | offset: 'bottom-in-view' 241 | }) 242 | runs(function() { 243 | $scroller.scrollTop(249) 244 | expect(hit).toBeFalsy() 245 | $scroller.scrollTop(250) 246 | }) 247 | waitsFor(hitToBeTrue, 'callback to trigger') 248 | }) 249 | }) 250 | 251 | describe('horizontal option', function() { 252 | var currentDirection 253 | 254 | beforeEach(function() { 255 | currentDirection = null 256 | $target = $('#same1') 257 | waypoint = new Waypoint({ 258 | element: $target[0], 259 | horizontal: true, 260 | handler: function(direction) { 261 | currentDirection = direction 262 | } 263 | }) 264 | }) 265 | 266 | it('triggers right/left directions', function() { 267 | runs(function() { 268 | $scroller.scrollLeft($target.offset().left) 269 | }) 270 | waitsFor(function() { 271 | return currentDirection === 'right' 272 | }, 'right direction to trigger') 273 | runs(function() { 274 | $scroller.scrollLeft($target.offset().left - 1) 275 | }) 276 | waitsFor(function() { 277 | return currentDirection === 'left' 278 | }) 279 | }) 280 | }) 281 | 282 | describe('continuous option', function() { 283 | var $later, laterWaypoint, hitCount, hitWaypoint 284 | 285 | function incrementHitCount() { 286 | hitCount += 1 287 | hitWaypoint = this 288 | } 289 | 290 | beforeEach(function() { 291 | $target = $('#same1') 292 | $later = $('#near1') 293 | hitCount = 0 294 | waypoint = new Waypoint({ 295 | element: $target[0], 296 | continuous: false, 297 | handler: incrementHitCount 298 | }) 299 | laterWaypoint = new Waypoint({ 300 | element: $later[0], 301 | continuous: false, 302 | handler: incrementHitCount 303 | }) 304 | }) 305 | 306 | it('does not trigger the earlier waypoint', function() { 307 | runs(function() { 308 | $scroller.scrollTop($later.offset().top) 309 | }) 310 | waitsFor(function() { 311 | return hitCount 312 | }, 'later callback to trigger') 313 | runs(function() { 314 | expect(hitCount).toEqual(1) 315 | expect(hitWaypoint).toEqual(laterWaypoint) 316 | }) 317 | }) 318 | 319 | it('prevents earlier trigger on refresh', function() { 320 | runs(function() { 321 | $target.css('top', '-1px') 322 | $later.css('top', '-2px') 323 | Waypoint.refreshAll() 324 | }) 325 | waitsFor(function() { 326 | return hitCount 327 | }, 'later callback to trigger') 328 | runs(function() { 329 | expect(hitCount).toEqual(1) 330 | expect(hitWaypoint).toEqual(waypoint) 331 | }) 332 | }) 333 | }) 334 | 335 | describe('with window as the waypoint element', function() { 336 | beforeEach(function() { 337 | $target = $(window) 338 | waypoint = new Waypoint({ 339 | element: $target[0], 340 | offset: -$target.height(), 341 | handler: setHitTrue 342 | }) 343 | }) 344 | 345 | it('triggers waypoint', function() { 346 | runs(function() { 347 | $target.scrollTop($target.height() + 1) 348 | }) 349 | waitsFor(hitToBeTrue, 'callback to trigger') 350 | }) 351 | }) 352 | 353 | describe('#disable()', function() { 354 | beforeEach(function() { 355 | $target = $('#same1') 356 | waypoint = new Waypoint({ 357 | element: $target[0], 358 | handler: setHitTrue 359 | }) 360 | returnValue = waypoint.disable() 361 | }) 362 | 363 | it('returns the same waypoint object for chaining', function() { 364 | expect(returnValue).toEqual(waypoint) 365 | }) 366 | 367 | it('disables callback triggers', function() { 368 | runs(function() { 369 | $scroller.scrollTop($target.offset().top) 370 | }) 371 | waits(standard) 372 | runs(function() { 373 | expect(hit).toBeFalsy() 374 | }) 375 | }) 376 | }) 377 | 378 | describe('#enable()', function() { 379 | beforeEach(function() { 380 | $target = $('#same1') 381 | waypoint = new Waypoint({ 382 | element: $target[0], 383 | handler: setHitTrue 384 | }) 385 | waypoint.disable() 386 | returnValue = waypoint.enable() 387 | }) 388 | 389 | it('returns the same waypoint instance for chaining', function() { 390 | expect(returnValue).toEqual(waypoint) 391 | }) 392 | 393 | it('enables callback triggers', function() { 394 | runs(function() { 395 | $scroller.scrollTop($target.offset().top) 396 | }) 397 | waitsFor(hitToBeTrue, 'callback to trigger') 398 | }) 399 | }) 400 | 401 | describe('#destroy()', function() { 402 | beforeEach(function() { 403 | $target = $('#same1') 404 | waypoint = new Waypoint({ 405 | element: $target[0], 406 | handler: setHitTrue 407 | }) 408 | returnValue = waypoint.destroy() 409 | }) 410 | 411 | it('returns undefined', function() { 412 | expect(returnValue).toBeUndefined() 413 | }) 414 | 415 | it('no longer triggers callbacks', function() { 416 | runs(function() { 417 | $scroller.scrollTop($target.offset().top) 418 | }) 419 | waits(standard) 420 | runs(function() { 421 | expect(hit).toBeFalsy() 422 | }) 423 | }) 424 | }) 425 | 426 | describe('#previous', function() { 427 | beforeEach(function() { 428 | $target = $('#same1') 429 | waypoint = new Waypoint({ 430 | element: $target[0], 431 | handler: setHitTrue 432 | }) 433 | }) 434 | 435 | it('calls previous on the waypoint group', function() { 436 | spyOn(waypoint.group, 'previous') 437 | waypoint.previous() 438 | expect(waypoint.group.previous).toHaveBeenCalledWith(waypoint) 439 | }) 440 | 441 | it('returns the group call results', function() { 442 | expect(waypoint.previous()).toBeNull() 443 | }) 444 | }) 445 | 446 | describe('#next', function() { 447 | beforeEach(function() { 448 | $target = $('#same1') 449 | waypoint = new Waypoint({ 450 | element: $target[0], 451 | handler: setHitTrue 452 | }) 453 | }) 454 | 455 | it('calls next on the waypoint group', function() { 456 | spyOn(waypoint.group, 'next') 457 | waypoint.next() 458 | expect(waypoint.group.next).toHaveBeenCalledWith(waypoint) 459 | }) 460 | 461 | it('returns the group call results', function() { 462 | expect(waypoint.next()).toBeNull() 463 | }) 464 | }) 465 | 466 | describe('Waypoint.viewportHeight()', function() { 467 | it('returns window innerHeight if it exists', function() { 468 | var height = Waypoint.viewportHeight() 469 | if (window.innerHeight) { 470 | expect(height).toEqual(window.innerHeight) 471 | } 472 | else { 473 | expect(height).toEqual(document.documentElement.clientHeight) 474 | } 475 | }) 476 | }) 477 | 478 | describe('Waypoint.viewportWidth()', function() { 479 | it('returns client width', function() { 480 | var clientWidth = document.documentElement.clientWidth 481 | expect(Waypoint.viewportWidth()).toEqual(clientWidth) 482 | }) 483 | }) 484 | 485 | describe('Waypoint.refreshAll()', function() { 486 | it('is an alias for Waypoint.Context.refreshAll', function() { 487 | spyOn(Waypoint.Context, 'refreshAll') 488 | Waypoint.refreshAll() 489 | expect(Waypoint.Context.refreshAll).toHaveBeenCalled() 490 | }) 491 | }) 492 | 493 | describe('Waypoint.destroyAll()', function() { 494 | it('calls destroy on all waypoints', function() { 495 | var secondWaypoint = new Waypoint({ 496 | element: $('#same1')[0], 497 | handler: function() {} 498 | }) 499 | waypoint = new Waypoint({ 500 | element: $('#same1')[0], 501 | handler: function() {} 502 | }) 503 | spyOn(secondWaypoint, 'destroy').andCallThrough() 504 | spyOn(waypoint, 'destroy').andCallThrough() 505 | Waypoint.destroyAll() 506 | expect(secondWaypoint.destroy).toHaveBeenCalled() 507 | expect(waypoint.destroy).toHaveBeenCalled() 508 | }) 509 | }) 510 | 511 | describe('Waypoint.disableAll()', function() { 512 | it('calls disable on all waypoints', function() { 513 | var secondWaypoint = new Waypoint({ 514 | element: $('#same1')[0], 515 | handler: function() {} 516 | }) 517 | waypoint = new Waypoint({ 518 | element: $('#same1')[0], 519 | handler: function() {} 520 | }) 521 | spyOn(secondWaypoint, 'disable').andCallThrough() 522 | spyOn(waypoint, 'disable').andCallThrough() 523 | Waypoint.disableAll() 524 | expect(secondWaypoint.disable).toHaveBeenCalled() 525 | expect(waypoint.disable).toHaveBeenCalled() 526 | }) 527 | }) 528 | 529 | describe('Waypoint.enableAll()', function() { 530 | var secondWaypoint 531 | 532 | beforeEach(function() { 533 | secondWaypoint = new Waypoint({ 534 | element: $('#same1')[0], 535 | handler: function() {} 536 | }) 537 | waypoint = new Waypoint({ 538 | element: $('#same1')[0], 539 | handler: function() {} 540 | }) 541 | Waypoint.disableAll() 542 | spyOn(Waypoint.Context, 'refreshAll').andCallThrough() 543 | Waypoint.enableAll() 544 | }) 545 | 546 | it('sets enabled on all waypoints', function() { 547 | expect(secondWaypoint.enabled).toBeTruthy() 548 | expect(waypoint.enabled).toBeTruthy() 549 | }) 550 | 551 | it('refreshes all contexts', function() { 552 | expect(Waypoint.Context.refreshAll).toHaveBeenCalled() 553 | }) 554 | }) 555 | }) 556 | }) 557 | }) 558 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "jasmine", 3 | "launch_in_dev": [ 4 | "PhantomJS" 5 | ], 6 | "launch_in_ci": [ 7 | "PhantomJS" 8 | ], 9 | "src_files": [ 10 | "bower_components/jquery/dist/jquery.js", 11 | "bower_components/jasmine-jquery/lib/jasmine-jquery.js", 12 | "bower_components/zepto/zepto.js", 13 | 14 | "src/waypoint.js", 15 | "src/context.js", 16 | "src/group.js", 17 | "src/debug.js", 18 | "src/adapters/*.js", 19 | "src/shortcuts/*.js", 20 | 21 | "test/settings.js", 22 | "test/*-spec.js" 23 | ] 24 | } 25 | --------------------------------------------------------------------------------