1/** 2 * Unslider 3 * version 2.0 4 * by @idiot and friends 5 */ 6 7(function(factory) { 8 if (typeof module === 'object' && typeof module.exports === 'object') { 9 factory(require('jquery')); 10 } else if (typeof define === 'function' && define.amd) { 11 // AMD. Register as an anonymous module. 12 define([], factory(window.jQuery)); 13 } else { 14 factory(window.jQuery); 15 } 16}(function($) { 17 // Don't throw any errors when jQuery 18 if(!$) { 19 return console.warn('Unslider needs jQuery'); 20 } 21 22 $.Unslider = function(context, options) { 23 var self = this; 24 25 // Create an Unslider reference we can use everywhere 26 self._ = 'unslider'; 27 28 // Store our default options in here 29 // Everything will be overwritten by the jQuery plugin though 30 self.defaults = { 31 // Should the slider move on its own or only when 32 // you interact with the nav/arrows? 33 // Only accepts boolean true/false. 34 autoplay: false, 35 36 // 3 second delay between slides moving, pass 37 // as a number in milliseconds. 38 delay: 3000, 39 40 // Animation speed in millseconds 41 speed: 750, 42 43 // An easing string to use. If you're using Velocity, use a 44 // Velocity string otherwise you can use jQuery/jQ UI options. 45 easing: 'swing', // [.42, 0, .58, 1], 46 47 // Does it support keyboard arrows? 48 // Can pass either true or false - 49 // or an object with the keycodes, like so: 50 // { 51 // prev: 37, 52 // next: 39 53 // } 54 // You can call any internal method name 55 // before the keycode and it'll be called. 56 keys: { 57 prev: 37, 58 next: 39 59 }, 60 61 // Do you want to generate clickable navigation 62 // to skip to each slide? Accepts boolean true/false or 63 // a callback function per item to generate. 64 nav: true, 65 66 // Should there be left/right arrows to go back/forth? 67 // -> This isn't keyboard support. 68 // Either set true/false, or an object with the HTML 69 // elements for each arrow like below: 70 arrows: { 71 prev: '<a class="' + self._ + '-arrow prev">Prev</a>', 72 next: '<a class="' + self._ + '-arrow next">Next</a>' 73 }, 74 75 // How should Unslider animate? 76 // It can do one of the following types: 77 // "fade": each slide fades in to each other 78 // "horizontal": each slide moves from left to right 79 // "vertical": each slide moves from top to bottom 80 animation: 'horizontal', 81 82 // If you don't want to use a list to display your slides, 83 // you can change it here. Not recommended and you'll need 84 // to adjust the CSS accordingly. 85 selectors: { 86 container: 'ul:first', 87 slides: 'li' 88 }, 89 90 // Do you want to animate the heights of each slide as 91 // it moves 92 animateHeight: false, 93 94 // Active class for the nav 95 activeClass: self._ + '-active', 96 97 // Have swipe support? 98 // You can set this here with a boolean and always use 99 // initSwipe/destroySwipe later on. 100 swipe: true, 101 // Swipe threshold - 102 // lower float for enabling short swipe 103 swipeThreshold: 0.2 104 }; 105 106 // Set defaults 107 self.$context = context; 108 self.options = {}; 109 110 // Leave our elements blank for now 111 // Since they get changed by the options, we'll need to 112 // set them in the init method. 113 self.$parent = null; 114 self.$container = null; 115 self.$slides = null; 116 self.$nav = null; 117 self.$arrows = []; 118 119 // Set our indexes and totals 120 self.total = 0; 121 self.current = 0; 122 123 // Generate a specific random ID so we don't dupe events 124 self.prefix = self._ + '-'; 125 self.eventSuffix = '.' + self.prefix + ~~(Math.random() * 2e3); 126 127 // In case we're going to use the autoplay 128 self.interval = []; 129 130 // Get everything set up innit 131 self.init = function(options) { 132 // Set up our options inside here so we can re-init at 133 // any time 134 self.options = $.extend({}, self.defaults, options); 135 136 // Our elements 137 self.$container = self.$context.find(self.options.selectors.container).addClass(self.prefix + 'wrap'); 138 self.$slides = self.$container.children(self.options.selectors.slides); 139 140 // We'll manually init the container 141 self.setup(); 142 143 // We want to keep this script as small as possible 144 // so we'll optimise some checks
145 $.each(['nav', 'arrows', 'keys', 'infinite'], function(index, module) { 146 self.options[module] && self['init' + $._ucfirst(module)](); 147 }); 148 149 // Add swipe support 150 if(jQuery.event.special.swipe && self.options.swipe) { 151 self.initSwipe(); 152 } 153 154 // If autoplay is set to true, call self.start() 155 // to start calling our timeouts 156 self.options.autoplay && self.start(); 157 158 // We should be able to recalculate slides at will 159 self.calculateSlides(); 160 161 // Listen to a ready event 162 self.$context.trigger(self._ + '.ready'); 163 164 // Everyday I'm chainin' 165 return self.animate(self.options.index || self.current, 'init'); 166 }; 167 168 self.setup = function() { 169 // Add a CSS hook to the main element 170 self.$context.addClass(self.prefix + self.options.animation).wrap('<div class="' + self._ + '" />'); 171 self.$parent = self.$context.parent('.' + self._); 172 173 // We need to manually check if the container is absolutely 174 // or relatively positioned 175 var position = self.$context.css('position'); 176 177 // If we don't already have a position set, we'll 178 // automatically set it ourselves 179 if(position === 'static') { 180 self.$context.css('position', 'relative'); 181 } 182 183 self.$context.css('overflow', 'hidden'); 184 }; 185 186 // Set up the slide widths to animate with 187 // so the box doesn't float over 188 self.calculateSlides = function() { 189 // update slides before recalculating the total 190 self.$slides = self.$container.children(self.options.selectors.slides); 191 192 self.total = self.$slides.length; 193 194 // Set the total width 195 if(self.options.animation !== 'fade') { 196 var prop = 'width'; 197 198 if(self.options.animation === 'vertical') { 199 prop = 'height'; 200 } 201 202 self.$container.css(prop, (self.total * 100) + '%').addClass(self.prefix + 'carousel'); 203 self.$slides.css(prop, (100 / self.total) + '%'); 204 } 205 }; 206 207 208 // Start our autoplay 209 self.start = function() { 210 self.interval.push(setTimeout(function() { 211 // Move on to the next slide 212 self.next(); 213 214 // If we've got autoplay set up 215 // we don't need to keep starting 216 // the slider from within our timeout 217 // as .animate() calls it for us 218 }, self.options.delay)); 219 220 return self; 221 }; 222 223 // And pause our timeouts 224 // and force stop the slider if needed 225 self.stop = function() { 226 var timeout; 227 while(timeout = self.interval.pop()) { 228 clearTimeout(timeout); 229 } 230 231 return self; 232 }; 233 234 235 // Set up our navigation 236 self.initNav = function() { 237 var $nav = $('<nav class="' + self.prefix + 'nav"><ol /></nav>'); 238 239 // Build our click navigation item-by-item 240 self.$slides.each(function(key) { 241 // If we've already set a label, let's use that 242 // instead of generating one 243 var label = this.getAttribute('data-nav') || key + 1; 244 245 // Listen to any callback functions 246 if($.isFunction(self.options.nav)) { 247 label = self.options.nav.call(self.$slides.eq(key), key, label); 248 } 249 250 // And add it to our navigation item 251 $nav.children('ol').append('<li data-slide="' + key + '">' + label + '</li>'); 252 }); 253 254 // Keep a copy of the nav everywhere so we can use it 255 self.$nav = $nav.insertAfter(self.$context); 256 257 // Now our nav is built, let's add it to the slider and bind 258 // for any click events on the generated links 259 self.$nav.find('li').on('click' + self.eventSuffix, function() { 260 // Cache our link and set it to be active 261 var $me = $(this).addClass(self.options.activeClass); 262 263 // Set the right active class, remove any other ones 264 $me.siblings().removeClass(self.options.activeClass); 265 266 // Move the slide 267 self.animate($me.attr('data-slide')); 268 }); 269 }; 270 271 272 // Set up our left-right arrow navigation 273 // (Not keyboard arrows, prev/next buttons) 274 self.initArrows = function() { 275 if(self.options.arrows === true) { 276 self.options.arrows = self.defaults.arrows; 277 } 278 279 // Loop our options object and bind our events 280 $.each(self.options.arrows, function(key, val) { 281 // Add our arrow HTML and bind it 282 self.$arrows.push(
283 $(val).insertAfter(self.$context).on('click' + self.eventSuffix, self[key]) 284 ); 285 }); 286 }; 287 288 289 // Set up our keyboad navigation 290 // Allow binding to multiple keycodes 291 self.initKeys = function() { 292 if(self.options.keys === true) { 293 self.options.keys = self.defaults.keys; 294 } 295 296 $(document).on('keyup' + self.eventSuffix, function(e) { 297 $.each(self.options.keys, function(key, val) { 298 if(e.which === val) { 299 $.isFunction(self[key]) && self[key].call(self); 300 } 301 }); 302 }); 303 }; 304 305 // Requires jQuery.event.swipe 306 // -> stephband.info/jquery.event.swipe 307 self.initSwipe = function() { 308 var width = self.$slides.width(); 309 310 // We don't want to have a tactile swipe in the slider 311 // in the fade animation, as it can cause some problems 312 // with layout, so we'll just disable it. 313 if(self.options.animation !== 'fade') { 314 315 self.$container.on({ 316 317 movestart: function(e) { 318 // If the movestart heads off in a upwards or downwards 319 // direction, prevent it so that the browser scrolls normally. 320 if((e.distX > e.distY && e.distX < -e.distY) || (e.distX < e.distY && e.distX > -e.distY)) { 321 return !!e.preventDefault(); 322 } 323 324 self.$container.css('position', 'relative'); 325 }, 326 327 move: function(e) { 328 self.$container.css('left', -(100 * self.current) + (100 * e.distX / width) + '%'); 329 }, 330 331 moveend: function(e) { 332 // Check if swiped distance is greater than threshold. 333 // If yes slide to next/prev slide. If not animate to 334 // starting point. 335 336 if((Math.abs(e.distX) / width) > self.options.swipeThreshold) { 337 338 self[e.distX < 0 ? 'next' : 'prev'](); 339 } 340 else { 341 342 self.$container.animate({left: -(100 * self.current) + '%' }, self.options.speed / 2 ); 343 } 344 } 345 }); 346 } 347 }; 348 349 // Infinite scrolling is a massive pain in the arse 350 // so we need to create a whole bloody function to set 351 // it up. Argh. 352 self.initInfinite = function() { 353 var pos = ['first', 'last']; 354 355 $.each(pos, function(index, item) { 356 self.$slides.push.apply( 357 self.$slides, 358 359 // Exclude all cloned slides and call .first() or .last() 360 // depending on what `item` is. 361 self.$slides.filter(':not(".' + self._ + '-clone")')[item]() 362 363 // Make a copy of it and identify it as a clone 364 .clone().addClass(self._ + '-clone') 365 366 // Either insert before or after depending on whether we're 367 // the first or last clone 368 ['insert' + (index === 0 ? 'After' : 'Before')]( 369 // Return the other element in the position array 370 // if item = first, return "last" 371 self.$slides[pos[~~!index]]() 372 ) 373 ); 374 }); 375 }; 376 377 // Remove any trace of arrows 378 // Loop our array of arrows and use jQuery to remove 379 // It'll unbind any event handlers for us 380 self.destroyArrows = function() { 381 $.each(self.$arrows, function(i, $arrow) { 382 $arrow.remove(); 383 }); 384 }; 385 386 // Remove any swipe events and reset the position 387 self.destroySwipe = function() { 388 // We bind to 4 events, so we'll unbind those 389 self.$container.off('movestart move moveend'); 390 }; 391 392 // Unset the keyboard navigation 393 // Remove the handler 394 self.destroyKeys = function() { 395 // Remove the event handler 396 $(document).off('keyup' + self.eventSuffix); 397 }; 398 399 self.setIndex = function(to) { 400 if(to < 0) { 401 to = self.total - 1; 402 } 403 404 self.current = Math.min(Math.max(0, to), self.total - 1); 405 406 if(self.options.nav) { 407 self.$nav.find('[data-slide="' + self.current + '"]')._active(self.options.activeClass); 408 } 409 410 self.$slides.eq(self.current)._active(self.options.activeClass); 411 412 return self; 413 }; 414 415 // Despite the name, this doesn't do any animation - since there's 416 // now three different types of animation, we let this method delegate 417 // to the right type, keeping the name for backwards compat. 418 self.animate = function(to, dir) { 419 // Animation shortcuts 420 // Instead of passing a number index, we can now 421 // use .data('unslider').animate('last'); 422 // or .unslider('animate:last') 423 // to go to the very last slide 424 if(to === 'first') to = 0; 425 if(to === 'last') to = self.total; 426 427 // Don't animate if it's not a valid index 428 if(isNaN(to)) { 429 return self; 430 } 431 432 if(self.options.autoplay) { 433 self.stop().start(); 434 } 435 436 self.setIndex(to); 437 438 // Temp to for slide effect
439 var tempTo = to; 440 var tempTo2 = false; 441 if(self.options.animation !== 'fade') { 442 // 443 tempTo = parseInt(to)+1; 444 // 445 if(tempTo >= (self.total-1) && dir !== 'prev') {tempTo = 1;tempTo2 = (self.total-1);} 446 if(tempTo >= (self.total-1) && dir === 'prev') {tempTo = (self.total-2);tempTo2 = 0;} 447 //console.log(tempTo2); 448 } 449 450 // Add a callback method to do stuff with 451 self.$context.trigger(self._ + '.change', [tempTo, self.$slides.eq(tempTo), ((tempTo2 !== false) ? self.$slides.eq(tempTo2) : false)]); 452 453 // Delegate the right method - everything's named consistently 454 // so we can assume it'll be called "animate" + 455 var fn = 'animate' + $._ucfirst(self.options.animation); 456 457 // Make sure it's a valid animation method, otherwise we'll get 458 // a load of bug reports that'll be really hard to report 459 if($.isFunction(self[fn])) { 460 self[fn](self.current, dir); 461 } 462 463 return self; 464 }; 465 466 467 // Shortcuts for animating if we don't know what the current 468 // index is (i.e back/forward) 469 // For moving forward we need to make sure we don't overshoot. 470 self.next = function() { 471 var target = self.current + 1; 472 var tempTotal = (self.options.animation === 'fade') ? self.total-2 : self.total; 473 474 // If we're at the end, we need to move back to the start 475 if(target >= tempTotal) { 476 if(self.options.noloop && !self.options.infinite) { 477 target = self.total - 1; 478 } else { 479 target = 0; 480 } 481 } 482 483 return self.animate(target, 'next'); 484 }; 485 486 // The same as next, but the opposite :) 487 self.prev = function() { 488 var target = self.current - 1; 489 490 // If we're at the first, we need to move forward to the end 491 if(target < 0) { 492 if(self.options.noloop && !self.options.infinite) { 493 target = 0; 494 } else { 495 target = (self.options.animation === 'fade') ? self.total - 3 : self.total - 1; 496 } 497 } 498 499 return self.animate(target, 'prev'); 500 }; 501 502 503 // Our default animation method, the old-school left-to-right 504 // horizontal animation 505 self.animateHorizontal = function(to) { 506 var prop = 'left'; 507 508 // Add RTL support, slide the slider 509 // the other way if the site is right-to-left 510 if(self.$context.attr('dir') === 'rtl') { 511 prop = 'right'; 512 } 513 514 if(self.options.infinite) { 515 // So then we need to hide the first slide 516 self.$container.css('margin-' + prop, '-100%'); 517 } 518 519 return self.slide(prop, to); 520 }; 521 522 // The same animation methods, but vertical support 523 // RTL doesn't affect the vertical direction so we 524 // can just call as is 525 self.animateVertical = function(to) { 526 self.options.animateHeight = true; 527 528 // Normal infinite CSS fix doesn't work for 529 // vertical animation so we need to manually set it 530 // with pixels. Ah well. 531 if(self.options.infinite) { 532 self.$container.css('margin-top', -self.$slides.outerHeight()); 533 } 534 535 return self.slide('top', to); 536 }; 537 538 // Actually move the slide now 539 // We have to pass a property to animate as there's 540 // a few different directions it can now move, but it's 541 // otherwise unchanged from before. 542 self.slide = function(prop, to) { 543 // If we want to change the height of the slider 544 // to match the current slide, you can set 545 // {animateHeight: true} 546 self.animateHeight(to); 547 548 // For infinite sliding we add a dummy slide at the end and start 549 // of each slider to give the appearance of being infinite 550 if(self.options.infinite) { 551 var dummy; 552 553 // Going backwards to last slide 554 if(to === self.total - 1) { 555 // We're setting a dummy position and an actual one 556 // the dummy is what the index looks like 557 // (and what we'll silently update to afterwards), 558 // and the actual is what makes it not go backwards 559 dummy = self.total - 3; 560 to = -1; 561 } 562 563 // Going forwards to first slide 564 if(to === self.total - 2) { 565 dummy = 0; 566 to = self.total - 2; 567 } 568 569 // If it's a number we can safely set it 570 if(typeof dummy === 'number') { 571 self.setIndex(dummy); 572 573 // Listen for when the slide's finished transitioning so 574 // we can silently move it into the right place and clear 575 // this whole mess up. 576 self.$context.on(self._ + '.moved', function() { 577 if(self.current === dummy) { 578 self.$container.css(prop, -(100 * dummy) + '%').off(self._ + '.moved'); 579 } 580 }); 581 } 582 } 583 584 // We need to create an object to store our property in
585 // since we don't know what it'll be. 586 var obj = {}; 587 588 // Manually create it here 589 obj[prop] = -(100 * to) + '%'; 590 591 // And animate using our newly-created object 592 return self._move(self.$container, obj); 593 }; 594 595 596 // Fade between slides rather than, uh, sliding it 597 self.animateFade = function(to) { 598 // If we want to change the height of the slider 599 // to match the current slide, you can set 600 // {animateHeight: true} 601 self.animateHeight(to); 602 603 var $active = self.$slides.eq(to).addClass(self.options.activeClass); 604 605 // Toggle our classes 606 self._move($active.siblings().removeClass(self.options.activeClass), {opacity: 0}); 607 self._move($active, {opacity: 1}, false); 608 }; 609 610 // Animate height of slider 611 self.animateHeight = function(to) { 612 // If we want to change the height of the slider 613 // to match the current slide, you can set 614 // {animateHeight: true} 615 if (self.options.animateHeight) { 616 self._move(self.$context, {height: self.$slides.eq(to).outerHeight()}, false); 617 } 618 }; 619
620 self._move = function($el, obj, callback, speed) { 621 if(callback !== false) { 622 callback = function() { 623 self.$context.trigger(self._ + '.moved'); 624 }; 625 } 626 627 return $el._move(obj, speed || self.options.speed, self.options.easing, callback); 628 }; 629 630 // Allow daisy-chaining of methods 631 return self.init(options); 632 }; 633 634 // Internal (but global) jQuery methods 635 // They're both just helpful types of shorthand for 636 // anything that might take too long to write out or 637 // something that might be used more than once. 638 $.fn._active = function(className) { 639 return this.addClass(className).siblings().removeClass(className); 640 }; 641 642 // The equivalent to PHP's ucfirst(). Take the first 643 // character of a string and make it uppercase. 644 // Simples. 645 $._ucfirst = function(str) { 646 // Take our variable, run a regex on the first letter 647 return (str + '').toLowerCase().replace(/^./, function(match) { 648 // And uppercase it. Simples. 649 return match.toUpperCase(); 650 }); 651 }; 652 653 $.fn._move = function() { 654 this.stop(true, true); 655 return $.fn[$.fn.velocity ? 'velocity' : 'animate'].apply(this, arguments); 656 }; 657 658 // And set up our jQuery plugin 659 $.fn.unslider = function(opts) { 660 return this.each(function(index,elem) { 661 var $this = $(elem); 662 var unslider = $(elem).data('unslider'); 663 if(unslider instanceof $.Unslider) { 664 return; 665 } 666 // Allow usage of .unslider('function_name') 667 // as well as using .data('unslider') to access the 668 // main Unslider object 669 if(typeof opts === 'string' && $this.data('unslider')) { 670 opts = opts.split(':'); 671 672 var call = $this.data('unslider')[opts[0]]; 673 674 // Do we have arguments to pass to the string-function? 675 if($.isFunction(call)) { 676 return call.apply($this, opts[1] ? opts[1].split(',') : null); 677 } 678 } 679 680 return $this.data('unslider', new $.Unslider($this, opts)); 681 }); 682 }; 683 684}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.