2
YUI 3.10.3 (build 2fb5187)
3
Copyright 2013 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
8
YUI.add('node-scroll-info', function (Y, NAME) {
11
Provides the ScrollInfo Node plugin, which exposes convenient events and methods
14
@module node-scroll-info
19
Provides convenient events and methods related to scrolling. This could be used,
20
for example, to implement infinite scrolling, or to lazy-load content based on
21
the current scroll position.
25
var body = Y.one('body');
27
body.plug(Y.Plugin.ScrollInfo);
29
body.scrollInfo.on('scrollToBottom', function (e) {
30
// Load more content when the user scrolls to the bottom of the page.
33
@class Plugin.ScrollInfo
39
Fired when the user scrolls within the host node.
41
This event (like all scroll events exposed by ScrollInfo) is throttled and fired
42
only after the number of milliseconds specified by the `scrollDelay` attribute
43
have passed in order to prevent thrashing.
45
This event passes along the event facade for the standard DOM `scroll` event and
46
mixes in the following additional properties.
49
@param {Boolean} atBottom Whether the current scroll position is at the bottom
50
of the scrollable region.
51
@param {Boolean} atLeft Whether the current scroll position is at the extreme
52
left of the scrollable region.
53
@param {Boolean} atRight Whether the current scroll position is at the extreme
54
right of the scrollable region.
55
@param {Boolean} atTop Whether the current scroll position is at the top of the
57
@param {Boolean} isScrollDown `true` if the user scrolled down.
58
@param {Boolean} isScrollLeft `true` if the user scrolled left.
59
@param {Boolean} isScrollRight `true` if the user scrolled right.
60
@param {Boolean} isScrollUp `true` if the user scrolled up.
61
@param {Number} scrollBottom Y value of the bottom-most onscreen pixel of the
63
@param {Number} scrollHeight Total height in pixels of the scrollable region,
64
including offscreen pixels.
65
@param {Number} scrollLeft X value of the left-most onscreen pixel of the
67
@param {Number} scrollRight X value of the right-most onscreen pixel of the
69
@param {Number} scrollTop Y value of the top-most onscreen pixel of the
71
@param {Number} scrollWidth Total width in pixels of the scrollable region,
72
including offscreen pixels.
76
var EVT_SCROLL = 'scroll',
79
Fired when the user scrolls down within the host node.
81
This event provides the same event facade as the `scroll` event. See that
87
EVT_SCROLL_DOWN = 'scrollDown',
90
Fired when the user scrolls left within the host node.
92
This event provides the same event facade as the `scroll` event. See that
98
EVT_SCROLL_LEFT = 'scrollLeft',
101
Fired when the user scrolls right within the host node.
103
This event provides the same event facade as the `scroll` event. See that
109
EVT_SCROLL_RIGHT = 'scrollRight',
112
Fired when the user scrolls up within the host node.
114
This event provides the same event facade as the `scroll` event. See that
120
EVT_SCROLL_UP = 'scrollUp',
123
Fired when the user scrolls to the bottom of the scrollable region within
126
This event provides the same event facade as the `scroll` event. See that
129
@event scrollToBottom
132
EVT_SCROLL_TO_BOTTOM = 'scrollToBottom',
135
Fired when the user scrolls to the extreme left of the scrollable region
136
within the host node.
138
This event provides the same event facade as the `scroll` event. See that
144
EVT_SCROLL_TO_LEFT = 'scrollToLeft',
147
Fired when the user scrolls to the extreme right of the scrollable region
148
within the host node.
150
This event provides the same event facade as the `scroll` event. See that
156
EVT_SCROLL_TO_RIGHT = 'scrollToRight',
159
Fired when the user scrolls to the top of the scrollable region within the
162
This event provides the same event facade as the `scroll` event. See that
168
EVT_SCROLL_TO_TOP = 'scrollToTop';
170
Y.Plugin.ScrollInfo = Y.Base.create('scrollInfoPlugin', Y.Plugin.Base, [], {
171
// -- Lifecycle Methods ----------------------------------------------------
172
initializer: function (config) {
173
// Cache for quicker lookups in the critical path.
174
this._host = config.host;
175
this._hostIsBody = this._host.get('nodeName').toLowerCase() === 'body';
176
this._scrollDelay = this.get('scrollDelay');
177
this._scrollMargin = this.get('scrollMargin');
178
this._scrollNode = this._getScrollNode();
180
this.refreshDimensions();
182
this._lastScroll = this.getScrollInfo();
187
destructor: function () {
188
(new Y.EventHandle(this._events)).detach();
192
// -- Public Methods -------------------------------------------------------
195
Returns a NodeList containing all offscreen nodes inside the host node that
196
match the given CSS selector. An offscreen node is any node that is entirely
197
outside the visible (onscreen) region of the host node based on the current
200
@method getOffscreenNodes
201
@param {String} [selector] CSS selector. If omitted, all offscreen nodes
203
@param {Number} [margin] Additional margin in pixels beyond the actual
204
onscreen region that should be considered "onscreen" for the purposes of
205
this query. Defaults to the value of the `scrollMargin` attribute.
206
@return {NodeList} Offscreen nodes matching _selector_.
209
getOffscreenNodes: function (selector, margin) {
210
if (typeof margin === 'undefined') {
211
margin = this._scrollMargin;
214
var lastScroll = this._lastScroll,
215
nodes = this._host.all(selector || '*'),
217
scrollBottom = lastScroll.scrollBottom + margin,
218
scrollLeft = lastScroll.scrollLeft - margin,
219
scrollRight = lastScroll.scrollRight + margin,
220
scrollTop = lastScroll.scrollTop - margin,
224
return nodes.filter(function (el) {
225
var xy = Y.DOM.getXY(el),
226
elLeft = xy[0] - self._left,
227
elTop = xy[1] - self._top,
230
// Check whether the element's top left point is within the
231
// viewport. This is the least expensive check.
232
if (elLeft >= scrollLeft && elLeft < scrollRight &&
233
elTop >= scrollTop && elTop < scrollBottom) {
238
// Check whether the element's bottom right point is within the
239
// viewport. This check is more expensive since we have to get the
240
// element's height and width.
241
elBottom = elTop + el.offsetHeight;
242
elRight = elLeft + el.offsetWidth;
244
if (elRight < scrollRight && elRight >= scrollLeft &&
245
elBottom < scrollBottom && elBottom >= scrollTop) {
250
// If we get here, the element isn't within the viewport.
256
Returns a NodeList containing all onscreen nodes inside the host node that
257
match the given CSS selector. An onscreen node is any node that is fully or
258
partially within the visible (onscreen) region of the host node based on the
259
current scroll location.
261
@method getOnscreenNodes
262
@param {String} [selector] CSS selector. If omitted, all onscreen nodes will
264
@param {Number} [margin] Additional margin in pixels beyond the actual
265
onscreen region that should be considered "onscreen" for the purposes of
266
this query. Defaults to the value of the `scrollMargin` attribute.
267
@return {NodeList} Onscreen nodes matching _selector_.
270
getOnscreenNodes: function (selector, margin) {
271
if (typeof margin === 'undefined') {
272
margin = this._scrollMargin;
275
var lastScroll = this._lastScroll,
276
nodes = this._host.all(selector || '*'),
278
scrollBottom = lastScroll.scrollBottom + margin,
279
scrollLeft = lastScroll.scrollLeft - margin,
280
scrollRight = lastScroll.scrollRight + margin,
281
scrollTop = lastScroll.scrollTop - margin,
285
return nodes.filter(function (el) {
286
var xy = Y.DOM.getXY(el),
287
elLeft = xy[0] - self._left,
288
elTop = xy[1] - self._top,
291
// Check whether the element's top left point is within the
292
// viewport. This is the least expensive check.
293
if (elLeft >= scrollLeft && elLeft < scrollRight &&
294
elTop >= scrollTop && elTop < scrollBottom) {
299
// Check whether the element's bottom right point is within the
300
// viewport. This check is more expensive since we have to get the
301
// element's height and width.
302
elBottom = elTop + el.offsetHeight;
303
elRight = elLeft + el.offsetWidth;
305
if (elRight < scrollRight && elRight >= scrollLeft &&
306
elBottom < scrollBottom && elBottom >= scrollTop) {
311
// If we get here, the element isn't within the viewport.
317
Returns an object hash containing information about the current scroll
318
position of the host node. This is the same information that's mixed into
319
the event facade of the `scroll` event and other scroll-related events.
321
@method getScrollInfo
322
@return {Object} Object hash containing information about the current scroll
323
position. See the `scroll` event for details on what properties this
327
getScrollInfo: function () {
328
var domNode = this._scrollNode,
329
lastScroll = this._lastScroll,
330
margin = this._scrollMargin,
332
scrollLeft = domNode.scrollLeft,
333
scrollHeight = domNode.scrollHeight,
334
scrollTop = domNode.scrollTop,
335
scrollWidth = domNode.scrollWidth,
337
scrollBottom = scrollTop + this._height,
338
scrollRight = scrollLeft + this._width;
341
atBottom: scrollBottom > (scrollHeight - margin),
342
atLeft : scrollLeft < margin,
343
atRight : scrollRight > (scrollWidth - margin),
344
atTop : scrollTop < margin,
346
isScrollDown : lastScroll && scrollTop > lastScroll.scrollTop,
347
isScrollLeft : lastScroll && scrollLeft < lastScroll.scrollLeft,
348
isScrollRight: lastScroll && scrollLeft > lastScroll.scrollLeft,
349
isScrollUp : lastScroll && scrollTop < lastScroll.scrollTop,
351
scrollBottom: scrollBottom,
352
scrollHeight: scrollHeight,
353
scrollLeft : scrollLeft,
354
scrollRight : scrollRight,
355
scrollTop : scrollTop,
356
scrollWidth : scrollWidth
361
Refreshes cached position, height, and width dimensions for the host node.
362
If the host node is the body, then the viewport height and width will be
365
This info is cached to improve performance during scroll events, since it's
366
expensive to touch the DOM for these values. Dimensions are automatically
367
refreshed whenever the browser is resized, but if you change the dimensions
368
or position of the host node in JS, you may need to call
369
`refreshDimensions()` manually to cache the new dimensions.
371
@method refreshDimensions
373
refreshDimensions: function () {
374
// WebKit only returns reliable scroll info on the body, and only
375
// returns reliable height/width info on the documentElement, so we
376
// have to special-case it (see the other special case in
377
// _getScrollNode()).
379
// On iOS devices, documentElement.clientHeight/Width aren't reliable,
380
// but window.innerHeight/Width are. And no, dom-screen's viewport size
381
// methods don't account for this, which is why we do it here.
383
var hostIsBody = this._hostIsBody,
384
iosHack = hostIsBody && Y.UA.ios,
388
if (hostIsBody && Y.UA.webkit) {
389
el = Y.config.doc.documentElement;
391
el = this._scrollNode;
394
this._height = iosHack ? win.innerHeight : el.clientHeight;
395
this._left = el.offsetLeft;
396
this._top = el.offsetTop;
397
this._width = iosHack ? win.innerWidth : el.clientWidth;
400
// -- Protected Methods ----------------------------------------------------
403
Binds event handlers.
409
var winNode = Y.one('win');
413
scrollDelayChange : this._afterScrollDelayChange,
414
scrollMarginChange: this._afterScrollMarginChange
417
winNode.on('windowresize', this._afterResize, this),
419
// If we're attached to the body, listen for the scroll event on the
420
// window, since <body> doesn't have a scroll event.
421
(this._hostIsBody ? winNode : this._host).after(
422
'scroll', this._afterScroll, this)
427
Returns the DOM node that should be used to lookup scroll coordinates. In
428
some browsers, the `<body>` element doesn't return scroll coordinates, and
429
the documentElement must be used instead; this method takes care of
430
determining which node should be used.
432
@method _getScrollNode
433
@return {HTMLElement} DOM node.
436
_getScrollNode: function () {
437
// WebKit returns scroll coordinates on the body element, but other
438
// browsers don't, so we have to use the documentElement.
439
return this._hostIsBody && !Y.UA.webkit ? Y.config.doc.documentElement :
440
Y.Node.getDOMNode(this._host);
444
Mixes detailed scroll information into the given DOM `scroll` event facade
445
and fires appropriate local events.
447
@method _triggerScroll
448
@param {EventFacade} e Event facade from the DOM `scroll` event.
451
_triggerScroll: function (e) {
452
var info = this.getScrollInfo(),
453
facade = Y.merge(e, info),
454
lastScroll = this._lastScroll;
456
this._lastScroll = info;
458
this.fire(EVT_SCROLL, facade);
460
if (info.isScrollLeft) {
461
this.fire(EVT_SCROLL_LEFT, facade);
462
} else if (info.isScrollRight) {
463
this.fire(EVT_SCROLL_RIGHT, facade);
466
if (info.isScrollUp) {
467
this.fire(EVT_SCROLL_UP, facade);
468
} else if (info.isScrollDown) {
469
this.fire(EVT_SCROLL_DOWN, facade);
472
if (info.atBottom && (!lastScroll.atBottom ||
473
info.scrollHeight > lastScroll.scrollHeight)) {
475
this.fire(EVT_SCROLL_TO_BOTTOM, facade);
478
if (info.atLeft && !lastScroll.atLeft) {
479
this.fire(EVT_SCROLL_TO_LEFT, facade);
482
if (info.atRight && (!lastScroll.atRight ||
483
info.scrollWidth > lastScroll.scrollWidth)) {
485
this.fire(EVT_SCROLL_TO_RIGHT, facade);
488
if (info.atTop && !lastScroll.atTop) {
489
this.fire(EVT_SCROLL_TO_TOP, facade);
493
// -- Protected Event Handlers ---------------------------------------------
496
Handles browser resize events.
499
@param {EventFacade} e
502
_afterResize: function (e) {
503
this.refreshDimensions();
507
Handles DOM `scroll` events.
510
@param {EventFacade} e
513
_afterScroll: function (e) {
516
clearTimeout(this._scrollTimeout);
518
this._scrollTimeout = setTimeout(function () {
519
self._triggerScroll(e);
520
}, this._scrollDelay);
524
Caches the `scrollDelay` value after that attribute changes to allow
525
quicker lookups in critical path code.
527
@method _afterScrollDelayChange
528
@param {EventFacade} e
531
_afterScrollDelayChange: function (e) {
532
this._scrollDelay = e.newVal;
536
Caches the `scrollMargin` value after that attribute changes to allow
537
quicker lookups in critical path code.
539
@method _afterScrollMarginChange
540
@param {EventFacade} e
543
_afterScrollMarginChange: function (e) {
544
this._scrollMargin = e.newVal;
551
Number of milliseconds to wait after a native `scroll` event before
552
firing local scroll events. If another native scroll event occurs during
553
this time, previous events will be ignored. This ensures that we don't
554
fire thousands of events when the user is scrolling quickly.
556
@attribute scrollDelay
565
Additional margin in pixels beyond the onscreen region of the host node
566
that should be considered "onscreen".
568
For example, if set to 50, then a `scrollToBottom` event would be fired
569
when the user scrolls to within 50 pixels of the bottom of the
570
scrollable region, even if they don't actually scroll completely to the
573
This margin also applies to the `getOffscreenNodes()` and
574
`getOnscreenNodes()` methods by default.
576
@attribute scrollMargin
587
}, '3.10.3', {"requires": ["base-build", "dom-screen", "event-resize", "node-pluginhost", "plugin"]});