用AngularJS来实现一些ui上的东西(1)(angular lazyload)
发布在编写自己的angular-ui2014年12月5日view:8433
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

开头先借用其他同学的一句话:

指令是AngularJS的灵魂,只有真正熟练掌握了Angular 指令,才有希望成为AngularJS高手(via:http://www.html-js.com/article/Using-Angular-to-develop-web-application-to-AngularJS-instruction-to-create-controller-interacts-with-ngMode

自定义指令是实现ui/组件、模块、引入第三方插件等等不可缺少的一部分,今天主要讲述自定义指令实现lazyload的功能,熟悉下AngularJS的基础知识。

首先回顾下最简单的lazyload实现方式。

  1. 页面中将img、iframe等tag设置src为1px空白图片或不设置src,避免资源下载;在元素上设定特殊的attr来保存真实资源地址

  2. 给容器(通常是window)绑定onscroll事件,滚动中循环判断lazyload的元素是否在可视范围内,在可视范围内则设置src为真实资源地址,不在可视范围内则跳过继续

相信借助jQuery,实现这个功能很容易,按照需要,一步一步的完成。但如何使用AngularJS将功能定义为module,能够很方便的使用,就需要考虑下实现方式了。

demo的html很简单

<div ng-repeat="img in images"  style="padding:20px;">
     <img data-lazy-src="{{img.src}}" alt="" width="{{img.width}}" height="{{img.height}}" />
</div> 

-- ng-repeat这个内置指令不需要解释,data-lazy-load是我们定义的自定义指令了,表明它是一个需要lazyload的元素。 (注意指令定义为lazyLoad,AngularJS支持在指令名称前加“data-” ,并驼峰转横线分割)

lazyload代码

angular.module('ui-lazyload', [])
    .directive('lazySrc', ['$window', '$document', function(win, $doc) {
           return {
                restrict: 'A',
                scope: {},
                link: function($scope, $elem, attrs) {                                                                                console.log(attrs.lazySrc);
                }
            }
     }]);

– 相信很容易看懂(废话,什么都还没有呢),但还是简单的说一下:lazyload不需要模板替换,使用绑定指令的元素本身就足够了,就不需要replace、transclude、template等等;’$window’, ’$document’ 这些是注入的变量跟controller里的$scope等一样,为了实现功能而用;restrict值为字符串A,表示当前这个指令只能在attr上使用;scope值为空对象,表示scope私有;link 这个稍微复杂,简单的将,就是指令编译过程的 link/链接 阶段,这个方法进行数据绑定、事件绑定等等(当然还有编译阶段compile,这篇不做介绍,小步慢跑~)

directive第一个参数是 指令名称,第二个参数是function或数组,由于function参数名不抗压缩,所以用数组的方式传入注入内容,比较合适(也可以手动指定注入内容$injector)

这个demo要跑起来(看到东西),还缺主module、controller、测试数据等:http://jsfiddle.net/fde3evw4/ ,能看到一大堆的console,表明准备工作已经完成!

lazyload的实现方式中,最重要的就是绑定scroll和判断是否在可视范围,更新lazyload指令代码如下(代码不复杂、也很短,直接上带注释的版本):

angular.module('ui-lazyload', [])
    .directive('lazySrc', ['$window', '$document', function(win, $doc) {

            var $win = angular.element(win),
                    doc = $doc[0],
                    isLazyLoading = false,
                    //用来维护当前需要lazyload的图片集合
                    elements = (function() {
                        var _uid = 0;
                        var _list = {}
                        return {
                            push: function(_data) {
                                _list[_uid++] = _data;
                                setTimeout(function() {
                                    checkImage(_data);
                                });
                            },
                            del: function(key) {
                                _list[key] && delete _list[key];
                            },
                            size: function() {
                                return Object.keys(_list).length;
                            },
                            get: function() {
                                return _list;
                            }

                        }
                    })(),
                    //判断图片元素是否在可视区域内,如果超出1/3可见,则显示
                    isVisible = function(elem) {
                        var rect = elem[0].getBoundingClientRect();
                        var ret = true;
                        if (rect.height > 0 && rect.width > 0) {
                            var x = rect.top > 0 && (rect.top + rect.height / 3) < Math.max(doc.documentElement.clientHeight, win.innerHeight || 0);
                            var y = rect.left > 0 && (rect.left + rect.width / 3) < Math.max(doc.documentElement.clientWidth, win.innerWidth || 0);
                            ret = x && y;
                        }
                        return  ret;
                    },
                    //每次scroll时,调用checkImage,循环检查图片
                    checkImage = function(evt, i, item) {
                        if (i >= 0 && item) {
                            return isVisible(item.elem) ? item.load(i) : false; //指定检查,返回是否显示
                        } else if (elements.size() == 0) {//全部显示之后,解除绑定
                            $win.off('scroll', checkImage);
                            $win.off('resize', checkImage);
                            isLazyLoading = false;
                        } else {
                            angular.forEach(elements.get(), function(item, key) {//循环检查
                                isVisible(item.elem) && item.load(key);
                            });
                        }
                    },
                    //初始化,绑定scroll
                    //如果已经全部显示了,会off,若有新的指令(ajax、js载入需要lazyload的内容),会重新绑定scroll
                    initLazyLoad = function() {
                        if (isLazyLoading === false) {
                            isLazyLoading = true;
                            $win.on('scroll', checkImage);
                            $win.on('resize', checkImage);
                        }
                    }
            return {
                restrict: 'A',//仅可以使用  attr  
                scope: {},//独立的scope
                link: function($scope, $elem, attrs) {
                    $elem[0].style.cssText && $elem.data('cssText',$elem[0].style.cssText);
                    $elem.css({'min-width':'1px','min-height':'1px'});
                    //传回调参数,不$watch 状态,以免增加过多
                    elements.push({
                        elem: $elem,
                        load: function(key) {
                            $elem.data('cssText') && ($elem[0].style.cssText = $elem.data('cssText'))
                            $elem.removeClass('ng-lazyload')
                            $elem.attr('src', attrs.lazySrc);
                            key >= 0 && elements.del(key);
                            $scope.$destroy();
                            return true;
                        }
                    });
                    initLazyLoad();
                }
            }
        }]);

代码不长且包含注释,应该都可以明白。 有些地方需要注意下,并不仅限于lazyload: - link方法是每个指令到了link阶段都会执行的。 - angular.forEach可以替代循环。(同样还有其他方法,学习每个框架,首先要看的就是它所有的api。 console.log(Object.keys(angular))、console.log(Object.keys(angular.element.prototype))) - getBoundingClientRect获取元素基于浏览器的位置可以很容易的判断出位置关系 - 隐藏元素getBoundingClientRect获取到的值都为0,需注意下。 - 不引入jQuery,内置的jqlite已经基本够用了。

完整的demo,注意仅学习实践使用,未充分测试 : http://jsfiddle.net/eyaytkr7/embedded/result/

其他方式: 在指令的scope中设置变量,使用$watch的方式来侦测什么时候给与展示。

--- 2014.12.05 第二篇继续lazyload,以更好的代码组织方式,并实现更多的功能(比如带滚动条的容器上绑定lazyload、可选为非绑定IMG而是创建IMAGE) 参考@Treri : https://github.com/Treri/me-lazyload/blob/master/me-lazyload.js

评论
发表评论
3年前
赞了此文章!
4年前
添加了一枚【评注】:尼玛,怎么先进
4年前
赞了此文章!
4年前

compile 效率怎么样

4年前
添加了一枚【评注】:不错啊
4年前

@Treri 了解,我考虑下实际的情况,$watch是否需要添加,在第二篇里完善下,tks

4年前

@henryli_hui 加上$watch是为了防止可能会修改图片的地址, 如果按照你的这种情况, 修改了lazy-src的地址后, 已经加载的图片也不会更新了

4年前

@Treri 有参考哦,没加上链接了 抱歉。不一样的是去掉了scope中的$watch、指令编译结束就立即开始检查是否显示、scroll/resize移除和需要时候再次绑定的处理

WRITTEN BY
PUBLISHED IN
编写自己的angular-ui

在编写ui的过程中,学习angular!

我的收藏