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