AngularJS实现一个简单的Carousel

利用AngularJS实现了一个简单版的Bootstrap Carousel,即单击Next或Prev按钮时能翻看后一张或前一张图片。 在这里总结一下,有些地方我自己也存在疑惑,望各位不吝赐教。

Bootstrap Carousel实现步骤

  1、这个方法很方便,不需要再额外编写js代码按照此方法即可实现此效果,看相关代码:

AngularJS实现一个简单的Carousel_第1张图片
  

点滴积累:

通过 data 属性:使用 data 属性可以很容易控制轮播(Carousel)的位置。

属性 data-slide 接受关键字 prev 或 next,用来改变幻灯片相对于当前位置的位置。

使用 data-slide-to 来向轮播床底一个原始滑动索引,data-slide-to="2" 将把滑块移动到一个特定的索引,索引从 0 开始计数。

2、先看一下相关的HTML片段,div.item的id是为了方便说明添加的,与实现无关。

 class="carousel">
     class="carousel-inner">
         class="item" id="item1">
             src="img1" alt=""/>
        
class="item active" id="item2"> src="img2" alt=""/>
class="item" id="item3"> src="img3" alt=""/>

在上面的示例代码中,我们要轮流播放img1、img2和img3三张图片。当前显示的是img2(因为item2属于active类),下一张要显示的图片假设是img3。 具体步骤如下(利用jQuery代码说明):

  1. $('#item3').addClass('next');(将#item3放在#item2的右侧,但是被浏览器隐藏起来)
  2. var reflow = $('#item3')[0].offsetWidth;( 这一步非得做吗?我自己实验了一下,如果不重绘UI,好像效果也一样 )
  3. $('#item2 #item3').addClass('left');(这会导致#item2和#item3同时向左平移, bootstrap的css文件中注册了item类元素针对translate属性的transition)
  4. 当平移结束时,$('#item2').removeClass('active left'); $('#item3').removeClass('next left').addClass('active');

AngularJS实现

先看index.html中的代码片段


 ng-app="app">
     class="row">
         class="col-md-2">
             ng-controller="CarouselController">
                 current-slide="currentSlide" forward="forward">
                     ng-repeat="img in images">
                         ng-src="{{img}}" alt=""/>
                    
                
                 ng-click="prev()" class="btn btn-primary">Prev
                 ng-click="next()" class="btn btn-primary">Next
            
src="angular.js"> src="ui-bootstrap.js"> src="carousel.js"> src="app.js">

下面一个个地解释涉及到的directive:

CarouselController

angular.module('app', ['carousel']).
    controller('CarouselController', function($scope, $element){
        $scope.images = [
            './imgs/img1-large.jpg',
            './imgs/img2-large.jpg',
            './imgs/img3-large.jpg',
            './imgs/img4-large.jpg',
            './imgs/img5-large.jpg'
        ];
        $scope.currentSlide = 1;

        $scope.prev = function(){
            $scope.forward = false;
            $scope.currentSlide = ($scope.currentSlide + $scope.images.length - 1) % $scope.images.length;
        };
        $scope.next = function(){
            $scope.forward = true;
            $scope.currentSlide = ($scope.currentSlide + 1) % $scope.images.length;
        };
    });

各变量说明如下:

  1. images是待显示的图片集合
  2. currentSlide表示当前显示的图片在images中的索引
  3. forward表示图片显示顺序。为true表示按照images中的索引从小到大显示;为false表示按照索引从大到小显示
  4. prev()和next()是单击Prev和Next按钮时执行的代码,它们只是更新了currentSlide和forward

carousel directive

angular.module('carousel', ['ui.bootstrap.transition']).
    directive('carousel', function($transition, $timeout){
        return {
            restrict: 'E',
            replace: true,
            transclude: true,
            scope: {
                forward: '=',
                currentSlide: '='
            },
            controller: function($scope, $element) {
                $scope.slides = [];
                this.addSlide = function (slide) {
                    if($scope.currentSlide === $scope.slides.length){
                        slide.active = true;
                    }
                    $scope.slides.push(slide);
                };
                this.removeSlide = function(slide){
                    var idx = $scope.slides.indexOf(slide);
                    $scope.slides.splice(idx, 1);
                };

                var oldSlide = $scope.currentSlide;
                var transition;
                function go(from, to, forward){
                    if(from === to) return;

                    var lor = forward ? 'left' : 'right';
                    var pon = forward ? 'next' : 'prev';
                    var fromSlide = $scope.slides[from];
                    var toSlide = $scope.slides[to];

                    toSlide[pon] = true;
                    //var reflow = toSlide.$element.offsetWidth;

                    $timeout(function(){
                        fromSlide[lor] = toSlide[lor] = true;
                    });

                    transition = $transition(toSlide.$element, {}).
                        then(function(){
                            angular.extend(fromSlide, {active: false, left: false, right: false});
                            angular.extend(toSlide, {active: true, left: false, right: false, prev: false, next: false});

                            oldSlide = to;
                            transition = null;
                        });
                }

                $scope.$watch('currentSlide', function(newSlide){
                    if(newSlide === oldSlide) return;

                    var forward = $scope.forward;
                    if(!transition){
                        go(oldSlide, newSlide, forward);
                    }
                });
            },
            templateUrl: 'kits/carousel/carousel.tmpl.html'
        };
    });

其$scope中有两个属性forward和currentSlide,它们是与CarouselController中的forward以及currentSlide绑定在一起的,而且通过$scope.$watch('currentSlide', ...)来监听currentSlide的变化。于是,当用户通过单击Prev或Next按钮来改变CarouselController中的currentSlide和forward时,carousel directive能及时地发现,并通过go(oldSlide, newSlide, forward)来执行图片切换。

carousel directive的HTML模版

 class="carousel slide">
     class="carousel-inner" ng-transclude>
    

这没什么好说,只不过其中有个data-ng-transclude来指定子元素的位置

slide directive

angular.module('carousel', ['ui.bootstrap.transition']).
    directive('slide', function(){
            return {
                scope: {},
                restrict: 'E',
                require: '^carousel',
                replace: true,
                transclude: true,
                templateUrl: 'kits/carousel/slide.tmpl.html',
                link: function(scope, element, attrs, carousel){
                    scope.$element = element;
                    carousel.addSlide(scope);

                    scope.$on('destroy', function(){ carousel.removeSlide(scope); })
                }
            }
        });

通过require: '^carousel',link函数中能获取到carousel的controller。通过carousel controller 的addSlide(scope)方法,每个slide都会将自身的scope添加到carousel scope的slides数组中。为什么要这样做呢?carousel中定义的go函数在实现transition时需要改变当前slide element和下一个slide element的class属性(参见前面的“Bootstrap Carousel实现步骤”小节),而这些class属性是和slide scope中相应的变量绑定在一起的。比如,当slide scope中active为true时,相应的slide element就会拥有active类。这是通过ng-class实现的,具体参见下面的slide directive模版。

 class="item" data-ng-class="{'active': active, 'prev': prev, 'next': next, 'left': left, 'right': right}" ng-transclude>

carousel directive中go(oldSlide, newSlide, forward)的实现

为方便查看,代码再次贴到此处

function go(from, to, forward){
        if(from === to) return;

        var lor = forward ? 'left' : 'right';
        var pon = forward ? 'next' : 'prev';
        var fromSlide = $scope.slides[from];
        var toSlide = $scope.slides[to];

        toSlide[pon] = true;
        var reflow = toSlide.$element[0].offsetWidth;

        $timeout(function(){
            fromSlide[lor] = toSlide[lor] = true;
        });

        transition = $transition(toSlide.$element, {}).
            then(function(){
                angular.extend(fromSlide, {active: false, left: false, right: false});
                angular.extend(toSlide, {active: true, left: false, right: false, prev: false, next: false});

                oldSlide = to;
                transition = null;
            });
    }

假设forward === true,我将这里执行的步骤与'Bootstrap Carousel实现步骤'进行对比

  1. toSlide[pon] = true; ===> $('#item3').addClass('next');
  2. var reflow = toSlide.$element[0].offsetWidth; ===> var reflow = $('#item3')[0].offsetWidth;
  3. $timeout(function(){ fromSlide[lor] = toSlide[lor] = true; }); ===> $('#item2 #item3').addClass('left');
  4. 平移完成后
    angular.extend(fromSlide, {active: false, left: false, right: false});
    angular.extend(toSlide, {active: true, left: false, right: false, prev: false, next: false});
    ===>
    当平移结束时
    $('#item2').removeClass('active left'); $('#item3').removeClass('next left').addClass('active');

可以看出没有本质区别。上面步骤3中,我用到了$timeout,这是我通过多次实验想到的。因为如果直接在$timeout外面写fromSlide[lor] = toSlide[lor] = true;下一张图片会立即显示出来,而不是缓慢地从右侧平移过来, 关于这一点我暂时还没有想明白为什么,希望清楚的小伙伴告诉我一下,谢谢啦^-^!


你可能感兴趣的:(AngularJS实现一个简单的Carousel)