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 = null; 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 = 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 clearTimeout(self.interval); 227 228 return self; 229 }; 230 231 232 // Set up our navigation 233 self.initNav = function() { 234 var $nav = $('<nav class="' + self.prefix + 'nav"><ol /></nav>'); 235 236 // Build our click navigation item-by-item 237 self.$slides.each(function(key) { 238 // If we've already set a label, let's use that 239 // instead of generating one 240 var label = this.getAttribute('data-nav') || key + 1; 241 242 // Listen to any callback functions 243 if($.isFunction(self.options.nav)) { 244 label = self.options.nav.call(self.$slides.eq(key), key, label); 245 } 246 247 // And add it to our navigation item 248 $nav.children('ol').append('<li data-slide="' + key + '">' + label + '</li>'); 249 }); 250 251 // Keep a copy of the nav everywhere so we can use it 252 self.$nav = $nav.insertAfter(self.$context); 253 254 // Now our nav is built, let's add it to the slider and bind 255 // for any click events on the generated links 256 self.$nav.find('li').on('click' + self.eventSuffix, function() { 257 // Cache our link and set it to be active 258 var $me = $(this).addClass(self.options.activeClass); 259 260 // Set the right active class, remove any other ones 261 $me.siblings().removeClass(self.options.activeClass); 262 263 // Move the slide 264 self.animate($me.attr('data-slide')); 265 }); 266 }; 267 268 269 // Set up our left-right arrow navigation 270 // (Not keyboard arrows, prev/next buttons) 271 self.initArrows = function() { 272 if(self.options.arrows === true) { 273 self.options.arrows = self.defaults.arrows; 274 } 275 276 // Loop our options object and bind our events 277 $.each(self.options.arrows, function(key, val) { 278 // Add our arrow HTML and bind it 279 self.$arrows.push( 280 $(val).insertAfter(self.$context).on('click' + self.eventSuffix, self[key]) 281 ); 282 }); 283 }; 284 285 286 // Set up our keyboad navigation 287 // Allow binding to multiple keycodes 288 self.initKeys = function() { 289 if(self.options.keys === true) { 290 self.options.keys = self.defaults.keys; 291 } 292 293 $(document).on('keyup' + self.eventSuffix, function(e) { 294 $.each(self.options.keys, function(key, val) { 295 if(e.which === val) { 296 $.isFunction(self[key]) && self[key].call(self); 297 } 298 }); 299 }); 300 }; 301 302 // Requires jQuery.event.swipe 303 // -> stephband.info/jquery.event.swipe 304 self.initSwipe = function() { 305 var width = self.$slides.width(); 306 307 // We don't want to have a tactile swipe in the slider 308 // in the fade animation, as it can cause some problems 309 // with layout, so we'll just disable it. 310 if(self.options.animation !== 'fade') { 311 312 self.$container.on({ 313 314 movestart: function(e) { 315 // If the movestart heads off in a upwards or downwards 316 // direction, prevent it so that the browser scrolls normally. 317 if((e.distX > e.distY && e.distX < -e.distY) || (e.distX < e.distY && e.distX > -e.distY)) { 318 return !!e.preventDefault(); 319 } 320 321 self.$container.css('position', 'relative'); 322 }, 323 324 move: function(e) { 325 self.$container.css('left', -(100 * self.current) + (100 * e.distX / width) + '%'); 326 }, 327 328 moveend: function(e) { 329 // Check if swiped distance is greater than threshold. 330 // If yes slide to next/prev slide. If not animate to 331 // starting point. 332 333 if((Math.abs(e.distX) / width) > self.options.swipeThreshold) { 334 335 self[e.distX < 0 ? 'next' : 'prev'](); 336 } 337 else { 338 339 self.$container.animate({left: -(100 * self.current) + '%' }, self.options.speed / 2 ); 340 } 341 } 342 }); 343 } 344 }; 345 346 // Infinite scrolling is a massive pain in the arse 347 // so we need to create a whole bloody function to set 348 // it up. Argh. 349 self.initInfinite = function() { 350 var pos = ['first', 'last']; 351 352 $.each(pos, function(index, item) { 353 self.$slides.push.apply( 354 self.$slides, 355 356 // Exclude all cloned slides and call .first() or .last() 357 // depending on what `item` is. 358 self.$slides.filter(':not(".' + self._ + '-clone")')[item]() 359 360 // Make a copy of it and identify it as a clone 361 .clone().addClass(self._ + '-clone') 362 363 // Either insert before or after depending on whether we're
364 // the first or last clone 365 ['insert' + (index === 0 ? 'After' : 'Before')]( 366 // Return the other element in the position array 367 // if item = first, return "last" 368 self.$slides[pos[~~!index]]() 369 ) 370 ); 371 }); 372 }; 373 374 // Remove any trace of arrows 375 // Loop our array of arrows and use jQuery to remove 376 // It'll unbind any event handlers for us 377 self.destroyArrows = function() { 378 $.each(self.$arrows, function(i, $arrow) { 379 $arrow.remove(); 380 }); 381 }; 382 383 // Remove any swipe events and reset the position 384 self.destroySwipe = function() { 385 // We bind to 4 events, so we'll unbind those 386 self.$container.off('movestart move moveend'); 387 }; 388 389 // Unset the keyboard navigation 390 // Remove the handler 391 self.destroyKeys = function() { 392 // Remove the event handler 393 $(document).off('keyup' + self.eventSuffix); 394 }; 395 396 self.setIndex = function(to) { 397 if(to < 0) { 398 to = self.total - 1; 399 } 400 401 self.current = Math.min(Math.max(0, to), self.total - 1); 402 403 if(self.options.nav) { 404 self.$nav.find('[data-slide="' + self.current + '"]')._active(self.options.activeClass); 405 } 406 407 self.$slides.eq(self.current)._active(self.options.activeClass); 408 409 return self; 410 }; 411 412 // Despite the name, this doesn't do any animation - since there's 413 // now three different types of animation, we let this method delegate 414 // to the right type, keeping the name for backwards compat. 415 self.animate = function(to, dir) { 416 // Animation shortcuts 417 // Instead of passing a number index, we can now 418 // use .data('unslider').animate('last'); 419 // or .unslider('animate:last') 420 // to go to the very last slide 421 if(to === 'first') to = 0; 422 if(to === 'last') to = self.total; 423 424 // Don't animate if it's not a valid index 425 if(isNaN(to)) { 426 return self; 427 } 428 429 if(self.options.autoplay) { 430 self.stop().start(); 431 } 432 433 self.setIndex(to); 434 435 // Add a callback method to do stuff with 436 self.$context.trigger(self._ + '.change', [to, self.$slides.eq(to)]); 437 438 // Delegate the right method - everything's named consistently 439 // so we can assume it'll be called "animate" + 440 var fn = 'animate' + $._ucfirst(self.options.animation); 441 442 // Make sure it's a valid animation method, otherwise we'll get 443 // a load of bug reports that'll be really hard to report 444 if($.isFunction(self[fn])) { 445 self[fn](self.current, dir); 446 } 447 448 return self; 449 }; 450 451 452 // Shortcuts for animating if we don't know what the current 453 // index is (i.e back/forward) 454 // For moving forward we need to make sure we don't overshoot. 455 self.next = function() { 456 var target = self.current + 1; 457 458 // If we're at the end, we need to move back to the start 459 if(target >= self.total) { 460 target = 0; 461 } 462 463 return self.animate(target, 'next'); 464 }; 465 466 // Previous is a bit simpler, we can just decrease the index 467 // by one and check if it's over 0. 468 self.prev = function() { 469 return self.animate(self.current - 1, 'prev'); 470 }; 471 472 473 // Our default animation method, the old-school left-to-right 474 // horizontal animation 475 self.animateHorizontal = function(to) { 476 var prop = 'left'; 477 478 // Add RTL support, slide the slider 479 // the other way if the site is right-to-left 480 if(self.$context.attr('dir') === 'rtl') { 481 prop = 'right'; 482 } 483 484 if(self.options.infinite) { 485 // So then we need to hide the first slide 486 self.$container.css('margin-' + prop, '-100%'); 487 } 488 489 return self.slide(prop, to); 490 }; 491 492 // The same animation methods, but vertical support 493 // RTL doesn't affect the vertical direction so we 494 // can just call as is 495 self.animateVertical = function(to) { 496 self.options.animateHeight = true; 497 498 // Normal infinite CSS fix doesn't work for 499 // vertical animation so we need to manually set it 500 // with pixels. Ah well. 501 if(self.options.infinite) { 502 self.$container.css('margin-top', -self.$slides.outerHeight()); 503 } 504 505 return self.slide('top', to); 506 }; 507 508 // Actually move the slide now 509 // We have to pass a property to animate as there's 510 // a few different directions it can now move, but it's 511 // otherwise unchanged from before. 512 self.slide = function(prop, to) { 513 // If we want to change the height of the slider 514 // to match the current slide, you can set 515 // {animateHeight: true} 516 self.animateHeight(to); 517 518 // For infinite sliding we add a dummy slide at the end and start 519 // of each slider to give the appearance of being infinite 520 if(self.options.infinite) { 521 var dummy; 522 523 // Going backwards to last slide 524 if(to === self.total - 1) { 525 // We're setting a dummy position and an actual one 526 // the dummy is what the index looks like 527 // (and what we'll silently update to afterwards), 528 // and the actual is what makes it not go backwards 529 dummy = self.total - 3; 530 to = -1; 531 } 532 533 // Going forwards to first slide 534 if(to === self.total - 2) { 535 dummy = 0; 536 to = self.total - 2; 537 } 538 539 // If it's a number we can safely set it 540 if(typeof dummy === 'number') { 541 self.setIndex(dummy); 542 543 // Listen for when the slide's finished transitioning so 544 // we can silently move it into the right place and clear 545 // this whole mess up. 546 self.$context.on(self._ + '.moved', function() { 547 if(self.current === dummy) { 548 self.$container.css(prop, -(100 * dummy) + '%').off(self._ + '.moved'); 549 } 550 }); 551 } 552 } 553 554 // We need to create an object to store our property in
555 // since we don't know what it'll be. 556 var obj = {}; 557 558 // Manually create it here 559 obj[prop] = -(100 * to) + '%'; 560 561 // And animate using our newly-created object 562 return self._move(self.$container, obj); 563 }; 564 565 566 // Fade between slides rather than, uh, sliding it 567 self.animateFade = function(to) { 568 // If we want to change the height of the slider 569 // to match the current slide, you can set 570 // {animateHeight: true} 571 self.animateHeight(to); 572 573 var $active = self.$slides.eq(to).addClass(self.options.activeClass); 574 575 // Toggle our classes 576 self._move($active.siblings().removeClass(self.options.activeClass), {opacity: 0}); 577 self._move($active, {opacity: 1}, false); 578 }; 579 580 // Animate height of slider 581 self.animateHeight = function(to) { 582 // If we want to change the height of the slider 583 // to match the current slide, you can set 584 // {animateHeight: true} 585 if (self.options.animateHeight) { 586 self._move(self.$context, {height: self.$slides.eq(to).outerHeight()}, false); 587 } 588 }; 589
590 self._move = function($el, obj, callback, speed) { 591 if(callback !== false) { 592 callback = function() { 593 self.$context.trigger(self._ + '.moved'); 594 }; 595 } 596 597 return $el._move(obj, speed || self.options.speed, self.options.easing, callback); 598 }; 599 600 // Allow daisy-chaining of methods 601 return self.init(options); 602 }; 603 604 // Internal (but global) jQuery methods 605 // They're both just helpful types of shorthand for 606 // anything that might take too long to write out or 607 // something that might be used more than once. 608 $.fn._active = function(className) { 609 return this.addClass(className).siblings().removeClass(className); 610 }; 611 612 // The equivalent to PHP's ucfirst(). Take the first 613 // character of a string and make it uppercase. 614 // Simples. 615 $._ucfirst = function(str) { 616 // Take our variable, run a regex on the first letter 617 return (str + '').toLowerCase().replace(/^./, function(match) { 618 // And uppercase it. Simples. 619 return match.toUpperCase(); 620 }); 621 }; 622 623 $.fn._move = function() { 624 this.stop(true, true); 625 return $.fn[$.fn.velocity ? 'velocity' : 'animate'].apply(this, arguments); 626 }; 627 628 // And set up our jQuery plugin 629 $.fn.unslider = function(opts) { 630 return this.each(function(index,elem) { 631 var $this = $(elem); 632 var unslider = $(elem).data('unslider'); 633 if(unslider instanceof $.Unslider) { 634 return; 635 } 636 // Allow usage of .unslider('function_name') 637 // as well as using .data('unslider') to access the 638 // main Unslider object 639 if(typeof opts === 'string' && $this.data('unslider')) { 640 opts = opts.split(':'); 641 642 var call = $this.data('unslider')[opts[0]]; 643 644 // Do we have arguments to pass to the string-function? 645 if($.isFunction(call)) { 646 return call.apply($this, opts[1] ? opts[1].split(',') : null); 647 } 648 } 649 650 return $this.data('unslider', new $.Unslider($this, opts)); 651 }); 652 }; 653 654}));
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.