The source code

    /**
     * @ignore
     * 数据延迟加载组件
     */
    KISSY.add('datalazyload', function (S, DOM, Event, Base, undefined) {

        var win = S.Env.host,
            doc = win.document,
            IMG_SRC_DATA = 'data-ks-lazyload',
            AREA_DATA_CLS = 'ks-datalazyload',
            CUSTOM = '-custom',
            DEFAULT = 'default',
            NONE = 'none',
            SCROLL = 'scroll',
            TOUCH_MOVE = "touchmove",
            RESIZE = 'resize',
            DURATION = 100;

        // 加载图片 src
        var loadImgSrc = function (img, flag) {
            flag = flag || IMG_SRC_DATA;
            var dataSrc = img.getAttribute(flag);

            if (dataSrc && img.src != dataSrc) {
                img.src = dataSrc;
                img.removeAttribute(flag);
            }
        };

        // 从 textarea 中加载数据
        function loadAreaData(textarea, execScript) {
            // 采用隐藏 textarea 但不去除方式，去除会引发 Chrome 下错乱
            textarea.style.display = NONE;
            textarea.className = ''; // clear hook
            var content = DOM.create('<div>');
            // textarea 直接是 container 的儿子
            textarea.parentNode.insertBefore(content, textarea);
            DOM.html(content, textarea.value, execScript);
        }

        function getCallbackKey(el, fn) {
            var id, fid;
            if (!(id = el.id)) {
                id = el.id = S.guid('ks-lazyload');
            }

            if (!(fid = fn.ksLazyloadId)) {
                fid = fn.ksLazyloadId = S.guid('ks-lazyload');
            }
            return id + fid;
        }

        function cacheWidth(el) {
            if (el._ks_lazy_width) {
                return el._ks_lazy_width;
            }
            return el._ks_lazy_width = DOM.outerWidth(el);
        }

        function cacheHeight(el) {
            if (el._ks_lazy_height) {
                return el._ks_lazy_height;
            }
            return el._ks_lazy_height = DOM.outerHeight(el);
        }

        /**
         * whether part of elem can be seen by user.
         * note: it will not handle display none.
         * @ignore
         */
        function elementInViewport(elem, windowRegion, containerRegion) {
            // it's better to removeElements,
            // but if user want to append it later?
            // use addElements instead
            // if (!inDocument(elem)) {
            //    return false;
            // }
            // display none or inside display none
            if (!elem.offsetWidth) {
                return false;
            }
            var elemOffset = DOM.offset(elem),
                inContainer = true,
                inWin,
                left = elemOffset.left,
                top = elemOffset.top,
                elemRegion = {
                    left: left,
                    top: top,
                    right: left + cacheWidth(elem),
                    bottom: top + cacheHeight(elem)
                };

            inWin = isCross(windowRegion, elemRegion);

            if (inWin && containerRegion) {
                inContainer = isCross(containerRegion, elemRegion);
            }

            // 确保在容器内出现
            // 并且在视窗内也出现
            return inContainer && inWin;
        }

        /**
         * LazyLoad elements which are out of current viewPort.
         * @class KISSY.DataLazyload
         * @extends KISSY.Base
         */
        function DataLazyload(container, config) {
            var self = this;

            // factory or constructor
            if (!(self instanceof DataLazyload)) {
                return new DataLazyload(container, config);
            }

            var newConfig = container;

            if (!S.isPlainObject(newConfig)) {
                newConfig = config || {};
                if (container) {
                    newConfig.container = container;
                }
            }

            DataLazyload.superclass.constructor.call(self, newConfig);

            // 需要延迟下载的图片
            // self._images

            // 需要延迟处理的 textarea
            // self._textareas

            // 和延迟项绑定的回调函数
            self._callbacks = {};
            self._containerIsNotDocument = self.get('container').nodeType != 9;

            self['_filterItems']();
            self['_initLoadEvent']();
        }

        DataLazyload.ATTRS = {
            /**
             * Distance outside viewport or specified container to pre load.
             * default: pre load one screen height and width.
             * @cfg {Number|Object} diff
             *
             *  for example:
             *
             *      diff : 50 // pre load 50px outside viewport or specified container
             *      // or more detailed :
             *      diff: {
             *          left:20, // pre load 50px outside left edge of viewport or specified container
             *          right:30, // pre load 50px outside right edge of viewport or specified container
             *          top:50, // pre load 50px outside top edge of viewport or specified container
             *          bottom:60 // pre load 50px outside bottom edge of viewport or specified container
             *      }
             */
            /**
             * @ignore
             */
            diff: {
                value: DEFAULT
            },

            // TODO: add containerDiff for container is not document

            /**
             * Placeholder img url for lazy loaded _images if image 's src is empty.
             * must be not empty!
             *
             * Defaults to: http://a.tbcdn.cn/kissy/1.0.0/build/imglazyload/spaceball.gif
             * @cfg {String} placeholder
             */
            /**
             * @ignore
             */
            placeholder: {
                value: 'http://a.tbcdn.cn/kissy/1.0.0/build/imglazyload/spaceball.gif'
            },

            /**
             * Whether execute script in lazy loaded textarea.
             * default: true
             * @cfg {Boolean} execScript
             */
            /**
             * @ignore
             */
            execScript: {
                value: true
            },

            /**
             * Container which will be monitor scroll event to lazy load elements within it.
             * default: document
             * @cfg {HTMLElement} container
             */
            /**
             * @ignore
             */
            container: {
                setter: function (el) {
                    el = el || doc;
                    if (S.isWindow(el)) {
                        el = el.document;
                    } else {
                        el = DOM.get(el);
                        if (DOM.nodeName(el) == 'body') {
                            el = el.ownerDocument;
                        }
                    }
                    return el;
                },
                valueFn: function () {
                    return doc;
                }
            },

            /**
             * Whether destroy this component when all lazy loaded elements are loaded.
             * default: true
             * @cfg {Boolean} autoDestroy
             */
            /**
             * @ignore
             */
            autoDestroy: {
                value: true
            }
        };

        // 两块区域是否相交
        function isCross(r1, r2) {
            var r = {};
            r.top = Math.max(r1.top, r2.top);
            r.bottom = Math.min(r1.bottom, r2.bottom);
            r.left = Math.max(r1.left, r2.left);
            r.right = Math.min(r1.right, r2.right);
            return r.bottom >= r.top && r.right >= r.left;
        }

        S.extend(DataLazyload, Base, {

            /**
             * get _images and _textareas which will lazyload.
             * @private
             */
            '_filterItems': function () {
                var self = this,
                    userConfig = self.userConfig,
                    container = self.get("container"),
                    _images = [],
                    _textareas = [],
                    containers = [container];

                // 兼容 1.2 传入数组，进入兼容模式，不检测 container 区域
                if (S.isArray(userConfig.container)) {
                    self._backCompact = 1;
                    containers = userConfig.container;
                }

                S.each(containers, function (container) {
                    _images = _images.concat(S.filter(DOM.query('img', container), self['_filterImg'], self));
                    _textareas = _textareas.concat(DOM.query('textarea.' + AREA_DATA_CLS, container));
                });

                self._images = _images;
                self._textareas = _textareas;

            },

            /**
             * filter for lazyload image
             * @private
             */
            '_filterImg': function (img) {
                var self = this,
                    placeholder = self.get("placeholder");

                if (img.getAttribute(IMG_SRC_DATA)) {
                    if (!img.src) {
                        img.src = placeholder;
                    }
                    return true;
                }

                return undefined;
            },

            /**
             * attach scroll/resize event
             * @private
             */
            '_initLoadEvent': function () {
                var self = this,
                    img = new Image(),
                    placeholder = self.get("placeholder"),
                    autoDestroy = self.get("autoDestroy"),
                // 加载延迟项
                    loadItems = function () {
                        self['_loadItems']();
                        if (autoDestroy && self['_isLoadAllLazyElements']()) {
                            self.destroy();
                        }
                    };

                // 加载函数
                self._loadFn = S.buffer(loadItems, DURATION, self);

                self.resume();

                img.src = placeholder;

                function firstLoad() {
                    // 需要立即加载一次，以保证第一屏的延迟项可见
                    if (!self['_isLoadAllLazyElements']()) {
                        S.ready(loadItems);
                    }
                }

                if (img.complete) {
                    firstLoad()
                } else {
                    img.onload = firstLoad;
                }
            },

            /**
             * force datalazyload to recheck constraints and load lazyload
             *
             */
            refresh: function () {
                this._loadFn();
            },

            /**
             * lazyload all items
             * @private
             */
            '_loadItems': function () {
                var self = this,
                    containerRegion,
                    container = self.get('container'),
                    windowRegion;
                // container is display none
                if (self._containerIsNotDocument && !container.offsetWidth) {
                    return;
                }
                windowRegion = self['_getBoundingRect']();
                // 兼容，不检测 container
                if (!self._backCompact && self._containerIsNotDocument) {
                    containerRegion = self['_getBoundingRect'](self.get('container'));
                }
                self['_loadImgs'](windowRegion, containerRegion);
                self['_loadTextAreas'](windowRegion, containerRegion);
                self['_fireCallbacks'](windowRegion, containerRegion);
            },

            /**
             * lazyload images
             * @private
             */
            '_loadImgs': function (windowRegion, containerRegion) {
                var self = this;
                self._images = S.filter(self._images, function (img) {
                    if (elementInViewport(img, windowRegion, containerRegion)) {
                        return loadImgSrc(img);
                    } else {
                        return true;
                    }
                }, self);
            },

            /**
             * lazyload textareas
             * @private
             */
            '_loadTextAreas': function (windowRegion, containerRegion) {
                var self = this, execScript = self.get('execScript');
                self._textareas = S.filter(self._textareas, function (textarea) {
                    if (elementInViewport(textarea, windowRegion, containerRegion)) {
                        return loadAreaData(textarea, execScript);
                    } else {
                        return true;
                    }
                }, self);
            },

            /**
             * fire callbacks
             * @private
             */
            '_fireCallbacks': function (windowRegion, containerRegion) {
                var self = this,
                    callbacks = self._callbacks;

                // may call addCallback/removeCallback
                S.each(callbacks, function (callback, key) {
                    var el = callback.el,
                        remove = false,
                        fn = callback.fn;
                    if (elementInViewport(el, windowRegion, containerRegion)) {
                        remove = fn.call(el);
                    }
                    if (remove !== false) {
                        delete callbacks[key];
                    }
                });
            },

            /**
             * Register callback function. When el is in viewport, then fn is called.
             * @param {HTMLElement|String} el html element to be monitored.
             * @param {function(this: HTMLElement): boolean} fn
             * Callback function to be called when el is in viewport.
             * return false to indicate el is actually not in viewport( for example display none element ).
             */
            'addCallback': function (el, fn) {
                var self = this,
                    callbacks = self._callbacks;
                el = DOM.get(el);

                callbacks[getCallbackKey(el, fn)] = {
                    el: DOM.get(el),
                    fn: fn
                };

                // add 立即检测，防止首屏元素问题
                self._loadFn();
            },

            /**
             * Remove a callback function. See {@link KISSY.DataLazyload#addCallback}
             * @param {HTMLElement|String} el html element to be monitored.
             * @param {Function} [fn] Callback function to be called when el is in viewport.
             *                        If not specified, remove all callbacks associated with el.
             */
            'removeCallback': function (el, fn) {
                var callbacks = this._callbacks;
                el = DOM.get(el);
                delete callbacks[getCallbackKey(el, fn)];
            },

            /**
             * get to be lazy loaded elements
             * @return {Object} eg: {images:,textareas:}
             */
            'getElements': function () {
                return {
                    images: this._images,
                    textareas: this._textareas
                };
            },

            /**
             * Add a array of imgs or textareas to be lazy loaded to monitor list.
             * @param {HTMLElement[]|String} els Array of imgs or textareas to be lazy loaded or selector
             */
            'addElements': function (els) {
                if (typeof els == 'string') {
                    els = DOM.query(els);
                } else if (!S.isArray(els)) {
                    els = [els];
                }
                var self = this,
                    imgs = self._images || [],
                    textareas = self._textareas || [];
                S.each(els, function (el) {
                    var nodeName = el.nodeName.toLowerCase();
                    if (nodeName == "img") {
                        if (!S.inArray(el, imgs)) {
                            imgs.push(el);
                        }
                    } else if (nodeName == "textarea") {
                        if (!S.inArray(el, textareas)) {
                            textareas.push(el);
                        }
                    }
                });
                self._images = imgs;
                self._textareas = textareas;
            },

            /**
             * Remove a array of element from monitor list. See {@link KISSY.DataLazyload#addElements}.
             * @param {HTMLElement[]|String} els Array of imgs or textareas to be lazy loaded
             */
            'removeElements': function (els) {
                if (typeof els == 'string') {
                    els = DOM.query(els);
                } else if (!S.isArray(els)) {
                    els = [els];
                }
                var self = this,
                    imgs = [],
                    textareas = [];
                S.each(self._images, function (img) {
                    if (!S.inArray(img, els)) {
                        imgs.push(img);
                    }
                });
                S.each(self._textareas, function (textarea) {
                    if (!S.inArray(textarea, els)) {
                        textareas.push(textarea);
                    }
                });
                self._images = imgs;
                self._textareas = textareas;
            },

            /**
             * get c's bounding textarea.
             * @param {window|HTMLElement} [c]
             * @private
             */
            '_getBoundingRect': function (c) {
                var vh, vw, left, top;

                if (c !== undefined) {
                    vh = DOM.outerHeight(c);
                    vw = DOM.outerWidth(c);
                    var offset = DOM.offset(c);
                    left = offset.left;
                    top = offset.top;
                } else {
                    vh = DOM.viewportHeight();
                    vw = DOM.viewportWidth();
                    left = DOM.scrollLeft();
                    top = DOM.scrollTop();
                }

                var diff = this.get("diff"),
                    diffX = diff === DEFAULT ? vw : diff,
                    diffX0 = 0,
                    diffX1 = diffX,
                    diffY = diff === DEFAULT ? vh : diff,
                // 兼容，默认只向下预读
                    diffY0 = 0,
                    diffY1 = diffY,
                    right = left + vw,
                    bottom = top + vh;

                if (S.isObject(diff)) {
                    diffX0 = diff.left || 0;
                    diffX1 = diff.right || 0;
                    diffY0 = diff.top || 0;
                    diffY1 = diff.bottom || 0;
                }

                left -= diffX0;
                right += diffX1;
                top -= diffY0;
                bottom += diffY1;

                return {
                    left: left,
                    top: top,
                    right: right,
                    bottom: bottom
                };
            },

            /**
             * get num of items waiting to lazyload
             * @private
             */
            '_isLoadAllLazyElements': function () {
                var self = this;
                return (self._images.length +
                    self._textareas.length +
                    (S.isEmptyObject(self._callbacks) ? 0 : 1)) == 0;
            },

            /**
             * pause lazyload
             */
            pause: function () {
                var self = this,
                    load = self._loadFn;
                if (self._destroyed) {
                    return;
                }
                Event.remove(win, SCROLL, load);
                Event.remove(win, TOUCH_MOVE, load);
                Event.remove(win, RESIZE, load);
                load.stop();
                if (self._containerIsNotDocument) {
                    var c = self.get('container');
                    Event.remove(c, SCROLL, load);
                    Event.remove(c, TOUCH_MOVE, load);
                }
            },

            /**
             * resume lazyload
             */
            resume: function () {
                var self = this,
                    load = self._loadFn;
                if (self._destroyed) {
                    return;
                }
                // scroll 和 resize 时，加载图片
                Event.on(win, SCROLL, load);
                Event.on(win, TOUCH_MOVE, load);
                Event.on(win, RESIZE, load);
                if (self._containerIsNotDocument) {
                    var c = self.get('container');
                    Event.on(c, SCROLL, load);
                    Event.on(c, TOUCH_MOVE, load);
                }
            },

            /**
             * Destroy this component.Will fire destroy event.
             */
            destroy: function () {
                var self = this;
                self.pause();
                self._callbacks = {};
                self._images = [];
                self._textareas = [];
                S.log("datalazyload is destroyed!");
                self.fire("destroy");
                self._destroyed = 1;
            }
        });

        /**
         * Load lazyload textarea and imgs manually.
         * @ignore
         * @method
         * @param {HTMLElement[]} containers Containers with in which lazy loaded elements are loaded.
         * @param {String} type Type of lazy loaded element. "img" or "textarea"
         * @param {String} [flag] flag which will be searched to find lazy loaded elements from containers.
         * Default "data-ks-lazyload-custom" for img attribute and "ks-lazyload-custom" for textarea css class.
         */
        function loadCustomLazyData(containers, type, flag) {
            if (type === 'img-src') {
                type = 'img';
            }

            // 支持数组
            if (!S.isArray(containers)) {
                containers = [DOM.get(containers)];
            }

            var imgFlag = flag || (IMG_SRC_DATA + CUSTOM),
                areaFlag = flag || (AREA_DATA_CLS + CUSTOM);

            S.each(containers, function (container) {
                // 遍历处理
                if (type == 'img') {
                    DOM.query('img', container).each(function (img) {
                        loadImgSrc(img, imgFlag);
                    });
                } else {
                    DOM.query('textarea.' + areaFlag, container).each(function (textarea) {
                        loadAreaData(textarea, true);
                    });
                }
            });
        }

        DataLazyload.loadCustomLazyData = loadCustomLazyData;

        S.DataLazyload = DataLazyload;

        return DataLazyload;

    }, { requires: ['dom', 'event', 'base'] });

    /**
     * @ignore
     *
     * NOTES:
     *
     * 模式为 auto 时：
     *  1. 在 Firefox 下非常完美。脚本运行时，还没有任何图片开始下载，能真正做到延迟加载。
     *  2. 在 IE 下不尽完美。脚本运行时，有部分图片已经与服务器建立链接，这部分 abort 掉，
     *     再在滚动时延迟加载，反而增加了链接数。
     *  3. 在 Safari 和 Chrome 下，因为 webkit 内核 bug，导致无法 abort 掉下载。该
     *     脚本完全无用。
     *  4. 在 Opera 下，和 Firefox 一致，完美。
     *  5. 2010-07-12: 发现在 Firefox 下，也有导致部分 Aborted 链接。
     *
     * 模式为 manual 时：（要延迟加载的图片，src 属性替换为 data-lazyload-src, 并将 src 的值赋为 placeholder ）
     *  1. 在任何浏览器下都可以完美实现。
     *  2. 缺点是不渐进增强，无 JS 时，图片不能展示。
     *
     * 缺点：
     *  1. 对于大部分情况下，需要拖动查看内容的页面（比如搜索结果页），快速滚动时加载有损用
     *     户体验（用户期望所滚即所得），特别是网速不好时。
     *  2. auto 模式不支持 Webkit 内核浏览器；IE 下，有可能导致 HTTP 链接数的增加。
     *
     * 优点：
     *  1. 可以很好的提高页面初始加载速度。
     *  2. 第一屏就跳转，延迟加载图片可以减少流量。
     *
     * 参考资料：
     *  1. http://davidwalsh.name/lazyload MooTools 的图片延迟插件
     *  2. http://vip.qq.com/ 模板输出时，就替换掉图片的 src
     *  3. http://www.appelsiini.net/projects/lazyload jQuery Lazyload
     *  4. http://www.dynamixlabs.com/2008/01/17/a-quick-look-add-a-loading-icon-to-your-larger-_images/
     *  5. http://www.nczonline.net/blog/2009/11/30/empty-image-src-can-destroy-your-site/
     *
     * 特别要注意的测试用例:
     *  1. 初始窗口很小，拉大窗口时，图片加载正常
     *  2. 页面有滚动位置时，刷新页面，图片加载正常
     *  3. 手动模式，第一屏有延迟图片时，加载正常
     *
     * 2009-12-17 补充：
     *  1. textarea 延迟加载约定：页面中需要延迟的 dom 节点，放在
     *       <textarea class='ks-datalazysrc invisible'>dom code</textarea>
     *     里。可以添加 hidden 等 class, 但建议用 invisible, 并设定 height = '实际高度'，这样可以保证
     *     滚动时，diff 更真实有效。
     *     注意：textarea 加载后，会替换掉父容器中的所有内容。
     *  2. 延迟 callback 约定：dataLazyload.addCallback(el, fn) 表示当 el 即将出现时，触发 fn.
     *  3. 所有操作都是最多触发一次，比如 callback. 来回拖动滚动条时，只有 el 第一次出现时会触发 fn 回调。
     *
     * xTODO:
     *   - [取消] 背景图片的延迟加载（对于 css 里的背景图片和 sprite 很难处理）
     *   - [取消] 加载时的 loading 图（对于未设定大小的图片，很难完美处理[参考资料 4]）
     *
     * UPDATE LOG:
     *   - 2012-01-07 yiminghe@gmail.com optimize for performance
     *   - 2012-04-27 yiminghe@gmail.com refactor to extend base, add removeCallback/addElements ...
     *   - 2012-04-27 yiminghe@gmail.com 检查是否在视窗内改做判断区域相交，textarea 可设置高度，宽度
     *   - 2012-04-25 yiminghe@gmail.com refactor, 监控容器内滚动，包括横轴滚动
     *   - 2012-04-12 yiminghe@gmail.com monitor touchmove in ios
     *   - 2011-12-21 yiminghe@gmail.com 增加 removeElements 与 destroy 接口
     *   - 2010-07-31 yubo IMG_SRC_DATA 由 data-lazyload-src 更名为 data-ks-lazyload + 支持 touch 设备
     *   - 2010-07-10 yiminghe@gmail.com 重构，使用正则表达式识别 html 中的脚本，使用 EventTarget 自定义事件机制来处理回调
     *   - 2010-05-10 yubo ie6 下，在 dom ready 后执行，会导致 placeholder 重复加载，为比避免此问题，默认为 none, 去掉占位图
     *   - 2010-04-05 yubo 重构，使得对 YUI 的依赖仅限于 YDOM
     *   - 2009-12-17 yubo 将 imglazyload 升级为 datalazyload, 支持 textarea 方式延迟和特定元素即将出现时的回调函数
     */