[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 Yahoo! Inc. All rights reserved. 4 Licensed under the BSD License. 5 http://yuilibrary.com/license/ 6 */ 7 8 YUI.add('node-scroll-info', function (Y, NAME) { 9 10 /*jshint onevar:false */ 11 12 /** 13 Provides the ScrollInfo Node plugin, which exposes convenient events and methods 14 related to scrolling. 15 16 @module node-scroll-info 17 @since 3.7.0 18 **/ 19 20 /** 21 Provides convenient events and methods related to scrolling. This could be used, 22 for example, to implement infinite scrolling, or to lazy-load content based on 23 the current scroll position. 24 25 ### Example 26 27 var body = Y.one('body'); 28 29 body.plug(Y.Plugin.ScrollInfo); 30 31 body.scrollInfo.on('scrollToBottom', function (e) { 32 // Load more content when the user scrolls to the bottom of the page. 33 }); 34 35 @class Plugin.ScrollInfo 36 @extends Plugin.Base 37 @since 3.7.0 38 **/ 39 40 var doc = Y.config.doc, 41 win = Y.config.win; 42 43 /** 44 Fired when the user scrolls within the host node. 45 46 This event (like all scroll events exposed by ScrollInfo) is throttled and fired 47 only after the number of milliseconds specified by the `scrollDelay` attribute 48 have passed in order to prevent thrashing. 49 50 This event passes along the event facade for the standard DOM `scroll` event and 51 mixes in the following additional properties. 52 53 @event scroll 54 @param {Boolean} atBottom Whether the current scroll position is at the bottom 55 of the scrollable region. 56 @param {Boolean} atLeft Whether the current scroll position is at the extreme 57 left of the scrollable region. 58 @param {Boolean} atRight Whether the current scroll position is at the extreme 59 right of the scrollable region. 60 @param {Boolean} atTop Whether the current scroll position is at the top of the 61 scrollable region. 62 @param {Boolean} isScrollDown `true` if the user scrolled down. 63 @param {Boolean} isScrollLeft `true` if the user scrolled left. 64 @param {Boolean} isScrollRight `true` if the user scrolled right. 65 @param {Boolean} isScrollUp `true` if the user scrolled up. 66 @param {Number} scrollBottom Y value of the bottom-most onscreen pixel of the 67 scrollable region. 68 @param {Number} scrollHeight Total height in pixels of the scrollable region, 69 including offscreen pixels. 70 @param {Number} scrollLeft X value of the left-most onscreen pixel of the 71 scrollable region. 72 @param {Number} scrollRight X value of the right-most onscreen pixel of the 73 scrollable region. 74 @param {Number} scrollTop Y value of the top-most onscreen pixel of the 75 scrollable region. 76 @param {Number} scrollWidth Total width in pixels of the scrollable region, 77 including offscreen pixels. 78 @see scrollDelay 79 @see scrollMargin 80 **/ 81 var EVT_SCROLL = 'scroll', 82 83 /** 84 Fired when the user scrolls down within the host node. 85 86 This event provides the same event facade as the `scroll` event. See that 87 event for details. 88 89 @event scrollDown 90 @see scroll 91 **/ 92 EVT_SCROLL_DOWN = 'scrollDown', 93 94 /** 95 Fired when the user scrolls left within the host node. 96 97 This event provides the same event facade as the `scroll` event. See that 98 event for details. 99 100 @event scrollLeft 101 @see scroll 102 **/ 103 EVT_SCROLL_LEFT = 'scrollLeft', 104 105 /** 106 Fired when the user scrolls right within the host node. 107 108 This event provides the same event facade as the `scroll` event. See that 109 event for details. 110 111 @event scrollRight 112 @see scroll 113 **/ 114 EVT_SCROLL_RIGHT = 'scrollRight', 115 116 /** 117 Fired when the user scrolls up within the host node. 118 119 This event provides the same event facade as the `scroll` event. See that 120 event for details. 121 122 @event scrollUp 123 @see scroll 124 **/ 125 EVT_SCROLL_UP = 'scrollUp', 126 127 /** 128 Fired when the user scrolls to the bottom of the scrollable region within 129 the host node. 130 131 This event provides the same event facade as the `scroll` event. See that 132 event for details. 133 134 @event scrollToBottom 135 @see scroll 136 **/ 137 EVT_SCROLL_TO_BOTTOM = 'scrollToBottom', 138 139 /** 140 Fired when the user scrolls to the extreme left of the scrollable region 141 within the host node. 142 143 This event provides the same event facade as the `scroll` event. See that 144 event for details. 145 146 @event scrollToLeft 147 @see scroll 148 **/ 149 EVT_SCROLL_TO_LEFT = 'scrollToLeft', 150 151 /** 152 Fired when the user scrolls to the extreme right of the scrollable region 153 within the host node. 154 155 This event provides the same event facade as the `scroll` event. See that 156 event for details. 157 158 @event scrollToRight 159 @see scroll 160 **/ 161 EVT_SCROLL_TO_RIGHT = 'scrollToRight', 162 163 /** 164 Fired when the user scrolls to the top of the scrollable region within the 165 host node. 166 167 This event provides the same event facade as the `scroll` event. See that 168 event for details. 169 170 @event scrollToTop 171 @see scroll 172 **/ 173 EVT_SCROLL_TO_TOP = 'scrollToTop'; 174 175 Y.Plugin.ScrollInfo = Y.Base.create('scrollInfoPlugin', Y.Plugin.Base, [], { 176 // -- Protected Properties ------------------------------------------------- 177 178 /** 179 Height of the visible region of the host node in pixels. If the host node is 180 the body, this will be the same as `_winHeight`. 181 182 @property {Number} _height 183 @protected 184 **/ 185 186 /** 187 Whether or not the host node is the `<body>` element. 188 189 @property {Boolean} _hostIsBody 190 @protected 191 **/ 192 193 /** 194 Width of the visible region of the host node in pixels. If the host node is 195 the body, this will be the same as `_winWidth`. 196 197 @property {Number} _width 198 @protected 199 **/ 200 201 /** 202 Height of the viewport in pixels. 203 204 @property {Number} _winHeight 205 @protected 206 **/ 207 208 /** 209 Width of the viewport in pixels. 210 211 @property {Number} _winWidth 212 @protected 213 **/ 214 215 // -- Lifecycle Methods ---------------------------------------------------- 216 initializer: function (config) { 217 // Cache for quicker lookups in the critical path. 218 this._host = config.host; 219 this._hostIsBody = this._host.get('nodeName').toLowerCase() === 'body'; 220 this._scrollDelay = this.get('scrollDelay'); 221 this._scrollMargin = this.get('scrollMargin'); 222 this._scrollNode = this._getScrollNode(); 223 224 this.refreshDimensions(); 225 226 this._lastScroll = this.getScrollInfo(); 227 228 this._bind(); 229 }, 230 231 destructor: function () { 232 new Y.EventHandle(this._events).detach(); 233 this._events = null; 234 }, 235 236 // -- Public Methods ------------------------------------------------------- 237 238 /** 239 Returns a NodeList containing all offscreen nodes inside the host node that 240 match the given CSS selector. An offscreen node is any node that is entirely 241 outside the visible (onscreen) region of the host node based on the current 242 scroll location. 243 244 @method getOffscreenNodes 245 @param {String} [selector] CSS selector. If omitted, all offscreen nodes 246 will be returned. 247 @param {Number} [margin] Additional margin in pixels beyond the actual 248 onscreen region that should be considered "onscreen" for the purposes of 249 this query. Defaults to the value of the `scrollMargin` attribute. 250 @return {NodeList} Offscreen nodes matching _selector_. 251 @see scrollMargin 252 **/ 253 getOffscreenNodes: function (selector, margin) { 254 if (typeof margin === 'undefined') { 255 margin = this._scrollMargin; 256 } 257 258 var elements = Y.Selector.query(selector || '*', this._host._node); 259 260 return new Y.NodeList(Y.Array.filter(elements, function (el) { 261 return !this._isElementOnscreen(el, margin); 262 }, this)); 263 }, 264 265 /** 266 Returns a NodeList containing all onscreen nodes inside the host node that 267 match the given CSS selector. An onscreen node is any node that is fully or 268 partially within the visible (onscreen) region of the host node based on the 269 current scroll location. 270 271 @method getOnscreenNodes 272 @param {String} [selector] CSS selector. If omitted, all onscreen nodes will 273 be returned. 274 @param {Number} [margin] Additional margin in pixels beyond the actual 275 onscreen region that should be considered "onscreen" for the purposes of 276 this query. Defaults to the value of the `scrollMargin` attribute. 277 @return {NodeList} Onscreen nodes matching _selector_. 278 @see scrollMargin 279 **/ 280 getOnscreenNodes: function (selector, margin) { 281 if (typeof margin === 'undefined') { 282 margin = this._scrollMargin; 283 } 284 285 var elements = Y.Selector.query(selector || '*', this._host._node); 286 287 return new Y.NodeList(Y.Array.filter(elements, function (el) { 288 return this._isElementOnscreen(el, margin); 289 }, this)); 290 }, 291 292 /** 293 Returns an object hash containing information about the current scroll 294 position of the host node. This is the same information that's mixed into 295 the event facade of the `scroll` event and other scroll-related events. 296 297 @method getScrollInfo 298 @return {Object} Object hash containing information about the current scroll 299 position. See the `scroll` event for details on what properties this 300 object contains. 301 @see scroll 302 **/ 303 getScrollInfo: function () { 304 var domNode = this._scrollNode, 305 lastScroll = this._lastScroll, 306 margin = this._scrollMargin, 307 308 scrollLeft = domNode.scrollLeft, 309 scrollHeight = domNode.scrollHeight, 310 scrollTop = domNode.scrollTop, 311 scrollWidth = domNode.scrollWidth, 312 313 scrollBottom = scrollTop + this._height, 314 scrollRight = scrollLeft + this._width; 315 316 return { 317 atBottom: scrollBottom > (scrollHeight - margin), 318 atLeft : scrollLeft < margin, 319 atRight : scrollRight > (scrollWidth - margin), 320 atTop : scrollTop < margin, 321 322 isScrollDown : lastScroll && scrollTop > lastScroll.scrollTop, 323 isScrollLeft : lastScroll && scrollLeft < lastScroll.scrollLeft, 324 isScrollRight: lastScroll && scrollLeft > lastScroll.scrollLeft, 325 isScrollUp : lastScroll && scrollTop < lastScroll.scrollTop, 326 327 scrollBottom: scrollBottom, 328 scrollHeight: scrollHeight, 329 scrollLeft : scrollLeft, 330 scrollRight : scrollRight, 331 scrollTop : scrollTop, 332 scrollWidth : scrollWidth 333 }; 334 }, 335 336 /** 337 Returns `true` if _node_ is at least partially onscreen within the host 338 node, `false` otherwise. 339 340 @method isNodeOnscreen 341 @param {HTMLElement|Node|String} node Node or selector to check. 342 @param {Number} [margin] Additional margin in pixels beyond the actual 343 onscreen region that should be considered "onscreen" for the purposes of 344 this query. Defaults to the value of the `scrollMargin` attribute. 345 @return {Boolean} `true` if _node_ is at least partially onscreen within the 346 host node, `false` otherwise. 347 @since 3.11.0 348 **/ 349 isNodeOnscreen: function (node, margin) { 350 node = Y.one(node); 351 return !!(node && this._isElementOnscreen(node._node, margin)); 352 }, 353 354 /** 355 Refreshes cached position, height, and width dimensions for the host node. 356 If the host node is the body, then the viewport height and width will be 357 used. 358 359 This info is cached to improve performance during scroll events, since it's 360 expensive to touch the DOM for these values. Dimensions are automatically 361 refreshed whenever the browser is resized, but if you change the dimensions 362 or position of the host node in JS, you may need to call 363 `refreshDimensions()` manually to cache the new dimensions. 364 365 @method refreshDimensions 366 **/ 367 refreshDimensions: function () { 368 var docEl = doc.documentElement; 369 370 // On iOS devices and on Chrome for Android, 371 // documentElement.clientHeight/Width aren't reliable, but 372 // window.innerHeight/Width are. The dom-screen module's viewport size 373 // methods don't account for this, which is why we do it here. 374 if (Y.UA.ios || (Y.UA.android && Y.UA.chrome)) { 375 this._winHeight = win.innerHeight; 376 this._winWidth = win.innerWidth; 377 } else { 378 this._winHeight = docEl.clientHeight; 379 this._winWidth = docEl.clientWidth; 380 } 381 382 if (this._hostIsBody) { 383 this._height = this._winHeight; 384 this._width = this._winWidth; 385 } else { 386 this._height = this._scrollNode.clientHeight; 387 this._width = this._scrollNode.clientWidth; 388 } 389 390 this._refreshHostBoundingRect(); 391 }, 392 393 // -- Protected Methods ---------------------------------------------------- 394 395 /** 396 Binds event handlers. 397 398 @method _bind 399 @protected 400 **/ 401 _bind: function () { 402 var winNode = Y.one('win'); 403 404 this._events = [ 405 this.after({ 406 scrollDelayChange : this._afterScrollDelayChange, 407 scrollMarginChange: this._afterScrollMarginChange 408 }), 409 410 winNode.on('windowresize', this._afterResize, this) 411 ]; 412 413 // If the host node is the body, listen for the scroll event on the 414 // window, since <body> doesn't have a scroll event. 415 if (this._hostIsBody) { 416 this._events.push(winNode.after('scroll', this._afterHostScroll, this)); 417 } else { 418 // The host node is not the body, but we still need to listen for 419 // window scroll events so we can determine whether nodes are 420 // onscreen. 421 this._events.push( 422 winNode.after('scroll', this._afterWindowScroll, this), 423 this._host.after('scroll', this._afterHostScroll, this) 424 ); 425 } 426 }, 427 428 /** 429 Returns the DOM node that should be used to lookup scroll coordinates. In 430 some browsers, the `<body>` element doesn't return scroll coordinates, and 431 the documentElement must be used instead; this method takes care of 432 determining which node should be used. 433 434 @method _getScrollNode 435 @return {HTMLElement} DOM node. 436 @protected 437 **/ 438 _getScrollNode: function () { 439 // WebKit returns scroll coordinates on the body element, but other 440 // browsers don't, so we have to use the documentElement. 441 return this._hostIsBody && !Y.UA.webkit ? doc.documentElement : 442 Y.Node.getDOMNode(this._host); 443 }, 444 445 /** 446 Underlying element-based implementation for `isNodeOnscreen()`. 447 448 @method _isElementOnscreen 449 @param {HTMLElement} el HTML element. 450 @param {Number} [margin] Additional margin in pixels beyond the actual 451 onscreen region that should be considered "onscreen" for the purposes of 452 this query. Defaults to the value of the `scrollMargin` attribute. 453 @return {Boolean} `true` if _el_ is at least partially onscreen within the 454 host node, `false` otherwise. 455 @protected 456 @since 3.11.0 457 **/ 458 _isElementOnscreen: function (el, margin) { 459 var hostRect = this._hostRect, 460 rect = el.getBoundingClientRect(); 461 462 if (typeof margin === 'undefined') { 463 margin = this._scrollMargin; 464 } 465 466 // Determine whether any part of _el_ is within the visible region of 467 // the host element or the specified margin around the visible region of 468 // the host element. 469 return !(rect.top > hostRect.bottom + margin 470 || rect.bottom < hostRect.top - margin 471 || rect.right < hostRect.left - margin 472 || rect.left > hostRect.right + margin); 473 }, 474 475 /** 476 Caches the bounding rect of the host node. 477 478 If the host node is the body, the bounding rect will be faked to represent 479 the dimensions of the viewport, since the actual body dimensions may extend 480 beyond the viewport and we only care about the visible region. 481 482 @method _refreshHostBoundingRect 483 @protected 484 **/ 485 _refreshHostBoundingRect: function () { 486 var winHeight = this._winHeight, 487 winWidth = this._winWidth, 488 489 hostRect; 490 491 if (this._hostIsBody) { 492 hostRect = { 493 bottom: winHeight, 494 height: winHeight, 495 left : 0, 496 right : winWidth, 497 top : 0, 498 width : winWidth 499 }; 500 501 this._isHostOnscreen = true; 502 } else { 503 hostRect = this._scrollNode.getBoundingClientRect(); 504 } 505 506 this._hostRect = hostRect; 507 }, 508 509 /** 510 Mixes detailed scroll information into the given DOM `scroll` event facade 511 and fires appropriate local events. 512 513 @method _triggerScroll 514 @param {EventFacade} e Event facade from the DOM `scroll` event. 515 @protected 516 **/ 517 _triggerScroll: function (e) { 518 var info = this.getScrollInfo(), 519 facade = Y.merge(e, info), 520 lastScroll = this._lastScroll; 521 522 this._lastScroll = info; 523 524 this.fire(EVT_SCROLL, facade); 525 526 if (info.isScrollLeft) { 527 this.fire(EVT_SCROLL_LEFT, facade); 528 } else if (info.isScrollRight) { 529 this.fire(EVT_SCROLL_RIGHT, facade); 530 } 531 532 if (info.isScrollUp) { 533 this.fire(EVT_SCROLL_UP, facade); 534 } else if (info.isScrollDown) { 535 this.fire(EVT_SCROLL_DOWN, facade); 536 } 537 538 if (info.atBottom && (!lastScroll.atBottom || 539 info.scrollHeight > lastScroll.scrollHeight)) { 540 541 this.fire(EVT_SCROLL_TO_BOTTOM, facade); 542 } 543 544 if (info.atLeft && !lastScroll.atLeft) { 545 this.fire(EVT_SCROLL_TO_LEFT, facade); 546 } 547 548 if (info.atRight && (!lastScroll.atRight || 549 info.scrollWidth > lastScroll.scrollWidth)) { 550 551 this.fire(EVT_SCROLL_TO_RIGHT, facade); 552 } 553 554 if (info.atTop && !lastScroll.atTop) { 555 this.fire(EVT_SCROLL_TO_TOP, facade); 556 } 557 }, 558 559 // -- Protected Event Handlers --------------------------------------------- 560 561 /** 562 Handles DOM `scroll` events on the host node. 563 564 @method _afterHostScroll 565 @param {EventFacade} e 566 @protected 567 **/ 568 _afterHostScroll: function (e) { 569 var self = this; 570 571 clearTimeout(this._scrollTimeout); 572 573 this._scrollTimeout = setTimeout(function () { 574 self._triggerScroll(e); 575 }, this._scrollDelay); 576 }, 577 578 /** 579 Handles browser resize events. 580 581 @method _afterResize 582 @protected 583 **/ 584 _afterResize: function () { 585 this.refreshDimensions(); 586 }, 587 588 /** 589 Caches the `scrollDelay` value after that attribute changes to allow 590 quicker lookups in critical path code. 591 592 @method _afterScrollDelayChange 593 @param {EventFacade} e 594 @protected 595 **/ 596 _afterScrollDelayChange: function (e) { 597 this._scrollDelay = e.newVal; 598 }, 599 600 /** 601 Caches the `scrollMargin` value after that attribute changes to allow 602 quicker lookups in critical path code. 603 604 @method _afterScrollMarginChange 605 @param {EventFacade} e 606 @protected 607 **/ 608 _afterScrollMarginChange: function (e) { 609 this._scrollMargin = e.newVal; 610 }, 611 612 /** 613 Handles DOM `scroll` events on the window. 614 615 @method _afterWindowScroll 616 @param {EventFacade} e 617 @protected 618 **/ 619 _afterWindowScroll: function () { 620 this._refreshHostBoundingRect(); 621 } 622 }, { 623 NS: 'scrollInfo', 624 625 ATTRS: { 626 /** 627 Number of milliseconds to wait after a native `scroll` event before 628 firing local scroll events. If another native scroll event occurs during 629 this time, previous events will be ignored. This ensures that we don't 630 fire thousands of events when the user is scrolling quickly. 631 632 @attribute scrollDelay 633 @type Number 634 @default 50 635 **/ 636 scrollDelay: { 637 value: 50 638 }, 639 640 /** 641 Additional margin in pixels beyond the onscreen region of the host node 642 that should be considered "onscreen". 643 644 For example, if set to 50, then a `scrollToBottom` event would be fired 645 when the user scrolls to within 50 pixels of the bottom of the 646 scrollable region, even if they don't actually scroll completely to the 647 very bottom pixel. 648 649 This margin also applies to the `getOffscreenNodes()` and 650 `getOnscreenNodes()` methods by default. 651 652 @attribute scrollMargin 653 @type Number 654 @default 50 655 **/ 656 scrollMargin: { 657 value: 50 658 } 659 } 660 }); 661 662 663 }, '3.17.2', {"requires": ["array-extras", "base-build", "event-resize", "node-pluginhost", "plugin", "selector"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |