├── README.md └── src ├── app.js ├── carousel.js └── index.html /README.md: -------------------------------------------------------------------------------- 1 | # A Lazy Loading, DOM-light AngularJS Carousel 2 | 3 | ## Where can I see a demo? 4 | 5 | Without further adieu, go check out our GitHub page for the [AngularJS-Carousel-Demo]. Then come back, and we'll 6 | talk about why it is so awesome. 7 | 8 | ## Why? 9 | 10 | Because everyone loves a carousel! And Twitter Bootstrap's carousel leaves a lot to be desired. 11 | 12 | But on a serious note, here's why we ended up creating Yet Another Carousel: 13 | 14 | * *Light on DOM* - We ran into a use case where we needed to display 1000's of items in a carousel. And make it 15 | load fast and be responsive. And whatever was out there wasn't cutting it. 16 | * *Lazy Loading* - As mentioned above, loading 1000 items upfront? Not a good idea. So we needed something that was 17 | smart about how it loaded items, and when it did so. 18 | * *Simple to use* - We needed to re use it multiple times, and rewriting it from scratch every time, or needing to 19 | tweak it slightly every time was not fun. So we made it as reusable as possible. 20 | * *Browser Compatibility* - While normal people use Google Chrome or Firefox, or even IE10 sometimes, we needed to 21 | support IE9 and **gasp** IE8. So CSS transitions were out! 22 | 23 | We ended up creating this directly, which was heavily inspired (and liberally borrows) from 24 | http://cubiq.org/swipeview[Cubiq's SwipeView]. Of course, we modified the guts of the beast, and replaced touch related 25 | events with click, and what not, but the core philosophy and the trick of using 3 divs for the carousel are all from him. 26 | 27 | ## Requirements 28 | 29 | Not too steep. All you need to get this working are 30 | 31 | 1. AngularJS (Duh!) 32 | 2. JQuery (To support transitions in IE9, and IE8!) 33 | 3. The directive code 34 | 35 | No extra CSS, all done with jQuery animations! 36 | 37 | ## Browser Support 38 | 39 | Ah, and here we get to the reason behind the jQuery necessity. The Carousel works on 40 | 41 | * Firefox 42 | * Chrome 43 | * Safari 44 | * IE8+ (probably 7 as well, but untested) 45 | 46 | ## Using it 47 | 48 | There are two parts to using the Carousel Directive, the HTML and the JS Controller code. 49 | 50 | Let us take a look at the HTML first: 51 | 52 | ``` 53 |
57 |
58 | Content goes here 59 | {{scope.awesomeContent}} 60 |
61 |
62 | Previous 63 | Next 64 | ``` 65 | 66 | There are five major attributes of note in the HTML 67 | 68 | 1. *carousel* : This marks the DOM element as a Carousel component. Without it, the rest are useless 69 | 2. *on-page-upcoming*: This is the function on the controller that is called whenever the carousel directive 70 | needs to load a certain page. It is called with the page number (Zero based, so page 1 is 0) 71 | 3. *give-carousel-to*: Now this function is called when the carousel is bootstrapped, and gives access to 72 | the controller to the directive. This is used for writing helpful functions to decide to show and hide 73 | the next / previous page links, and reset the carousel. 74 | 4. *carousel-class*: If you want to specify a class to be added to each page of the carousel. 75 | 5. *myCarouselPage*: This is the div that gets replicated for each page of the carousel. So, for every page 76 | that is there in the carousel, one copy of this div will get created. 77 | 78 | Now, let us take a look at the controller code to get this to work: 79 | 80 | ``` 81 | // The following code is in the controller 82 | 83 | var carousel; 84 | $scope.loadPage = function(page, tmplCb) { 85 | var carouselPageScope = $scope.$new(); 86 | // Assign / load all the items you want to carouselPageScope. 87 | // Do work 88 | // Ensure that the carousel knows how many total pages there are 89 | carousel.updatePageCount(totalPages); 90 | tmplCb(carouselPageScope); 91 | }; 92 | $scope.onCarouselAvailable = function(car) { 93 | carousel = car; 94 | }; 95 | $scope.hasNext = function() { 96 | return carousel ? carousel.hasNext() : false; 97 | }; 98 | $scope.hasPrevious = function() { 99 | return carousel ? carousel.hasPrevious() : false; 100 | }; 101 | $scope.next = function() { 102 | if (carousel) carousel.next(); 103 | }; 104 | $scope.prev = function() { 105 | if (carousel) carousel.prev(); 106 | }; 107 | ``` 108 | 109 | We have quite a bit happening here, but there is really two major functions, and the rest are pretty boiler-plate: 110 | 111 | 1. *loadPage*: The load page function, as mentioned above takes two arguments. And it has two responsibilities. It needs 112 | to (ideally) create a new scope, and then fetch the contents for that particular page, and set it on this scope. The 113 | data on the scope can be set / reassigned at any point, as long as the reference to the scope is maintained. So async 114 | calls are not a problem! 115 | 2. *onCarouselAvailable*: THis gives the controller a handle on the carousel. Nothing more, nothing less. 116 | 3. *The pagination functions*: These are used to hide / show the next and previous links, based on the state of the carousel. 117 | 118 | ## Who are we? 119 | 120 | We are Fundoo Solutions, an awesome company based out of India who just love AngularJS. Check out our [website] or our [LinkedIn] page. 121 | 122 | ## License 123 | 124 | The code above is open sourced, and licensed under the MIT License, which is the simplest and easiest to understand, and most open. 125 | Basically, feel free to use this code or modify it as per your needs. The actual license is below: 126 | 127 | ### The MIT License 128 | 129 | > Copyright (C) 2011-2013 Vojta Jína. 130 | > 131 | > Permission is hereby granted, free of charge, to any person 132 | > obtaining a copy of this software and associated documentation files 133 | > (the "Software"), to deal in the Software without restriction, 134 | > including without limitation the rights to use, copy, modify, merge, 135 | > publish, distribute, sublicense, and/or sell copies of the Software, 136 | > and to permit persons to whom the Software is furnished to do so, 137 | > subject to the following conditions: 138 | > 139 | > The above copyright notice and this permission notice shall be 140 | > included in all copies or substantial portions of the Software. 141 | > 142 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 143 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 144 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 145 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 146 | > BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 147 | > ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 148 | > CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 149 | > SOFTWARE. 150 | 151 | 152 | 153 | [AngularJS-Carousel-Demo]: http://fundoo-solutions.github.io/angularjs-carousel/ 154 | [website]: http://www.befundoo.com 155 | [LinkedIn]: http://www.linkedin.com/company/fundoo-solutions 156 | 157 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | angular.module('FundooCarouselApp', ['fundoo.directives']) 2 | .controller('MainCtrl', ['$scope', 'FlickrApi', function($scope, flickr) { 3 | var carousel; 4 | 5 | $scope.hasPrevious = function() { 6 | return carousel ? carousel.hasPrevious() : false; 7 | }; 8 | $scope.previous = function() { 9 | if (carousel) { carousel.prev(); } 10 | }; 11 | $scope.hasNext = function() { 12 | return carousel ? carousel.hasNext() : false; 13 | }; 14 | $scope.next = function() { 15 | if (carousel) { carousel.next(); } 16 | }; 17 | 18 | var loadPhotos = function(carouselScope, page) { 19 | carousel.updatePageCount(6); 20 | carouselScope.photos = flickr.getPhotos(page); 21 | carouselScope.getPhotoUrl = function(photo) { 22 | return flickr.getPhotoUrl(photo); 23 | }; 24 | }; 25 | $scope.loadPage = function(page, tmplCb) { 26 | var newScope = $scope.$new(); 27 | loadPhotos(newScope, page); 28 | tmplCb(newScope); 29 | }; 30 | $scope.onCarouselAvailable = function(car) { 31 | carousel = car; 32 | }; 33 | }]).factory('FlickrApi', function() { 34 | var pages = [ 35 | [ 36 | { "id": "8610594328", "secret": "e0a01f093f", "server": "8398", "farm": 9, "title": "Back from Work [Explored]", "isprimary": 0 }, 37 | { "id": "8609770312", "secret": "ce2cc025ee", "server": "8392", "farm": 9, "title": "Chhau Masks of Charida [Explored]", "isprimary": 0 }, 38 | { "id": "8605097271", "secret": "84b6ec7e0e", "server": "8120", "farm": 9, "title": "স্তব্ধতার গান......The Sound of Silence [Explored]", "isprimary": 0 }, 39 | { "id": "8602385755", "secret": "b26323b04c", "server": "8532", "farm": 9, "title": "Sandakphu @ Sunset [Explored]", "isprimary": 0 }, 40 | { "id": "8600778114", "secret": "6636515c02", "server": "8227", "farm": 9, "title": "Spring Colours [Explored]", "isprimary": 0 } 41 | ], [ 42 | { "id": "8593917187", "secret": "a274587f5c", "server": "8241", "farm": 9, "title": "Celebration [Explored]", "isprimary": 0 }, 43 | { "id": "8592654144", "secret": "3654566f78", "server": "8105", "farm": 9, "title": "Spring Garden [Explored]", "isprimary": 1 }, 44 | { "id": "8590057990", "secret": "9948a167ea", "server": "8095", "farm": 9, "title": "Spring Solar System [Explored]", "isprimary": 0 }, 45 | { "id": "8585649920", "secret": "43dc41c8bb", "server": "8518", "farm": 9, "title": "Cherry Blossom [Explored]", "isprimary": 0 }, 46 | { "id": "8583184576", "secret": "52eb4819d8", "server": "8372", "farm": 9, "title": "Flambeau [Explored]", "isprimary": 0 } 47 | ], [ 48 | { "id": "8580890561", "secret": "45c23c4225", "server": "8231", "farm": 9, "title": "Spring Garden [Explored]", "isprimary": 0 }, 49 | { "id": "8428963019", "secret": "9525636b0f", "server": "8186", "farm": 9, "title": "Sandakphu [Explored]", "isprimary": 0 }, 50 | { "id": "8426344513", "secret": "e2e6bb897f", "server": "8514", "farm": 9, "title": "Life process continues to flow... [Explored]", "isprimary": 0 }, 51 | { "id": "8423110349", "secret": "ce1da3ab8f", "server": "8472", "farm": 9, "title": "Varanasi [Explored]", "isprimary": 0 }, 52 | { "id": "8418889023", "secret": "a4e0cf98e2", "server": "8326", "farm": 9, "title": "To Get A Perfect Sunset.... [Explored]", "isprimary": 0 } 53 | ], [ 54 | { "id": "8417868981", "secret": "7a4811fb3d", "server": "8212", "farm": 9, "title": "Murguma Lake at Sunset [Explored]", "isprimary": 0 }, 55 | { "id": "8413140020", "secret": "c82a9a2bc5", "server": "8356", "farm": 9, "title": "The Peasant and The Photographer [Explored]", "isprimary": 0 }, 56 | { "id": "8404264476", "secret": "9395407e0a", "server": "8466", "farm": 9, "title": "Every Day A New Beginning ! [Explored]", "isprimary": 0 }, 57 | { "id": "8401996292", "secret": "dfc05d1414", "server": "8515", "farm": 9, "title": "Red In White [Explored]", "isprimary": 0 }, 58 | { "id": "8398341696", "secret": "1e90a37771", "server": "8473", "farm": 9, "title": "Bengal in Winter [Explored]", "isprimary": 0 } 59 | ], [ 60 | { "id": "8395243202", "secret": "faae05edf7", "server": "8227", "farm": 9, "title": "SEA.....SAND......SUNSET.....SHADOW [Explored]", "isprimary": 0 }, 61 | { "id": "8233392271", "secret": "bcd284de10", "server": "8342", "farm": 9, "title": "Back from work.....[Explored]", "isprimary": 0 }, 62 | { "id": "8230954133", "secret": "b34c08caab", "server": "8060", "farm": 9, "title": "নীলকন্ঠ পাখির খোঁজে [Explored]", "isprimary": 0 }, 63 | { "id": "8210730110", "secret": "1145031dec", "server": "8057", "farm": 9, "title": "Catch The Dream [Explored]", "isprimary": 0 }, 64 | { "id": "8205723341", "secret": "70fd23564a", "server": "8207", "farm": 9, "title": "The Gate [Explored]", "isprimary": 0 } 65 | ], [ 66 | { "id": "8204067606", "secret": "afcaae887d", "server": "8347", "farm": 9, "title": "Life in the Mist [Explored]", "isprimary": 0 }, 67 | { "id": "8201251807", "secret": "599bcd9439", "server": "8485", "farm": 9, "title": "Chhath......Festival Portrait [Explored]", "isprimary": 0 }, 68 | { "id": "8193202609", "secret": "e187f099c4", "server": "8203", "farm": 9, "title": "The Temple Bells [Explored]", "isprimary": 0 }, 69 | { "id": "8031568446", "secret": "0d1c1b0b83", "server": "8310", "farm": 9, "title": "তামট । Plain Tiger [Explored]", "isprimary": 0 }, 70 | { "id": "8035496828", "secret": "0b950f69f6", "server": "8036", "farm": 9, "title": "Explore Front Page", "isprimary": 0 } 71 | ] 72 | ]; 73 | return { 74 | getPhotos: function(page) { 75 | // Ideally, go off and fetch the next page of data fromt he server, but we'll do it locally in the sample 76 | return pages[page]; 77 | }, 78 | getPhotoUrl: function(photo) { 79 | return 'http://farm' + photo.farm + '.staticflickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '_s.jpg'; 80 | } 81 | }; 82 | }); 83 | -------------------------------------------------------------------------------- /src/carousel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Heavily inspired by and liberally borrowed from Cubiq's SwipeView (http://cubiq.org/swipeview). 3 | * This takes SwipeView as the base, replaces CSS3 animations for jQuery animations 4 | * to enable IE8 and IE9 support, adds ability to have more divs instead 5 | * of a fixed 3, and uses jQuery data instead of HTML5 data for IE8 support. 6 | * 7 | * Lots of hooks for AngularJS directive support as well. 8 | */ 9 | 10 | var Carousel = function (el, options) { 11 | var i, 12 | div, 13 | className, 14 | pageIndex; 15 | 16 | this.wrapper = typeof el === 'string' ? document.querySelector(el) : el; 17 | this.options = { 18 | text: null, 19 | numberOfPages: 3, 20 | numberOfDivsOnEachSide: 1, 21 | snapThreshold: null, 22 | hastyPageFlip: false, 23 | loop: false 24 | }; 25 | 26 | // User defined options 27 | for (i in options) { 28 | if(options.hasOwnProperty(i)){ 29 | this.options[i] = options[i]; 30 | } 31 | } 32 | 33 | this.wrapper.style.overflow = 'hidden'; 34 | this.wrapper.style.position = 'relative'; 35 | 36 | this.masterPages = []; 37 | this.numDivs = (2 * this.options.numberOfDivsOnEachSide) + 1; 38 | 39 | div = document.createElement('div'); 40 | div.id = 'carousel-slider'; 41 | div.style.cssText = 'position:relative;top:0;height:100%;width:100%;'; 42 | this.wrapper.appendChild(div); 43 | this.slider = div; 44 | 45 | this.refreshSize(); 46 | 47 | var nonLoopPageCounter = 1; 48 | 49 | for (i=-this.options.numberOfDivsOnEachSide; i < (this.options.numberOfDivsOnEachSide + 1); i++) { 50 | div = document.createElement('div'); 51 | div.id = 'carousel-masterpage-' + (i+ this.options.numberOfDivsOnEachSide); 52 | div.style.cssText = 'position:absolute;top:0;height:100%;width:100%;left:' + i*100 + '%'; 53 | //if (!div.dataset) {div.dataset = {};} 54 | if (this.options.loop === true) { 55 | pageIndex = i < 0 ? this.options.numberOfPages + i : i; 56 | } else { 57 | pageIndex = i < 0 ? this.options.numberOfDivsOnEachSide + nonLoopPageCounter++ : i; 58 | } 59 | $(div).data('pageIndex', pageIndex); 60 | $(div).data('upcomingPageIndex', pageIndex); 61 | 62 | if (!this.options.loop && i === -1) {div.style.visibility = 'hidden';} 63 | 64 | this.slider.appendChild(div); 65 | this.masterPages.push(div); 66 | } 67 | 68 | className = this.masterPages[this.options.numberOfDivsOnEachSide].className; 69 | this.masterPages[this.options.numberOfDivsOnEachSide].className = !className ? 'carousel-active' : className + ' carousel-active'; 70 | this.currentMasterPage = this.options.numberOfDivsOnEachSide; 71 | }; 72 | 73 | Carousel.prototype = { 74 | currentMasterPage: 1, 75 | x: 0, 76 | page: 0, 77 | pageIndex: 0, 78 | customEvents: [], 79 | 80 | onFlip: function (fn) { 81 | $(this.wrapper).on('carouselflip', fn); 82 | this.customEvents.push(['flip', fn]); 83 | }, 84 | 85 | onReset: function (fn) { 86 | $(this.wrapper).on('carouselreset', fn); 87 | this.customEvents.push(['reset', fn]); 88 | }, 89 | 90 | destroy: function () { 91 | while ( this.customEvents.length ) { 92 | $(this.wrapper).off('carousel' + this.customEvents[0][0]); 93 | this.customEvents.shift(); 94 | } 95 | }, 96 | 97 | refreshSize: function () { 98 | this.wrapperWidth = this.wrapper.clientWidth; 99 | this.pageWidth = this.wrapperWidth; 100 | this.maxX = -this.options.numberOfPages * this.pageWidth + this.wrapperWidth; 101 | this.snapThreshold = this.options.snapThreshold === null ? 102 | Math.round(this.pageWidth * 0.15) : 103 | /%/.test(this.options.snapThreshold) ? 104 | Math.round(this.pageWidth * this.options.snapThreshold.replace('%', '') / 100) : 105 | this.options.snapThreshold; 106 | }, 107 | 108 | updatePageCount: function (n) { 109 | this.options.numberOfPages = n; 110 | this.maxX = -this.options.numberOfPages * this.pageWidth + this.wrapperWidth; 111 | }, 112 | 113 | goToPage: function (p, reset) { 114 | var i, index; 115 | 116 | if (reset) { 117 | this.updatePageCount(1); 118 | } 119 | 120 | this.masterPages[this.currentMasterPage].className = this.masterPages[this.currentMasterPage].className.replace(/(^|\s)carousel-active(\s|$)/, ''); 121 | for (i=0; i this.options.numberOfPages-1 ? this.options.numberOfPages-1 : p; 130 | this.page = p; 131 | this.pageIndex = p; 132 | this.__pos(-p * this.pageWidth); 133 | 134 | this.currentMasterPage = (this.page + 1) - Math.floor((this.page + 1) / this.numDivs) * this.numDivs; 135 | 136 | this.masterPages[this.currentMasterPage].className = this.masterPages[this.currentMasterPage].className + ' carousel-active'; 137 | 138 | var divsOnEachSide = Math.floor(this.numDivs / 2); 139 | 140 | var sequenceOfIndicesFromMaster = []; 141 | for (i = 0; i < this.numDivs; i++) { 142 | index = this.currentMasterPage + i; 143 | if (index >= this.numDivs) { 144 | index -= this.numDivs; 145 | } 146 | if (i <= divsOnEachSide) { 147 | sequenceOfIndicesFromMaster.push(index); 148 | } else { 149 | sequenceOfIndicesFromMaster.unshift(index); 150 | } 151 | } 152 | 153 | for (i = 0; i < sequenceOfIndicesFromMaster.length; i++) { 154 | index = sequenceOfIndicesFromMaster[i]; 155 | if (i < divsOnEachSide) { 156 | var minusPages = (divsOnEachSide - i); 157 | this.masterPages[index].style.left = this.page * 100 - (minusPages * 100) + '%'; 158 | $(this.masterPages[index]).data('upcomingPageIndex', this.page - minusPages < 0 ? this.options.numberOfPages - minusPages : this.page - minusPages); 159 | if ($(this.masterPages[index]).data('upcomingPageIndex') !== this.page - minusPages) { 160 | this.masterPages[index].style.visibility = 'hidden'; 161 | } 162 | } else { 163 | var plusPages = (i - divsOnEachSide); 164 | this.masterPages[index].style.left = this.page * 100 + (plusPages * 100) + '%'; 165 | $(this.masterPages[index]).data('upcomingPageIndex', this.page >= this.options.numberOfPages - plusPages ? plusPages : this.page + plusPages); 166 | } 167 | } 168 | 169 | if (reset) { 170 | this.__reset(); 171 | } 172 | }, 173 | 174 | hasNext: function() { 175 | return this.page < this.options.numberOfPages - 1; 176 | }, 177 | hasPrevious: function() { 178 | return this.page > 0; 179 | }, 180 | next: function () { 181 | if (!this.options.loop && this.x === this.maxX) {return;} 182 | 183 | this.directionX = -1; 184 | this.x -= 1; 185 | this.__checkPosition(); 186 | }, 187 | 188 | prev: function () { 189 | if (!this.options.loop && this.x === 0){ return;} 190 | 191 | this.directionX = 1; 192 | this.x += 1; 193 | this.__checkPosition(); 194 | }, 195 | 196 | /** 197 | * 198 | * Pseudo private methods 199 | * 200 | */ 201 | __pos: function (x) { 202 | this.x = x; 203 | var self = this; 204 | $(this.slider).animate({left: x}, 500, 'swing', function() { 205 | self.__flip(); 206 | }); 207 | }, 208 | 209 | __checkPosition: function () { 210 | this.refreshSize(); 211 | var pageFlip, 212 | pageFlipIndex, 213 | className; 214 | 215 | this.masterPages[this.currentMasterPage].className = this.masterPages[this.currentMasterPage].className.replace(/(^|\s)carousel-active(\s|$)/, ''); 216 | // Flip the page 217 | if (this.directionX > 0) { 218 | this.page = -Math.ceil(this.x / this.pageWidth); 219 | this.currentMasterPage = (this.options.numberOfDivsOnEachSide + ((this.page) % this.numDivs)) % this.numDivs; 220 | 221 | this.pageIndex = this.pageIndex === 0 ? this.options.numberOfPages - 1 : this.pageIndex - 1; 222 | 223 | pageFlip = this.currentMasterPage - 1; 224 | pageFlip = pageFlip < 0 ? this.numDivs - 1 : pageFlip; 225 | this.masterPages[pageFlip].style.left = this.page * 100 - 100 + '%'; 226 | 227 | pageFlipIndex = this.page - 1; 228 | } else { 229 | this.page = -Math.floor(this.x / this.pageWidth); 230 | this.currentMasterPage = (this.options.numberOfDivsOnEachSide + ((this.page) % this.numDivs)) % this.numDivs; 231 | 232 | this.pageIndex = this.pageIndex === this.options.numberOfPages - 1 ? 0 : this.pageIndex + 1; 233 | 234 | pageFlip = this.currentMasterPage + 1; 235 | pageFlip = pageFlip > (this.numDivs - 1) ? 0 : pageFlip; 236 | this.masterPages[pageFlip].style.display = ''; 237 | this.masterPages[pageFlip].style.left = this.page * 100 + 100 + '%'; 238 | 239 | pageFlipIndex = this.page + 1; 240 | } 241 | 242 | // Add active class to current page 243 | className = this.masterPages[this.currentMasterPage].className; 244 | if(!/(^|\s)carousel-active(\s|$)/.test(className)) { 245 | this.masterPages[this.currentMasterPage].className = !className ? 'carousel-active' : className + ' carousel-active'; 246 | } 247 | 248 | this.masterPages[this.currentMasterPage].style.visibility = ''; 249 | 250 | // Add loading class to flipped page 251 | className = this.masterPages[pageFlip].className; 252 | if(!/(^|\s)carousel-loading(\s|$)/.test(className)) { 253 | this.masterPages[pageFlip].className = !className ? 'carousel-loading' : className + ' carousel-loading'; 254 | } 255 | 256 | pageFlipIndex = pageFlipIndex - Math.floor(pageFlipIndex / this.options.numberOfPages) * this.options.numberOfPages; 257 | $(this.masterPages[pageFlip]).data('upcomingPageIndex', pageFlipIndex); // Index to be loaded in the newly flipped page 258 | 259 | var newX = -this.page * this.pageWidth; 260 | 261 | // Hide the next page if we decided to disable looping 262 | if (!this.options.loop) { 263 | this.masterPages[pageFlip].style.visibility = newX === 0 || newX === this.maxX ? 'hidden' : ''; 264 | } 265 | 266 | this.__pos(newX); 267 | if (this.options.hastyPageFlip) { 268 | this.__flip(); 269 | } 270 | 271 | }, 272 | 273 | __flip: function () { 274 | this.__event('flip'); 275 | 276 | for (var i=0; i'); 310 | 311 | return function(scope, elem, attrs) { 312 | var carouselContainer = elem.find('>:first-child'); 313 | carouselContainer.addClass(attrs.carouselClass); 314 | var carousel = new Carousel(carouselContainer[0], {numberOfPages: 1, loop: false}); 315 | var tmplCbFunction = function(index) { 316 | return function(scope) { 317 | carouselHtmlTemplate(scope, function(clonedElem) { 318 | if (carousel.masterPages[index].firstChild) { 319 | carousel.masterPages[index].replaceChild(clonedElem[0], carousel.masterPages[index].firstChild); 320 | } else { 321 | carousel.masterPages[index].appendChild(clonedElem[0]); 322 | } 323 | }); 324 | }; 325 | }; 326 | var initialCarouselLoad = function() { 327 | 328 | var nonLoopPageCounter = 1; 329 | for (var i = -carousel.options.numberOfDivsOnEachSide; i <= carousel.options.numberOfDivsOnEachSide; i++) { 330 | var pageIndex; 331 | if (carousel.options.loop === true) { 332 | pageIndex = i < 0 ? carousel.options.numberOfPages + i : i; 333 | } else { 334 | pageIndex = i < 0 ? carousel.options.numberOfDivsOnEachSide + nonLoopPageCounter++ : i; 335 | } 336 | scope.onPageUpcoming({page: pageIndex, tmplCb: tmplCbFunction(i + carousel.options.numberOfDivsOnEachSide)}); 337 | } 338 | }; 339 | scope.giveCarouselTo({carousel: carousel}); 340 | 341 | initialCarouselLoad(); 342 | 343 | carousel.onReset(function() { 344 | initialCarouselLoad(); 345 | }); 346 | 347 | carousel.onFlip(function () { 348 | var upcoming, i; 349 | 350 | for (i=0; i < carousel.numDivs; i++) { 351 | upcoming = $(carousel.masterPages[i]).data('upcomingPageIndex'); 352 | if (upcoming !== $(carousel.masterPages[i]).data('pageIndex')) { 353 | scope.onPageUpcoming({page: upcoming, tmplCb: tmplCbFunction(i)}); 354 | } 355 | } 356 | }); 357 | 358 | // Handle resizing of the page 359 | function resizeCarousel() { 360 | carousel.refreshSize(); 361 | carousel.goToPage(carousel.page); 362 | } 363 | 364 | // The browser resize event is sent multiple times while a user is 365 | // dragging the browser to resize. So we do this stuff to ensure that 366 | // we only do a resize once the user has finished his resizing activities. 367 | var rtime = new Date(1, 1, 2000, 12, 0, 0); 368 | var timeout = false; 369 | var delta = 200; 370 | $(window).resize(function() { 371 | rtime = new Date(); 372 | if (timeout === false) { 373 | timeout = true; 374 | setTimeout(resizeend, delta); 375 | } 376 | }); 377 | 378 | function resizeend() { 379 | if (new Date() - rtime < delta) { 380 | setTimeout(resizeend, delta); 381 | } else { 382 | timeout = false; 383 | resizeCarousel(); 384 | } 385 | } 386 | }; 387 | } 388 | }; 389 | }]); 390 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AngularJS Carousel Directive Demo 5 | 6 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | << Previous 27 | << Previous 28 | Next >> 29 | Next >> 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | --------------------------------------------------------------------------------