[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/imageloader/ -> imageloader-debug.js (source)

   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('imageloader', function (Y, NAME) {
   9  
  10  /**
  11   * The ImageLoader Utility is a framework to dynamically load images according to certain triggers,
  12   * enabling faster load times and a more responsive UI.
  13   *
  14   * @module imageloader
  15   */
  16  
  17  
  18      /**
  19       * A group for images. A group can have one time limit and a series of triggers. Thus the images belonging to this group must share these constraints.
  20       * @class ImgLoadGroup
  21       * @extends Base
  22       * @constructor
  23       */
  24      Y.ImgLoadGroup = function() {
  25          // call init first, because it sets up local vars for storing attribute-related info
  26          this._init();
  27          Y.ImgLoadGroup.superclass.constructor.apply(this, arguments);
  28      };
  29  
  30      Y.ImgLoadGroup.NAME = 'imgLoadGroup';
  31  
  32      Y.ImgLoadGroup.ATTRS = {
  33  
  34          /**
  35           * Name for the group. Only used to identify the group in logging statements.
  36           * @attribute name
  37           * @type String
  38           */
  39          name: {
  40              value: ''
  41          },
  42  
  43          /**
  44           * Time limit, in seconds, after which images are fetched regardless of trigger events.
  45           * @attribute timeLimit
  46           * @type Number
  47           */
  48          timeLimit: {
  49              value: null
  50          },
  51  
  52          /**
  53           * Distance below the fold for which images are loaded. Images are not loaded until they are at most this distance away from (or above) the fold.
  54           * This check is performed at page load (domready) and after any window scroll or window resize event (until all images are loaded).
  55           * @attribute foldDistance
  56           * @type Number
  57           */
  58          foldDistance: {
  59              validator: Y.Lang.isNumber,
  60              setter: function(val) { this._setFoldTriggers(); return val; },
  61              lazyAdd: false
  62          },
  63  
  64          /**
  65           * Class name that will identify images belonging to the group. This class name will be removed from each element in order to fetch images.
  66           * This class should have, in its CSS style definition, "<code>background:none !important;</code>".
  67           * @attribute className
  68           * @type String
  69           */
  70          className: {
  71              value: null,
  72              setter: function(name) { this._className = name; return name; },
  73              lazyAdd: false
  74          },
  75  
  76          /**
  77           * Determines how to act when className is used as the way to delay load images. The "default" action is to just
  78           * remove the class name. The "enhanced" action is to remove the class name and also set the src attribute if
  79           * the element is an img.
  80           * @attribute classNameAction
  81           * @type String
  82           */
  83          classNameAction: {
  84              value: "default"
  85          }
  86  
  87      };
  88  
  89      var groupProto = {
  90  
  91          /**
  92           * Initialize all private members needed for the group.
  93           * @method _init
  94           * @private
  95           */
  96          _init: function() {
  97  
  98              /**
  99               * Collection of triggers for this group.
 100               * Keeps track of each trigger's event handle, as returned from <code>Y.on</code>.
 101               * @property _triggers
 102               * @private
 103               * @type Array
 104               */
 105              this._triggers = [];
 106  
 107              /**
 108               * Collection of images (<code>Y.ImgLoadImgObj</code> objects) registered with this group, keyed by DOM id.
 109               * @property _imgObjs
 110               * @private
 111               * @type Object
 112               */
 113              this._imgObjs = {};
 114  
 115              /**
 116               * Timeout object to keep a handle on the time limit.
 117               * @property _timeout
 118               * @private
 119               * @type Object
 120               */
 121              this._timeout = null;
 122  
 123              /**
 124               * DOM elements having the class name that is associated with this group.
 125               * Elements are stored during the <code>_foldCheck</code> function and reused later during any subsequent <code>_foldCheck</code> calls - gives a slight performance improvement when the page fold is repeatedly checked.
 126               * @property _classImageEls
 127               * @private
 128               * @type Array
 129               */
 130              this._classImageEls = null;
 131  
 132              /**
 133               * Keep the CSS class name in a member variable for ease and speed.
 134               * @property _className
 135               * @private
 136               * @type String
 137               */
 138              this._className = null;
 139  
 140              /**
 141               * Boolean tracking whether the window scroll and window resize triggers have been set if this is a fold group.
 142               * @property _areFoldTriggersSet
 143               * @private
 144               * @type Boolean
 145               */
 146              this._areFoldTriggersSet = false;
 147  
 148              /**
 149               * The maximum pixel height of the document that has been made visible.
 150               * During fold checks, if the user scrolls up then there's no need to check for newly exposed images.
 151               * @property _maxKnownHLimit
 152               * @private
 153               * @type Int
 154               */
 155              this._maxKnownHLimit = 0;
 156  
 157              // add a listener to domready that will start the time limit
 158              Y.on('domready', this._onloadTasks, this);
 159          },
 160  
 161          /**
 162           * Adds a trigger to the group. Arguments are passed to <code>Y.on</code>.
 163           * @method addTrigger
 164           * @chainable
 165           * @param {Object} obj  The DOM object to attach the trigger event to
 166           * @param {String} type  The event type
 167           */
 168          addTrigger: function(obj, type) {
 169              if (! obj || ! type) {
 170                  return this;
 171              }
 172  
 173              Y.log('adding trigger to group: ' + this.get('name'), 'info', 'imageloader');
 174  
 175              /* Need to wrap the fetch function. Event Util can't distinguish prototyped functions of different instantiations.
 176               *   Leads to this scenario: groupA and groupZ both have window-scroll triggers. groupZ also has a 2-sec timeout (groupA has no timeout).
 177               *   groupZ's timeout fires; we remove the triggers. The detach call finds the first window-scroll event with Y.ILG.p.fetch, which is groupA's.
 178               *   groupA's trigger is removed and never fires, leaving images unfetched.
 179               */
 180              var wrappedFetch = function() {
 181                  this.fetch();
 182              };
 183              this._triggers.push( Y.on(type, wrappedFetch, obj, this) );
 184  
 185              return this;
 186          },
 187  
 188          /**
 189           * Adds a custom event trigger to the group.
 190           * @method addCustomTrigger
 191           * @chainable
 192           * @param {String} name  The name of the event
 193           * @param {Object} obj  The object on which to attach the event. <code>obj</code> is optional - by default the event is attached to the <code>Y</code> instance
 194           */
 195          addCustomTrigger: function(name, obj) {
 196              if (! name) {
 197                  return this;
 198              }
 199  
 200              Y.log('adding custom trigger to group: ' + this.get('name'), 'info', 'imageloader');
 201  
 202              // see comment in addTrigger()
 203              var wrappedFetch = function() {
 204                  this.fetch();
 205              };
 206              if (Y.Lang.isUndefined(obj)) {
 207                  this._triggers.push( Y.on(name, wrappedFetch, this) );
 208              }
 209              else {
 210                  this._triggers.push( obj.on(name, wrappedFetch, this) );
 211              }
 212  
 213              return this;
 214          },
 215  
 216          /**
 217           * Sets the window scroll and window resize triggers for any group that is fold-conditional (i.e., has a fold distance set).
 218           * @method _setFoldTriggers
 219           * @private
 220           */
 221          _setFoldTriggers: function() {
 222              if (this._areFoldTriggersSet) {
 223                  return;
 224              }
 225  
 226              Y.log('setting window scroll and resize events for group: ' + this.get('name'), 'info', 'imageloader');
 227  
 228              var wrappedFoldCheck = function() {
 229                  this._foldCheck();
 230              };
 231              this._triggers.push( Y.on('scroll', wrappedFoldCheck, window, this) );
 232              this._triggers.push( Y.on('resize', wrappedFoldCheck, window, this) );
 233              this._areFoldTriggersSet = true;
 234          },
 235  
 236          /**
 237           * Performs necessary setup at domready time.
 238           * Initiates time limit for group; executes the fold check for the images.
 239           * @method _onloadTasks
 240           * @private
 241           */
 242          _onloadTasks: function() {
 243              var timeLim = this.get('timeLimit');
 244              if (timeLim && timeLim > 0) {
 245                  Y.log('setting time limit of ' + timeLim + ' seconds for group: ' + this.get('name'), 'info', 'imageloader');
 246                  this._timeout = setTimeout(this._getFetchTimeout(), timeLim * 1000);
 247              }
 248  
 249              if (! Y.Lang.isUndefined(this.get('foldDistance'))) {
 250                  this._foldCheck();
 251              }
 252          },
 253  
 254          /**
 255           * Returns the group's <code>fetch</code> method, with the proper closure, for use with <code>setTimeout</code>.
 256           * @method _getFetchTimeout
 257           * @return {Function}  group's <code>fetch</code> method
 258           * @private
 259           */
 260          _getFetchTimeout: function() {
 261              var self = this;
 262              return function() { self.fetch(); };
 263          },
 264  
 265          /**
 266           * Registers an image with the group.
 267           * Arguments are passed through to a <code>Y.ImgLoadImgObj</code> constructor; see that class' attribute documentation for detailed information. "<code>domId</code>" is a required attribute.
 268           * @method registerImage
 269           * @param {Object} config A configuration object literal with attribute name/value pairs  (passed through to a <code>Y.ImgLoadImgObj</code> constructor)
 270           * @return {Object}  <code>Y.ImgLoadImgObj</code> that was registered
 271           */
 272          registerImage: function() {
 273              var domId = arguments[0].domId;
 274              if (! domId) {
 275                  return null;
 276              }
 277  
 278              Y.log('adding image with id: ' + domId + ' to group: ' + this.get('name'), 'info', 'imageloader');
 279  
 280              this._imgObjs[domId] = new Y.ImgLoadImgObj(arguments[0]);
 281              return this._imgObjs[domId];
 282          },
 283  
 284          /**
 285           * Displays the images in the group.
 286           * This method is called when a trigger fires or the time limit expires; it shouldn't be called externally, but is not private in the rare event that it needs to be called immediately.
 287           * @method fetch
 288           */
 289          fetch: function() {
 290              Y.log('Fetching images in group: "' + this.get('name') + '".', 'info', 'imageloader');
 291  
 292              // done with the triggers
 293              this._clearTriggers();
 294  
 295              // fetch whatever we need to by className
 296              this._fetchByClass();
 297  
 298              // fetch registered images
 299              for (var id in this._imgObjs) {
 300                  if (this._imgObjs.hasOwnProperty(id)) {
 301                      this._imgObjs[id].fetch();
 302                  }
 303              }
 304          },
 305  
 306          /**
 307           * Clears the timeout and all triggers associated with the group.
 308           * @method _clearTriggers
 309           * @private
 310           */
 311          _clearTriggers: function() {
 312              clearTimeout(this._timeout);
 313              // detach all listeners
 314              for (var i=0, len = this._triggers.length; i < len; i++) {
 315                  this._triggers[i].detach();
 316              }
 317          },
 318  
 319          /**
 320           * Checks the position of each image in the group. If any part of the image is within the specified distance (<code>foldDistance</code>) of the client viewport, the image is fetched immediately.
 321           * @method _foldCheck
 322           * @private
 323           */
 324          _foldCheck: function() {
 325              Y.log('Checking for images above the fold in group: "' + this.get('name') + '"', 'info', 'imageloader');
 326  
 327              var allFetched = true,
 328                  viewReg = Y.DOM.viewportRegion(),
 329                  hLimit = viewReg.bottom + this.get('foldDistance'),
 330                      id, imgFetched, els, i, len;
 331  
 332              // unless we've uncovered new frontiers, there's no need to continue
 333              if (hLimit <= this._maxKnownHLimit) {
 334                  return;
 335              }
 336              this._maxKnownHLimit = hLimit;
 337  
 338              for (id in this._imgObjs) {
 339                  if (this._imgObjs.hasOwnProperty(id)) {
 340                      imgFetched = this._imgObjs[id].fetch(hLimit);
 341                      allFetched = allFetched && imgFetched;
 342                  }
 343              }
 344  
 345              // and by class
 346              if (this._className) {
 347                  if (this._classImageEls === null) {
 348                      // get all the relevant elements and store them
 349                      this._classImageEls = [];
 350                      els = Y.all('.' + this._className);
 351                      els.each( function(node) { this._classImageEls.push( { el: node, y: node.getY(), fetched: false } ); }, this);
 352                  }
 353                  els = this._classImageEls;
 354                  for (i=0, len = els.length; i < len; i++) {
 355                      if (els[i].fetched) {
 356                          continue;
 357                      }
 358                      if (els[i].y && els[i].y <= hLimit) {
 359                          //els[i].el.removeClass(this._className);
 360                          this._updateNodeClassName(els[i].el);
 361                          els[i].fetched = true;
 362                          Y.log('Image with id "' + els[i].el.get('id') + '" is within distance of the fold. Fetching image. (Image registered by class name with the group - may not have an id.)', 'info', 'imageloader');
 363                      }
 364                      else {
 365                          allFetched = false;
 366                      }
 367                  }
 368              }
 369  
 370              // if allFetched, remove listeners
 371              if (allFetched) {
 372                  Y.log('All images fetched; removing listeners for group: "' + this.get('name') + '"', 'info', 'imageloader');
 373                  this._clearTriggers();
 374              }
 375          },
 376  
 377          /**
 378           * Updates a given node, removing the ImageLoader class name. If the
 379           * node is an img and the classNameAction is "enhanced", then node
 380           * class name is removed and also the src attribute is set to the
 381           * image URL as well as clearing the style background image.
 382           * @method _updateNodeClassName
 383           * @param node {Node} The node to act on.
 384           * @private
 385           */
 386          _updateNodeClassName: function(node){
 387              var url;
 388  
 389              if (this.get("classNameAction") == "enhanced"){
 390  
 391                  if (node.get("tagName").toLowerCase() == "img"){
 392                      url = node.getStyle("backgroundImage");
 393                      /url\(["']?(.*?)["']?\)/.test(url);
 394                      url = RegExp.$1;
 395                      node.set("src", url);
 396                      node.setStyle("backgroundImage", "");
 397                  }
 398              }
 399  
 400              node.removeClass(this._className);
 401          },
 402  
 403          /**
 404           * Finds all elements in the DOM with the class name specified in the group. Removes the class from the element in order to let the style definitions trigger the image fetching.
 405           * @method _fetchByClass
 406           * @private
 407           */
 408          _fetchByClass: function() {
 409              if (! this._className) {
 410                  return;
 411              }
 412  
 413              Y.log('Fetching all images with class "' + this._className + '" in group "' + this.get('name') + '".', 'info', 'imageloader');
 414  
 415              Y.all('.' + this._className).each(Y.bind(this._updateNodeClassName, this));
 416          }
 417  
 418      };
 419  
 420  
 421      Y.extend(Y.ImgLoadGroup, Y.Base, groupProto);
 422  
 423  
 424      //------------------------------------------------
 425  
 426  
 427      /**
 428       * Image objects to be registered with the groups
 429       * @class ImgLoadImgObj
 430       * @extends Base
 431       * @constructor
 432       */
 433      Y.ImgLoadImgObj = function() {
 434          Y.ImgLoadImgObj.superclass.constructor.apply(this, arguments);
 435          this._init();
 436      };
 437  
 438      Y.ImgLoadImgObj.NAME = 'imgLoadImgObj';
 439  
 440      Y.ImgLoadImgObj.ATTRS = {
 441          /**
 442           * HTML DOM id of the image element.
 443           * @attribute domId
 444           * @type String
 445           */
 446          domId: {
 447              value: null,
 448              writeOnce: true
 449          },
 450  
 451          /**
 452           * Background URL for the image.
 453           * For an image whose URL is specified by "<code>background-image</code>" in the element's style.
 454           * @attribute bgUrl
 455           * @type String
 456           */
 457          bgUrl: {
 458              value: null
 459          },
 460  
 461          /**
 462           * Source URL for the image.
 463           * For an image whose URL is specified by a "<code>src</code>" attribute in the DOM element.
 464           * @attribute srcUrl
 465           * @type String
 466           */
 467          srcUrl: {
 468              value: null
 469          },
 470  
 471          /**
 472           * Pixel width of the image. Will be set as a <code>width</code> attribute on the DOM element after the image is fetched.
 473           * Defaults to the natural width of the image (no <code>width</code> attribute will be set).
 474           * Usually only used with src images.
 475           * @attribute width
 476           * @type Int
 477           */
 478          width: {
 479              value: null
 480          },
 481  
 482          /**
 483           * Pixel height of the image. Will be set as a <code>height</code> attribute on the DOM element after the image is fetched.
 484           * Defaults to the natural height of the image (no <code>height</code> attribute will be set).
 485           * Usually only used with src images.
 486           * @attribute height
 487           * @type Int
 488           */
 489          height: {
 490              value: null
 491          },
 492  
 493          /**
 494           * Whether the image's <code>style.visibility</code> should be set to <code>visible</code> after the image is fetched.
 495           * Used when setting images as <code>visibility:hidden</code> prior to image fetching.
 496           * @attribute setVisible
 497           * @type Boolean
 498           */
 499          setVisible: {
 500              value: false
 501          },
 502  
 503          /**
 504           * Whether the image is a PNG.
 505           * PNG images get special treatment in that the URL is specified through AlphaImageLoader for IE, versions 6 and earlier.
 506           * Only used with background images.
 507           * @attribute isPng
 508           * @type Boolean
 509           */
 510          isPng: {
 511              value: false
 512          },
 513  
 514          /**
 515           * AlphaImageLoader <code>sizingMethod</code> property to be set for the image.
 516           * Only set if <code>isPng</code> value for this image is set to <code>true</code>.
 517           * Defaults to <code>scale</code>.
 518           * @attribute sizingMethod
 519           * @type String
 520           */
 521          sizingMethod: {
 522              value: 'scale'
 523          },
 524  
 525          /**
 526           * AlphaImageLoader <code>enabled</code> property to be set for the image.
 527           * Only set if <code>isPng</code> value for this image is set to <code>true</code>.
 528           * Defaults to <code>true</code>.
 529           * @attribute enabled
 530           * @type String
 531           */
 532          enabled: {
 533              value: 'true'
 534          }
 535  
 536      };
 537  
 538      var imgProto = {
 539  
 540          /**
 541           * Initialize all private members needed for the group.
 542           * @method _init
 543           * @private
 544           */
 545          _init: function() {
 546  
 547              /**
 548               * Whether this image has already been fetched.
 549               * In the case of fold-conditional groups, images won't be fetched twice.
 550               * @property _fetched
 551               * @private
 552               * @type Boolean
 553               */
 554              this._fetched = false;
 555  
 556              /**
 557               * The Node object returned from <code>Y.one</code>, to avoid repeat calls to access the DOM.
 558               * @property _imgEl
 559               * @private
 560               * @type Object
 561               */
 562              this._imgEl = null;
 563  
 564              /**
 565               * The vertical position returned from <code>getY</code>, to avoid repeat calls to access the DOM.
 566               * The Y position is checked only for images registered with fold-conditional groups. The position is checked first at page load (domready)
 567               *   and this caching enhancement assumes that the image's vertical position won't change after that first check.
 568               * @property _yPos
 569               * @private
 570               * @type Int
 571               */
 572              this._yPos = null;
 573          },
 574  
 575          /**
 576           * Displays the image; puts the URL into the DOM.
 577           * This method shouldn't be called externally, but is not private in the rare event that it needs to be called immediately.
 578           * @method fetch
 579           * @param {Number} withinY  The pixel distance from the top of the page, for which if the image lies within, it will be fetched. Undefined indicates that no check should be made, and the image should always be fetched
 580           * @return {Boolean}  Whether the image has been fetched (either during this execution or previously)
 581           */
 582          fetch: function(withinY) {
 583              if (this._fetched) {
 584                  return true;
 585              }
 586  
 587              var el = this._getImgEl(),
 588                  yPos;
 589              if (! el) {
 590                  return false;
 591              }
 592  
 593              if (withinY) {
 594                  // need a distance check
 595                  yPos = this._getYPos();
 596                  if (! yPos || yPos > withinY) {
 597                      return false;
 598                  }
 599                  Y.log('Image with id "' + this.get('domId') + '" is within distance of the fold. Fetching image.', 'info', 'imageloader');
 600              }
 601  
 602              Y.log('Fetching image with id "' + this.get('domId') + '".', 'info', 'imageloader');
 603  
 604              // apply url
 605              if (this.get('bgUrl') !== null) {
 606                  // bg url
 607                  if (this.get('isPng') && Y.UA.ie && Y.UA.ie <= 6) {
 608                      // png for which to apply AlphaImageLoader
 609                      el.setStyle('filter', 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + this.get('bgUrl') + '", sizingMethod="' + this.get('sizingMethod') + '", enabled="' + this.get('enabled') + '")');
 610                  }
 611                  else {
 612                      // regular bg image
 613                      el.setStyle('backgroundImage', "url('" + this.get('bgUrl') + "')");
 614                  }
 615              }
 616              else if (this.get('srcUrl') !== null) {
 617                  // regular src image
 618                  el.setAttribute('src', this.get('srcUrl'));
 619              }
 620  
 621              // apply attributes
 622              if (this.get('setVisible')) {
 623                  el.setStyle('visibility', 'visible');
 624              }
 625              if (this.get('width')) {
 626                  el.setAttribute('width', this.get('width'));
 627              }
 628              if (this.get('height')) {
 629                  el.setAttribute('height', this.get('height'));
 630              }
 631  
 632              this._fetched = true;
 633  
 634              return true;
 635          },
 636  
 637          /**
 638           * Gets the object (as a <code>Y.Node</code>) of the DOM element indicated by "<code>domId</code>".
 639           * @method _getImgEl
 640           * @return {Object} DOM element of the image as a <code>Y.Node</code> object
 641           * @private
 642           */
 643          _getImgEl: function() {
 644              if (this._imgEl === null) {
 645                  this._imgEl = Y.one('#' + this.get('domId'));
 646              }
 647              return this._imgEl;
 648          },
 649  
 650          /**
 651           * Gets the Y position of the node in page coordinates.
 652           * Expects that the page-coordinate position of the image won't change.
 653           * @method _getYPos
 654           * @return {Object} The Y position of the image
 655           * @private
 656           */
 657          _getYPos: function() {
 658              if (this._yPos === null) {
 659                  this._yPos = this._getImgEl().getY();
 660              }
 661              return this._yPos;
 662          }
 663  
 664      };
 665  
 666  
 667      Y.extend(Y.ImgLoadImgObj, Y.Base, imgProto);
 668  
 669  
 670  
 671  
 672  }, '3.17.2', {"requires": ["base-base", "node-style", "node-screen"]});


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1