waypoints.js (17964B)
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.elementorWaypoint = createExtension(window.jQuery) 657 } 658 if (window.Zepto) { 659 window.Zepto.fn.elementorWaypoint = createExtension(window.Zepto) 660 } 661 }()) 662 ;