jquery.waypoints.js 18 KB

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