jquery.waypoints.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. /*!
  2. Waypoints - 3.1.1
  3. Copyright © 2011-2015 Caleb Troughton
  4. Licensed under the MIT license.
  5. https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
  6. */
  7. (function() {
  8. 'use strict'
  9. var keyCounter = 0
  10. var allWaypoints = {}
  11. /* http://imakewebthings.com/waypoints/api/waypoint */
  12. function Waypoint(options) {
  13. if ( ! options) {
  14. throw new Error( 'No options passed to Waypoint constructor' )
  15. }
  16. if ( ! options.element) {
  17. throw new Error( 'No element option passed to Waypoint constructor' )
  18. }
  19. if ( ! options.handler) {
  20. throw new Error( 'No handler option passed to Waypoint constructor' )
  21. }
  22. this.key = 'waypoint-' + keyCounter
  23. this.options = Waypoint.Adapter.extend( {}, Waypoint.defaults, options )
  24. this.element = this.options.element
  25. this.adapter = new Waypoint.Adapter( this.element )
  26. this.callback = options.handler
  27. this.axis = this.options.horizontal ? 'horizontal' : 'vertical'
  28. this.enabled = this.options.enabled
  29. this.triggerPoint = null
  30. this.group = Waypoint.Group.findOrCreate({
  31. name: this.options.group,
  32. axis: this.axis
  33. })
  34. this.context = Waypoint.Context.findOrCreateByElement( this.options.context )
  35. if (Waypoint.offsetAliases[this.options.offset]) {
  36. this.options.offset = Waypoint.offsetAliases[this.options.offset]
  37. }
  38. this.group.add( this )
  39. this.context.add( this )
  40. allWaypoints[this.key] = this
  41. keyCounter += 1
  42. }
  43. /* Private */
  44. Waypoint.prototype.queueTrigger = function(direction) {
  45. this.group.queueTrigger( this, direction )
  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. /* Public */
  57. /* http://imakewebthings.com/waypoints/api/destroy */
  58. Waypoint.prototype.destroy = function() {
  59. this.context.remove( this )
  60. this.group.remove( this )
  61. delete allWaypoints[this.key]
  62. }
  63. /* Public */
  64. /* http://imakewebthings.com/waypoints/api/disable */
  65. Waypoint.prototype.disable = function() {
  66. this.enabled = false
  67. return this
  68. }
  69. /* Public */
  70. /* http://imakewebthings.com/waypoints/api/enable */
  71. Waypoint.prototype.enable = function() {
  72. this.context.refresh()
  73. this.enabled = true
  74. return this
  75. }
  76. /* Public */
  77. /* http://imakewebthings.com/waypoints/api/next */
  78. Waypoint.prototype.next = function() {
  79. return this.group.next( this )
  80. }
  81. /* Public */
  82. /* http://imakewebthings.com/waypoints/api/previous */
  83. Waypoint.prototype.previous = function() {
  84. return this.group.previous( this )
  85. }
  86. /* Private */
  87. Waypoint.invokeAll = function(method) {
  88. var allWaypointsArray = []
  89. for (var waypointKey in allWaypoints) {
  90. allWaypointsArray.push( allWaypoints[waypointKey] )
  91. }
  92. for (var i = 0, end = allWaypointsArray.length; i < end; i++) {
  93. allWaypointsArray[i][method]()
  94. }
  95. }
  96. /* Public */
  97. /* http://imakewebthings.com/waypoints/api/destroy-all */
  98. Waypoint.destroyAll = function() {
  99. Waypoint.invokeAll( 'destroy' )
  100. }
  101. /* Public */
  102. /* http://imakewebthings.com/waypoints/api/disable-all */
  103. Waypoint.disableAll = function() {
  104. Waypoint.invokeAll( 'disable' )
  105. }
  106. /* Public */
  107. /* http://imakewebthings.com/waypoints/api/enable-all */
  108. Waypoint.enableAll = function() {
  109. Waypoint.invokeAll( 'enable' )
  110. }
  111. /* Public */
  112. /* http://imakewebthings.com/waypoints/api/refresh-all */
  113. Waypoint.refreshAll = function() {
  114. Waypoint.Context.refreshAll()
  115. }
  116. /* Public */
  117. /* http://imakewebthings.com/waypoints/api/viewport-height */
  118. Waypoint.viewportHeight = function() {
  119. return window.innerHeight || document.documentElement.clientHeight
  120. }
  121. /* Public */
  122. /* http://imakewebthings.com/waypoints/api/viewport-width */
  123. Waypoint.viewportWidth = function() {
  124. return document.documentElement.clientWidth
  125. }
  126. Waypoint.adapters = []
  127. Waypoint.defaults = {
  128. context: window,
  129. continuous: true,
  130. enabled: true,
  131. group: 'default',
  132. horizontal: false,
  133. offset: 0
  134. }
  135. Waypoint.offsetAliases = {
  136. 'bottom-in-view': function() {
  137. return this.context.innerHeight() - this.adapter.outerHeight()
  138. },
  139. 'right-in-view': function() {
  140. return this.context.innerWidth() - this.adapter.outerWidth()
  141. }
  142. }
  143. window.Waypoint = Waypoint
  144. }())
  145. ;(function() {
  146. 'use strict'
  147. function requestAnimationFrameShim(callback) {
  148. window.setTimeout( callback, 1000 / 60 )
  149. }
  150. var keyCounter = 0
  151. var contexts = {}
  152. var Waypoint = window.Waypoint
  153. var oldWindowLoad = window.onload
  154. /* http://imakewebthings.com/waypoints/api/context */
  155. function Context(element) {
  156. this.element = element
  157. this.Adapter = Waypoint.Adapter
  158. this.adapter = new this.Adapter( element )
  159. this.key = 'waypoint-context-' + keyCounter
  160. this.didScroll = false
  161. this.didResize = false
  162. this.oldScroll = {
  163. x: this.adapter.scrollLeft(),
  164. y: this.adapter.scrollTop()
  165. }
  166. this.waypoints = {
  167. vertical: {},
  168. horizontal: {}
  169. }
  170. element.waypointContextKey = this.key
  171. contexts[element.waypointContextKey] = this
  172. keyCounter += 1
  173. this.createThrottledScrollHandler()
  174. this.createThrottledResizeHandler()
  175. }
  176. /* Private */
  177. Context.prototype.add = function(waypoint) {
  178. var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical'
  179. this.waypoints[axis][waypoint.key] = waypoint
  180. this.refresh()
  181. }
  182. /* Private */
  183. Context.prototype.checkEmpty = function() {
  184. var horizontalEmpty = this.Adapter.isEmptyObject( this.waypoints.horizontal )
  185. var verticalEmpty = this.Adapter.isEmptyObject( this.waypoints.vertical )
  186. if (horizontalEmpty && verticalEmpty) {
  187. this.adapter.off( '.waypoints' )
  188. delete contexts[this.key]
  189. }
  190. }
  191. /* Private */
  192. Context.prototype.createThrottledResizeHandler = function() {
  193. var self = this
  194. function resizeHandler() {
  195. self.handleResize()
  196. self.didResize = false
  197. }
  198. this.adapter.on('resize.waypoints', function() {
  199. if ( ! self.didResize) {
  200. self.didResize = true
  201. Waypoint.requestAnimationFrame( resizeHandler )
  202. }
  203. })
  204. }
  205. /* Private */
  206. Context.prototype.createThrottledScrollHandler = function() {
  207. var self = this
  208. function scrollHandler() {
  209. self.handleScroll()
  210. self.didScroll = false
  211. }
  212. this.adapter.on('scroll.waypoints', function() {
  213. if ( ! self.didScroll || Waypoint.isTouch) {
  214. self.didScroll = true
  215. Waypoint.requestAnimationFrame( scrollHandler )
  216. }
  217. })
  218. }
  219. /* Private */
  220. Context.prototype.handleResize = function() {
  221. Waypoint.Context.refreshAll()
  222. }
  223. /* Private */
  224. Context.prototype.handleScroll = function() {
  225. var triggeredGroups = {}
  226. var axes = {
  227. horizontal: {
  228. newScroll: this.adapter.scrollLeft(),
  229. oldScroll: this.oldScroll.x,
  230. forward: 'right',
  231. backward: 'left'
  232. },
  233. vertical: {
  234. newScroll: this.adapter.scrollTop(),
  235. oldScroll: this.oldScroll.y,
  236. forward: 'down',
  237. backward: 'up'
  238. }
  239. }
  240. for (var axisKey in axes) {
  241. var axis = axes[axisKey]
  242. var isForward = axis.newScroll > axis.oldScroll
  243. var direction = isForward ? axis.forward : axis.backward
  244. for (var waypointKey in this.waypoints[axisKey]) {
  245. var waypoint = this.waypoints[axisKey][waypointKey]
  246. var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint
  247. var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint
  248. var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint
  249. var crossedBackward = ! wasBeforeTriggerPoint && ! nowAfterTriggerPoint
  250. if (crossedForward || crossedBackward) {
  251. waypoint.queueTrigger( direction )
  252. triggeredGroups[waypoint.group.id] = waypoint.group
  253. }
  254. }
  255. }
  256. for (var groupKey in triggeredGroups) {
  257. triggeredGroups[groupKey].flushTriggers()
  258. }
  259. this.oldScroll = {
  260. x: axes.horizontal.newScroll,
  261. y: axes.vertical.newScroll
  262. }
  263. }
  264. /* Private */
  265. Context.prototype.innerHeight = function() {
  266. /*eslint-disable eqeqeq */
  267. if (this.element == this.element.window) {
  268. return Waypoint.viewportHeight()
  269. }
  270. /*eslint-enable eqeqeq */
  271. return this.adapter.innerHeight()
  272. }
  273. /* Private */
  274. Context.prototype.remove = function(waypoint) {
  275. delete this.waypoints[waypoint.axis][waypoint.key]
  276. this.checkEmpty()
  277. }
  278. /* Private */
  279. Context.prototype.innerWidth = function() {
  280. /*eslint-disable eqeqeq */
  281. if (this.element == this.element.window) {
  282. return Waypoint.viewportWidth()
  283. }
  284. /*eslint-enable eqeqeq */
  285. return this.adapter.innerWidth()
  286. }
  287. /* Public */
  288. /* http://imakewebthings.com/waypoints/api/context-destroy */
  289. Context.prototype.destroy = function() {
  290. var allWaypoints = []
  291. for (var axis in this.waypoints) {
  292. for (var waypointKey in this.waypoints[axis]) {
  293. allWaypoints.push( this.waypoints[axis][waypointKey] )
  294. }
  295. }
  296. for (var i = 0, end = allWaypoints.length; i < end; i++) {
  297. allWaypoints[i].destroy()
  298. }
  299. }
  300. /* Public */
  301. /* http://imakewebthings.com/waypoints/api/context-refresh */
  302. Context.prototype.refresh = function() {
  303. /*eslint-disable eqeqeq */
  304. var isWindow = this.element == this.element.window
  305. /*eslint-enable eqeqeq */
  306. var contextOffset = this.adapter.offset()
  307. var triggeredGroups = {}
  308. var axes
  309. this.handleScroll()
  310. axes = {
  311. horizontal: {
  312. contextOffset: isWindow ? 0 : contextOffset.left,
  313. contextScroll: isWindow ? 0 : this.oldScroll.x,
  314. contextDimension: this.innerWidth(),
  315. oldScroll: this.oldScroll.x,
  316. forward: 'right',
  317. backward: 'left',
  318. offsetProp: 'left'
  319. },
  320. vertical: {
  321. contextOffset: isWindow ? 0 : contextOffset.top,
  322. contextScroll: isWindow ? 0 : this.oldScroll.y,
  323. contextDimension: this.innerHeight(),
  324. oldScroll: this.oldScroll.y,
  325. forward: 'down',
  326. backward: 'up',
  327. offsetProp: 'top'
  328. }
  329. }
  330. for (var axisKey in axes) {
  331. var axis = axes[axisKey]
  332. for (var waypointKey in this.waypoints[axisKey]) {
  333. var waypoint = this.waypoints[axisKey][waypointKey]
  334. var adjustment = waypoint.options.offset
  335. var oldTriggerPoint = waypoint.triggerPoint
  336. var elementOffset = 0
  337. var freshWaypoint = oldTriggerPoint == null
  338. var contextModifier, wasBeforeScroll, nowAfterScroll
  339. var triggeredBackward, triggeredForward
  340. if (waypoint.element !== waypoint.element.window) {
  341. elementOffset = waypoint.adapter.offset()[axis.offsetProp]
  342. }
  343. if (typeof adjustment === 'function') {
  344. adjustment = adjustment.apply( waypoint )
  345. } else if (typeof adjustment === 'string') {
  346. adjustment = parseFloat( adjustment )
  347. if (waypoint.options.offset.indexOf( '%' ) > - 1) {
  348. adjustment = Math.ceil( axis.contextDimension * adjustment / 100 )
  349. }
  350. }
  351. contextModifier = axis.contextScroll - axis.contextOffset
  352. waypoint.triggerPoint = elementOffset + contextModifier - adjustment
  353. wasBeforeScroll = oldTriggerPoint < axis.oldScroll
  354. nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll
  355. triggeredBackward = wasBeforeScroll && nowAfterScroll
  356. triggeredForward = ! wasBeforeScroll && ! nowAfterScroll
  357. if ( ! freshWaypoint && triggeredBackward) {
  358. waypoint.queueTrigger( axis.backward )
  359. triggeredGroups[waypoint.group.id] = waypoint.group
  360. } else if ( ! freshWaypoint && triggeredForward) {
  361. waypoint.queueTrigger( axis.forward )
  362. triggeredGroups[waypoint.group.id] = waypoint.group
  363. } else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) {
  364. waypoint.queueTrigger( axis.forward )
  365. triggeredGroups[waypoint.group.id] = waypoint.group
  366. }
  367. }
  368. }
  369. for (var groupKey in triggeredGroups) {
  370. triggeredGroups[groupKey].flushTriggers()
  371. }
  372. return this
  373. }
  374. /* Private */
  375. Context.findOrCreateByElement = function(element) {
  376. return Context.findByElement( element ) || new Context( element )
  377. }
  378. /* Private */
  379. Context.refreshAll = function() {
  380. for (var contextId in contexts) {
  381. contexts[contextId].refresh()
  382. }
  383. }
  384. /* Public */
  385. /* http://imakewebthings.com/waypoints/api/context-find-by-element */
  386. Context.findByElement = function(element) {
  387. return contexts[element.waypointContextKey]
  388. }
  389. window.onload = function() {
  390. if (oldWindowLoad) {
  391. oldWindowLoad()
  392. }
  393. Context.refreshAll()
  394. }
  395. Waypoint.requestAnimationFrame = function(callback) {
  396. var requestFn = window.requestAnimationFrame ||
  397. window.mozRequestAnimationFrame ||
  398. window.webkitRequestAnimationFrame ||
  399. requestAnimationFrameShim
  400. requestFn.call( window, callback )
  401. }
  402. Waypoint.Context = Context
  403. }())
  404. ;(function() {
  405. 'use strict'
  406. function byTriggerPoint(a, b) {
  407. return a.triggerPoint - b.triggerPoint
  408. }
  409. function byReverseTriggerPoint(a, b) {
  410. return b.triggerPoint - a.triggerPoint
  411. }
  412. var groups = {
  413. vertical: {},
  414. horizontal: {}
  415. }
  416. var Waypoint = window.Waypoint
  417. /* http://imakewebthings.com/waypoints/api/group */
  418. function Group(options) {
  419. this.name = options.name
  420. this.axis = options.axis
  421. this.id = this.name + '-' + this.axis
  422. this.waypoints = []
  423. this.clearTriggerQueues()
  424. groups[this.axis][this.name] = this
  425. }
  426. /* Private */
  427. Group.prototype.add = function(waypoint) {
  428. this.waypoints.push( waypoint )
  429. }
  430. /* Private */
  431. Group.prototype.clearTriggerQueues = function() {
  432. this.triggerQueues = {
  433. up: [],
  434. down: [],
  435. left: [],
  436. right: []
  437. }
  438. }
  439. /* Private */
  440. Group.prototype.flushTriggers = function() {
  441. for (var direction in this.triggerQueues) {
  442. var waypoints = this.triggerQueues[direction]
  443. var reverse = direction === 'up' || direction === 'left'
  444. waypoints.sort( reverse ? byReverseTriggerPoint : byTriggerPoint )
  445. for (var i = 0, end = waypoints.length; i < end; i += 1) {
  446. var waypoint = waypoints[i]
  447. if (waypoint.options.continuous || i === waypoints.length - 1) {
  448. waypoint.trigger( [direction] )
  449. }
  450. }
  451. }
  452. this.clearTriggerQueues()
  453. }
  454. /* Private */
  455. Group.prototype.next = function(waypoint) {
  456. this.waypoints.sort( byTriggerPoint )
  457. var index = Waypoint.Adapter.inArray( waypoint, this.waypoints )
  458. var isLast = index === this.waypoints.length - 1
  459. return isLast ? null : this.waypoints[index + 1]
  460. }
  461. /* Private */
  462. Group.prototype.previous = function(waypoint) {
  463. this.waypoints.sort( byTriggerPoint )
  464. var index = Waypoint.Adapter.inArray( waypoint, this.waypoints )
  465. return index ? this.waypoints[index - 1] : null
  466. }
  467. /* Private */
  468. Group.prototype.queueTrigger = function(waypoint, direction) {
  469. this.triggerQueues[direction].push( waypoint )
  470. }
  471. /* Private */
  472. Group.prototype.remove = function(waypoint) {
  473. var index = Waypoint.Adapter.inArray( waypoint, this.waypoints )
  474. if (index > -1) {
  475. this.waypoints.splice( index, 1 )
  476. }
  477. }
  478. /* Public */
  479. /* http://imakewebthings.com/waypoints/api/first */
  480. Group.prototype.first = function() {
  481. return this.waypoints[0]
  482. }
  483. /* Public */
  484. /* http://imakewebthings.com/waypoints/api/last */
  485. Group.prototype.last = function() {
  486. return this.waypoints[this.waypoints.length - 1]
  487. }
  488. /* Private */
  489. Group.findOrCreate = function(options) {
  490. return groups[options.axis][options.name] || new Group( options )
  491. }
  492. Waypoint.Group = Group
  493. }())
  494. ;(function() {
  495. 'use strict'
  496. var $ = window.jQuery
  497. var Waypoint = window.Waypoint
  498. function JQueryAdapter(element) {
  499. this.$element = $( element )
  500. }
  501. $.each([
  502. 'innerHeight',
  503. 'innerWidth',
  504. 'off',
  505. 'offset',
  506. 'on',
  507. 'outerHeight',
  508. 'outerWidth',
  509. 'scrollLeft',
  510. 'scrollTop'
  511. ], function(i, method) {
  512. JQueryAdapter.prototype[method] = function() {
  513. var args = Array.prototype.slice.call( arguments )
  514. return this.$element[method].apply( this.$element, args )
  515. }
  516. })
  517. $.each([
  518. 'extend',
  519. 'inArray',
  520. 'isEmptyObject'
  521. ], function(i, method) {
  522. JQueryAdapter[method] = $[method]
  523. })
  524. Waypoint.adapters.push({
  525. name: 'jquery',
  526. Adapter: JQueryAdapter
  527. })
  528. Waypoint.Adapter = JQueryAdapter
  529. }())
  530. ;(function() {
  531. 'use strict'
  532. var Waypoint = window.Waypoint
  533. function createExtension(framework) {
  534. return function() {
  535. var waypoints = []
  536. var overrides = arguments[0]
  537. if (framework.isFunction( arguments[0] )) {
  538. overrides = framework.extend( {}, arguments[1] )
  539. overrides.handler = arguments[0]
  540. }
  541. this.each(function() {
  542. var options = framework.extend({}, overrides, {
  543. element: this
  544. })
  545. if (typeof options.context === 'string') {
  546. options.context = framework( this ).closest( options.context )[0]
  547. }
  548. waypoints.push( new Waypoint( options ) )
  549. })
  550. return waypoints
  551. }
  552. }
  553. if (window.jQuery) {
  554. window.jQuery.fn.waypoint = createExtension( window.jQuery )
  555. }
  556. if (window.Zepto) {
  557. window.Zepto.fn.waypoint = createExtension( window.Zepto )
  558. }
  559. }())
  560. ;