imagesloaded源码分析
来源:互联网 发布:移动网络电视好吗? 编辑:程序博客网 时间:2024/06/04 19:31
小白的第一篇源码分析, 选择的是imagesloaded插件(版本4.1.3), 这个被masonry使用, 作者居然是同一个人!
git clone
之后, 打开imagesloaded.pkgd.js
(这个是将ev-emitter打包进去的, 可以直接使用)
源码结构
- EvEmitter(观察者模式实现)
- 工具方法(extend, makeArray等)
- ImagesLoaded构造函数
- ImagesLoaded原型, 原型属性, 方法
- LoadingImage构造函数
- LoadingImage原型, 原型属性, 方法
- Background构造函数
- Background原型, 原型属性, 方法
- 构造jQuery插件(makeJQueryPlugin方法)
源码上来就是EvEmitter部分, 这我们已经用过太多了, 其实说到底就是观察者模式, 那就先分析下它吧.
EvEmitter(观察者模式实现)
为了避免篇幅过长, EvEmitter源码分析见我的另一篇博客.
工具方法(extend, makeArray等)
extend
简单的属性拷贝, 无须多讲,
但是注意, 这会将源对象的原型上的属性也拷贝到目标对象
// extend objectsfunction extend( a, b ) { for ( var prop in b ) { a[ prop ] = b[ prop ]; } return a;}
makeArray
的实现
// 将一个element或者nodeList转换为数组// turn element or nodeList into an arrayfunction makeArray(obj) { var ary = []; // 如果是数组, 直接获取 if (Array.isArray(obj)) { // use object if already an array ary = obj; } // 如果是NodeList对象 else if (typeof obj.length == 'number') { // convert nodeList to array for (var i=0; i < obj.length; i++) { ary.push(obj[i]); } } else { // array of single index ary.push(obj); } return ary;}
注意这里的第二个判断else if
部分并不十分严格,
个人理解是作者考虑到自身库的使用情况而做的判断.
ImagesLoaded构造函数
为了方便理解, 建议先看完LoadingImage和Background部分再回来看这部分, 因为ImagesLoaded部分要借助LoadingImage
和Background
.
* @param {Array, Element, NodeList, String} elem* @param {Object or Function} options - if function, use as callback* @param {Function} onAlways - callback function*/function ImagesLoaded(elem, options, onAlways) { // 保证调用返回为ImagesLoaded的实例 // coerce ImagesLoaded() without new, to be new ImagesLoaded() if (!(this instanceof ImagesLoaded)) { return new ImagesLoaded(elem, options, onAlways); } // elem参数为选择器 // use elem as selector string if (typeof elem == 'string') { elem = document.querySelectorAll(elem); } // elements属性为数组 this.elements = makeArray(elem); // options属性为对象 this.options = extend({}, this.options); // new ImagesLoaded(elem, function)形式调用 if (typeof options == 'function') { onAlways = options; } else { // new ImagesLoaded(elem, options, function)形式调用 extend(this.options, options); } // 指定always回调 if (onAlways) { this.on('always', onAlways); } // 寻找图片 this.getImages(); // 如果有jQuery存在, 添加jqDeferred字段为一个Deferred对象 if ($) { // add jQuery Deferred object this.jqDeferred = new $.Deferred(); } // 检测图片加载图片 // HACK check async to allow time to bind listeners setTimeout(function() { this.check(); }.bind(this));}
先看下getImages
方法, 存在<img />
和背景图两种情况
ImagesLoaded.prototype.getImages = function() { this.images = []; // 对elements和循环调用addElementImages // filter & find items if we have an item selector this.elements.forEach(this.addElementImages, this);};
addElementImages
依赖addImage方法和addElementBackgroundImages方法:
/** * @param {Node} element */ImagesLoaded.prototype.addElementImages = function(elem) { // 图片(<img>)调用addImage方法 // 注意nodeName获取到的是大写, 这里转大写再判断应该更安全 // filter siblings if (elem.nodeName == 'IMG') { this.addImage(elem); } // 如果还有背景图, 调用addElementBackgroundImages方法 // get background image on element if (this.options.background === true) { this.addElementBackgroundImages(elem); } // 指定元素白名单, 只有下面三种才继续进行 ` var elementNodeTypes = { 1: true, // Element 9: true, // Document 11: true // DocumentFragment }; ` // find children // no non-element nodes, #143 var nodeType = elem.nodeType; if (!nodeType || !elementNodeTypes[nodeType]) { return; } // 从子结点中找图片 var childImgs = elem.querySelectorAll('img'); // concat childElems to filterFound array for (var i = 0; i < childImgs.length; i++) { var img = childImgs[i]; this.addImage(img); } // 从子结点中找背景图 // get child background images if (typeof this.options.background == 'string') { var children = elem.querySelectorAll(this.options.background); for (i = 0; i < children.length; i++) { var child = children[i]; this.addElementBackgroundImages(child); } }};
addImage
方法
// 往images中添加一个LoadingImage实例ImagesLoaded.prototype.addImage = function(img) { var loadingImage = new LoadingImage(img); this.images.push(loadingImage);};
addElementBackgroundImages
方法
ImagesLoaded.prototype.addElementBackgroundImages = function(elem) { // 获取最终应用在元素上的所有CSS属性对象 var style = getComputedStyle(elem); if (!style) { // 兼容firefox的bug, 好 // Firefox returns null if in a hidden iframe https://bugzil.la/548397 return; } // 获取背景图片地址, 一般形式为url("xxx.jpg"), 引号可不写或者写单引号 // get url inside url("...") var reURL = /url\((['"])?(.*?)\1\)/gi; var matches = reURL.exec(style.backgroundImage); // 亮点 // 因为css3中支持多背景图, 所以这里是while而不是if while (matches !== null) { // 分组2(matches[2]), 也就是上述reURL的`(.*?)`部分 var url = matches && matches[2]; if (url) { this.addBackground(url, elem); } matches = reURL.exec(style.backgroundImage); }};
addBackground
方法
// 同addImage思路一致, 不过换成了BackgroundImagesLoaded.prototype.addBackground = function(url, elem) { var background = new Background(url, elem); this.images.push(background);};
*到这里可以看出作者思路清晰, 写法严谨.
对<img />
和背景图分别用对象表示, 然后在添加图片时又层层调用,
对css3也有考虑, 兼容性也有涉及(firefox下的getComputedStyle).
说实话, 若不是看源代码, 谁能知道这些兼容性问题呢, 谁又能说考虑这么全面呢?*
接着说构造函数中的check
方法:
建议先看progress方法
ImagesLoaded.prototype.check = function() { var _this = this; // 加载完成数 this.progressedCount = 0; // 是否有加载失败的图片 this.hasAnyBroken = false; // complete if no images if (!this.images.length) { this.complete(); return; } function onProgress(image, elem, message) { // HACK - Chrome triggers event before object properties have changed. #83 setTimeout(function() { _this.progress(image, elem, message); }); } this.images.forEach(function(loadingImage) { // 对progress事件确保只触发一次 loadingImage.once('progress', onProgress); // 对每张图都调用自身的check方法 // 无论是背景图或是`<img />`, 这里都抽象为`一张加载中的的图`(参数名loadingImage也起得很好), 保证了check接口统一, 好 loadingImage.check(); });};
progress
方法
ImagesLoaded.prototype.progress = function(image, elem, message) { this.progressedCount++; this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded; // progress event // 触发progress回调 // imgLoad.on( 'progress', function( instance, image ) { // ... // }); this.emitEvent('progress', [this, image, elem]); // 处理以jquery插件的调用方式 // $('#container').imagesLoaded() // .progress( function( instance, image ) { // ... // }); if (this.jqDeferred && this.jqDeferred.notify) { this.jqDeferred.notify(this, image); } // 查看是否已经加载完成所有图片 // check if completed if (this.progressedCount == this.images.length) { this.complete(); } // 兼容调试 if (this.options.debug && console) { console.log('progress: ' + message, image, elem); }};
complete
较为简单
ImagesLoaded.prototype.complete = function() { // 如果没有完全加载完, 都进入到fail回调 var eventName = this.hasAnyBroken ? 'fail' : 'done'; this.isComplete = true; this.emitEvent(eventName, [this]); // always回调 this.emitEvent('always', [this]); // jquery兼容 // $('#container').imagesLoaded() // .always( function( instance ) { // console.log('all images loaded'); // }) // .done( function( instance ) { // console.log('all images successfully loaded'); // }) // .fail( function() { // console.log('all images loaded, at least one is broken'); // }); if (this.jqDeferred) { var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve'; this.jqDeferred[jqMethod](this); }};
LoadingImage构造函数
这表示一个加载中的图片:
// 参数img是一个图片dom结点function LoadingImage(img) { this.img = img;}
// 为LoadingImage指定原型LoadingImage.prototype = Object.create(EvEmitter.prototype);
对Object.create
不明白的可以看这里.
LoadingImage原型, 原型属性, 方法
原型方法check
(检查图片是否加载完成)
LoadingImage.prototype.check = function() { // 手动检查图片是否加载完成 // If complete is true and browser supports natural sizes, // try to check for image status manually. var isComplete = this.getIsImageComplete(); if (isComplete) { // report based on naturalWidth this.confirm(this.img.naturalWidth !== 0, 'naturalWidth'); return; } // If none of the checks above matched, simulate loading on detached element. this.proxyImage = new Image(); this.proxyImage.addEventListener('load', this); this.proxyImage.addEventListener('error', this); // 这里是说在firefox下, 只用proxyproxyImage会有问题 // #191是说issue编号 // issue讨论地址: https://github.com/desandro/imagesloaded/issues/191 // 相关代码: https://github.com/desandro/imagesloaded/blob/v3.2.0/imagesloaded.js#L287-L289 // bind to image as well for Firefox. #191 this.img.addEventListener('load', this); this.img.addEventListener('error', this); this.proxyImage.src = this.img.src;};
再看上述用到的getIsImageComplete
和confirm
的具体实现:
// 检测图片加载完成(加载错误也算)LoadingImage.prototype.getIsImageComplete = function() { return this.img.complete && this.img.naturalWidth !== undefined;};// 确定加载完成(设置标志和触发事件)LoadingImage.prototype.confirm = function( isLoaded, message ) { this.isLoaded = isLoaded; this.emit( 'progress', this, this.img, message );};
getIsImageComplete
方法用到了图片的complete
属性和naturalWidth
属性. complete
还算是用过, 它说的是图片是否加载完成, 兼容性还不清楚. naturalWidth
说的是图片的原始大小(加样式之前), 兼容IE9+.
complete
在我写这篇文章时, mdn上还没有详情页面, 只在列表中出现, 引用如下:
HTMLImageElement.complete (Read only)
Returns a Boolean that is true if the browser has finished fetching the image, whether successful or not. It also shows true, if the image has no src value.
LoadingImage事件相关原型方法
// ----- events ----- //// 触发指定事件// trigger specified handler for event typeLoadingImage.prototype.handleEvent = function(event) { var method = 'on' + event.type; if (this[method]) { this[method](event); }};// 处理load事件LoadingImage.prototype.onload = function() { this.confirm(true, 'onload'); this.unbindEvents();};// 处理error事件LoadingImage.prototype.onerror = function() { this.confirm(false, 'onerror'); this.unbindEvents();};// 移除事件LoadingImage.prototype.unbindEvents = function() { this.proxyImage.removeEventListener('load', this); this.proxyImage.removeEventListener('error', this); this.img.removeEventListener('load', this); this.img.removeEventListener('error', this);};
Background部分(代表一张背景图)
这部分借助了上述的LoadingImage
, 毕竟也是一张图片嘛.
直接上代码, 相信一看就懂:
// -------------------------- Background -------------------------- //function Background(url, element) { this.url = url; this.element = element; this.img = new Image();}// 亮点// 指定原型为LoadingImage.prototype, 从而一些方法可以继承过来// inherit LoadingImage prototypeBackground.prototype = Object.create(LoadingImage.prototype);// 覆写原型方法Background.prototype.check = function() { this.img.addEventListener('load', this); this.img.addEventListener('error', this); this.img.src = this.url; // 调用继承过来的方法检测 // check if image is already complete var isComplete = this.getIsImageComplete(); if (isComplete) { this.confirm(this.img.naturalWidth !== 0, 'naturalWidth'); this.unbindEvents(); }};// 覆写原型方法Background.prototype.unbindEvents = function() { this.img.removeEventListener('load', this); this.img.removeEventListener('error', this);};// 覆写原型方法Background.prototype.confirm = function(isLoaded, message) { this.isLoaded = isLoaded; this.emitEvent('progress', [this, this.element, message]);};
jQuery插件
ImagesLoaded.makeJQueryPlugin = function(jQuery) { jQuery = jQuery || window.jQuery; if (!jQuery) { return; } // set local variable $ = jQuery; // $().imagesLoaded() $.fn.imagesLoaded = function(options, callback) { var instance = new ImagesLoaded(this, options, callback); // 将jquery中promise的属性和方法拷贝到当前选中的元素上来 // jquery中promise方法的实现 // promise: function( obj ) { // return obj != null ? jQuery.extend( obj, promise ) : promise; // } // 所以有这种调用方式: // $('#container').imagesLoaded() // .always(function(instance) { // }) // .done(function(instance) { // }) // .fail(function() { // }) // .progress(function(instance, image) { // }); return instance.jqDeferred.promise($(this)); };};// try making pluginImagesLoaded.makeJQueryPlugin();
欢迎批评指正!
- imagesloaded源码分析
- imagesLoaded图片预加载
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析:SparseArray分析
- 源码- Spark Broadcast源码分析
- Android源码/框架源码分析
- 用jQuery imagesLoaded plugin实现页面加载效果
- 检测图片是否正确加载的js插件-imagesLoaded
- 基于 infinitescroll、isotope、imagesLoaded 的瀑布流网站
- imagesLoaded-检测图片是否正确加载的js插件
- imagesLoaded-检测图片是否正确加载的js插件
- Spring Boot结合cxf发布WebService接口jar包冲突的问题
- A. Vicious Keyboard Codeforce
- eclipse和myeclipse取消所有断点
- MySQL查看和设置会话系统变量的方法
- jsp页面中引入js路径的问题
- imagesloaded源码分析
- NOI2015 软件包管理器
- springboot中使用定时任务,异步调用,自定义配置参数(八)
- 每日一练-10
- 第五章
- 80C51单片机模仿实例100—2 从左到右的流水灯
- 为啥文件有写权限还是说只读
- [Machine Learning & Algorithm] 随机森林(Random Forest)
- MySQL数据库基本操作(DDL)