[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/imageloader/ -> imageloader.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  
 174              /* Need to wrap the fetch function. Event Util can't distinguish prototyped functions of different instantiations.
 175               *   Leads to this scenario: groupA and groupZ both have window-scroll triggers. groupZ also has a 2-sec timeout (groupA has no timeout).
 176               *   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.
 177               *   groupA's trigger is removed and never fires, leaving images unfetched.
 178               */
 179              var wrappedFetch = function() {
 180                  this.fetch();
 181              };
 182              this._triggers.push( Y.on(type, wrappedFetch, obj, this) );
 183  
 184              return this;
 185          },
 186  
 187          /**
 188           * Adds a custom event trigger to the group.
 189           * @method addCustomTrigger
 190           * @chainable
 191           * @param {String} name  The name of the event
 192           * @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
 193           */
 194          addCustomTrigger: function(name, obj) {
 195              if (! name) {
 196                  return this;
 197              }
 198  
 199  
 200              // see comment in addTrigger()
 201              var wrappedFetch = function() {
 202                  this.fetch();
 203              };
 204              if (Y.Lang.isUndefined(obj)) {
 205                  this._triggers.push( Y.on(name, wrappedFetch, this) );
 206              }
 207              else {
 208                  this._triggers.push( obj.on(name, wrappedFetch, this) );
 209              }
 210  
 211              return this;
 212          },
 213  
 214          /**
 215           * Sets the window scroll and window resize triggers for any group that is fold-conditional (i.e., has a fold distance set).
 216           * @method _setFoldTriggers
 217           * @private
 218           */
 219          _setFoldTriggers: function() {
 220              if (this._areFoldTriggersSet) {
 221                  return;
 222              }
 223  
 224  
 225              var wrappedFoldCheck = function() {
 226                  this._foldCheck();
 227              };
 228              this._triggers.push( Y.on('scroll', wrappedFoldCheck, window, this) );
 229              this._triggers.push( Y.on('resize', wrappedFoldCheck, window, this) );
 230              this._areFoldTriggersSet = true;
 231          },
 232  
 233          /**
 234           * Performs necessary setup at domready time.
 235           * Initiates time limit for group; executes the fold check for the images.
 236           * @method _onloadTasks
 237           * @private
 238           */
 239          _onloadTasks: function() {
 240              var timeLim = this.get('timeLimit');
 241              if (timeLim && timeLim > 0) {
 242                  this._timeout = setTimeout(this._getFetchTimeout(), timeLim * 1000);
 243              }
 244  
 245              if (! Y.Lang.isUndefined(this.get('foldDistance'))) {
 246                  this._foldCheck();
 247              }
 248          },
 249  
 250          /**
 251           * Returns the group's <code>fetch</code> method, with the proper closure, for use with <code>setTimeout</code>.
 252           * @method _getFetchTimeout
 253           * @return {Function}  group's <code>fetch</code> method
 254           * @private
 255           */
 256          _getFetchTimeout: function() {
 257              var self = this;
 258              return function() { self.fetch(); };
 259          },
 260  
 261          /**
 262           * Registers an image with the group.
 263           * 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.
 264           * @method registerImage
 265           * @param {Object} config A configuration object literal with attribute name/value pairs  (passed through to a <code>Y.ImgLoadImgObj</code> constructor)
 266           * @return {Object}  <code>Y.ImgLoadImgObj</code> that was registered
 267           */
 268          registerImage: function() {
 269              var domId = arguments[0].domId;
 270              if (! domId) {
 271                  return null;
 272              }
 273  
 274  
 275              this._imgObjs[domId] = new Y.ImgLoadImgObj(arguments[0]);
 276              return this._imgObjs[domId];
 277          },
 278  
 279          /**
 280           * Displays the images in the group.
 281           * 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.
 282           * @method fetch
 283           */
 284          fetch: function() {
 285  
 286              // done with the triggers
 287              this._clearTriggers();
 288  
 289              // fetch whatever we need to by className
 290              this._fetchByClass();
 291  
 292              // fetch registered images
 293              for (var id in this._imgObjs) {
 294                  if (this._imgObjs.hasOwnProperty(id)) {
 295                      this._imgObjs[id].fetch();
 296                  }
 297              }
 298          },
 299  
 300          /**
 301           * Clears the timeout and all triggers associated with the group.
 302           * @method _clearTriggers
 303           * @private
 304           */
 305          _clearTriggers: function() {
 306              clearTimeout(this._timeout);
 307              // detach all listeners
 308              for (var i=0, len = this._triggers.length; i < len; i++) {
 309                  this._triggers[i].detach();
 310              }
 311          },
 312  
 313          /**
 314           * 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.
 315           * @method _foldCheck
 316           * @private
 317           */
 318          _foldCheck: function() {
 319  
 320              var allFetched = true,
 321                  viewReg = Y.DOM.viewportRegion(),
 322                  hLimit = viewReg.bottom + this.get('foldDistance'),
 323                      id, imgFetched, els, i, len;
 324  
 325              // unless we've uncovered new frontiers, there's no need to continue
 326              if (hLimit <= this._maxKnownHLimit) {
 327                  return;
 328              }
 329              this._maxKnownHLimit = hLimit;
 330  
 331              for (id in this._imgObjs) {
 332                  if (this._imgObjs.hasOwnProperty(id)) {
 333                      imgFetched = this._imgObjs[id].fetch(hLimit);
 334                      allFetched = allFetched && imgFetched;
 335                  }
 336              }
 337  
 338              // and by class
 339              if (this._className) {
 340                  if (this._classImageEls === null) {
 341                      // get all the relevant elements and store them
 342                      this._classImageEls = [];
 343                      els = Y.all('.' + this._className);
 344                      els.each( function(node) { this._classImageEls.push( { el: node, y: node.getY(), fetched: false } ); }, this);
 345                  }
 346                  els = this._classImageEls;
 347                  for (i=0, len = els.length; i < len; i++) {
 348                      if (els[i].fetched) {
 349                          continue;
 350                      }
 351                      if (els[i].y && els[i].y <= hLimit) {
 352                          //els[i].el.removeClass(this._className);
 353                          this._updateNodeClassName(els[i].el);
 354                          els[i].fetched = true;
 355                      }
 356                      else {
 357                          allFetched = false;
 358                      }
 359                  }
 360              }
 361  
 362              // if allFetched, remove listeners
 363              if (allFetched) {
 364                  this._clearTriggers();
 365              }
 366          },
 367  
 368          /**
 369           * Updates a given node, removing the ImageLoader class name. If the
 370           * node is an img and the classNameAction is "enhanced", then node
 371           * class name is removed and also the src attribute is set to the
 372           * image URL as well as clearing the style background image.
 373           * @method _updateNodeClassName
 374           * @param node {Node} The node to act on.
 375           * @private
 376           */
 377          _updateNodeClassName: function(node){
 378              var url;
 379  
 380              if (this.get("classNameAction") == "enhanced"){
 381  
 382                  if (node.get("tagName").toLowerCase() == "img"){
 383                      url = node.getStyle("backgroundImage");
 384                      /url\(["']?(.*?)["']?\)/.test(url);
 385                      url = RegExp.$1;
 386                      node.set("src", url);
 387                      node.setStyle("backgroundImage", "");
 388                  }
 389              }
 390  
 391              node.removeClass(this._className);
 392          },
 393  
 394          /**
 395           * 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.
 396           * @method _fetchByClass
 397           * @private
 398           */
 399          _fetchByClass: function() {
 400              if (! this._className) {
 401                  return;
 402              }
 403  
 404  
 405              Y.all('.' + this._className).each(Y.bind(this._updateNodeClassName, this));
 406          }
 407  
 408      };
 409  
 410  
 411      Y.extend(Y.ImgLoadGroup, Y.Base, groupProto);
 412  
 413  
 414      //------------------------------------------------
 415  
 416  
 417      /**
 418       * Image objects to be registered with the groups
 419       * @class ImgLoadImgObj
 420       * @extends Base
 421       * @constructor
 422       */
 423      Y.ImgLoadImgObj = function() {
 424          Y.ImgLoadImgObj.superclass.constructor.apply(this, arguments);
 425          this._init();
 426      };
 427  
 428      Y.ImgLoadImgObj.NAME = 'imgLoadImgObj';
 429  
 430      Y.ImgLoadImgObj.ATTRS = {
 431          /**
 432           * HTML DOM id of the image element.
 433           * @attribute domId
 434           * @type String
 435           */
 436          domId: {
 437              value: null,
 438              writeOnce: true
 439          },
 440  
 441          /**
 442           * Background URL for the image.
 443           * For an image whose URL is specified by "<code>background-image</code>" in the element's style.
 444           * @attribute bgUrl
 445           * @type String
 446           */
 447          bgUrl: {
 448              value: null
 449          },
 450  
 451          /**
 452           * Source URL for the image.
 453           * For an image whose URL is specified by a "<code>src</code>" attribute in the DOM element.
 454           * @attribute srcUrl
 455           * @type String
 456           */
 457          srcUrl: {
 458              value: null
 459          },
 460  
 461          /**
 462           * Pixel width of the image. Will be set as a <code>width</code> attribute on the DOM element after the image is fetched.
 463           * Defaults to the natural width of the image (no <code>width</code> attribute will be set).
 464           * Usually only used with src images.
 465           * @attribute width
 466           * @type Int
 467           */
 468          width: {
 469              value: null
 470          },
 471  
 472          /**
 473           * Pixel height of the image. Will be set as a <code>height</code> attribute on the DOM element after the image is fetched.
 474           * Defaults to the natural height of the image (no <code>height</code> attribute will be set).
 475           * Usually only used with src images.
 476           * @attribute height
 477           * @type Int
 478           */
 479          height: {
 480              value: null
 481          },
 482  
 483          /**
 484           * Whether the image's <code>style.visibility</code> should be set to <code>visible</code> after the image is fetched.
 485           * Used when setting images as <code>visibility:hidden</code> prior to image fetching.
 486           * @attribute setVisible
 487           * @type Boolean
 488           */
 489          setVisible: {
 490              value: false
 491          },
 492  
 493          /**
 494           * Whether the image is a PNG.
 495           * PNG images get special treatment in that the URL is specified through AlphaImageLoader for IE, versions 6 and earlier.
 496           * Only used with background images.
 497           * @attribute isPng
 498           * @type Boolean
 499           */
 500          isPng: {
 501              value: false
 502          },
 503  
 504          /**
 505           * AlphaImageLoader <code>sizingMethod</code> property to be set for the image.
 506           * Only set if <code>isPng</code> value for this image is set to <code>true</code>.
 507           * Defaults to <code>scale</code>.
 508           * @attribute sizingMethod
 509           * @type String
 510           */
 511          sizingMethod: {
 512              value: 'scale'
 513          },
 514  
 515          /**
 516           * AlphaImageLoader <code>enabled</code> property to be set for the image.
 517           * Only set if <code>isPng</code> value for this image is set to <code>true</code>.
 518           * Defaults to <code>true</code>.
 519           * @attribute enabled
 520           * @type String
 521           */
 522          enabled: {
 523              value: 'true'
 524          }
 525  
 526      };
 527  
 528      var imgProto = {
 529  
 530          /**
 531           * Initialize all private members needed for the group.
 532           * @method _init
 533           * @private
 534           */
 535          _init: function() {
 536  
 537              /**
 538               * Whether this image has already been fetched.
 539               * In the case of fold-conditional groups, images won't be fetched twice.
 540               * @property _fetched
 541               * @private
 542               * @type Boolean
 543               */
 544              this._fetched = false;
 545  
 546              /**
 547               * The Node object returned from <code>Y.one</code>, to avoid repeat calls to access the DOM.
 548               * @property _imgEl
 549               * @private
 550               * @type Object
 551               */
 552              this._imgEl = null;
 553  
 554              /**
 555               * The vertical position returned from <code>getY</code>, to avoid repeat calls to access the DOM.
 556               * The Y position is checked only for images registered with fold-conditional groups. The position is checked first at page load (domready)
 557               *   and this caching enhancement assumes that the image's vertical position won't change after that first check.
 558               * @property _yPos
 559               * @private
 560               * @type Int
 561               */
 562              this._yPos = null;
 563          },
 564  
 565          /**
 566           * Displays the image; puts the URL into the DOM.
 567           * This method shouldn't be called externally, but is not private in the rare event that it needs to be called immediately.
 568           * @method fetch
 569           * @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
 570           * @return {Boolean}  Whether the image has been fetched (either during this execution or previously)
 571           */
 572          fetch: function(withinY) {
 573              if (this._fetched) {
 574                  return true;
 575              }
 576  
 577              var el = this._getImgEl(),
 578                  yPos;
 579              if (! el) {
 580                  return false;
 581              }
 582  
 583              if (withinY) {
 584                  // need a distance check
 585                  yPos = this._getYPos();
 586                  if (! yPos || yPos > withinY) {
 587                      return false;
 588                  }
 589              }
 590  
 591  
 592              // apply url
 593              if (this.get('bgUrl') !== null) {
 594                  // bg url
 595                  if (this.get('isPng') && Y.UA.ie && Y.UA.ie <= 6) {
 596                      // png for which to apply AlphaImageLoader
 597                      el.setStyle('filter', 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + this.get('bgUrl') + '", sizingMethod="' + this.get('sizingMethod') + '", enabled="' + this.get('enabled') + '")');
 598                  }
 599                  else {
 600                      // regular bg image
 601                      el.setStyle('backgroundImage', "url('" + this.get('bgUrl') + "')");
 602                  }
 603              }
 604              else if (this.get('srcUrl') !== null) {
 605                  // regular src image
 606                  el.setAttribute('src', this.get('srcUrl'));
 607              }
 608  
 609              // apply attributes
 610              if (this.get('setVisible')) {
 611                  el.setStyle('visibility', 'visible');
 612              }
 613              if (this.get('width')) {
 614                  el.setAttribute('width', this.get('width'));
 615              }
 616              if (this.get('height')) {
 617                  el.setAttribute('height', this.get('height'));
 618              }
 619  
 620              this._fetched = true;
 621  
 622              return true;
 623          },
 624  
 625          /**
 626           * Gets the object (as a <code>Y.Node</code>) of the DOM element indicated by "<code>domId</code>".
 627           * @method _getImgEl
 628           * @return {Object} DOM element of the image as a <code>Y.Node</code> object
 629           * @private
 630           */
 631          _getImgEl: function() {
 632              if (this._imgEl === null) {
 633                  this._imgEl = Y.one('#' + this.get('domId'));
 634              }
 635              return this._imgEl;
 636          },
 637  
 638          /**
 639           * Gets the Y position of the node in page coordinates.
 640           * Expects that the page-coordinate position of the image won't change.
 641           * @method _getYPos
 642           * @return {Object} The Y position of the image
 643           * @private
 644           */
 645          _getYPos: function() {
 646              if (this._yPos === null) {
 647                  this._yPos = this._getImgEl().getY();
 648              }
 649              return this._yPos;
 650          }
 651  
 652      };
 653  
 654  
 655      Y.extend(Y.ImgLoadImgObj, Y.Base, imgProto);
 656  
 657  
 658  
 659  
 660  }, '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