1/*! 2 * sly 1.6.1 - 8th Aug 2015 3 * https://github.com/darsain/sly 4 * 5 * Licensed under the MIT license. 6 * http://opensource.org/licenses/MIT 7 */ 8 9;(function ($, w, undefined) { 10 'use strict'; 11 12 var pluginName = 'sly'; 13 var className = 'Sly'; 14 var namespace = pluginName; 15 16 // Local WindowAnimationTiming interface 17 var cAF = w.cancelAnimationFrame || w.cancelRequestAnimationFrame; 18 var rAF = w.requestAnimationFrame; 19 20 // Support indicators 21 var transform, gpuAcceleration; 22 23 // Other global values 24 var $doc = $(document); 25 var dragInitEvents = 'touchstart.' + namespace + ' mousedown.' + namespace; 26 var dragMouseEvents = 'mousemove.' + namespace + ' mouseup.' + namespace; 27 var dragTouchEvents = 'touchmove.' + namespace + ' touchend.' + namespace; 28 var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel.' : 'mousewheel.') + namespace; 29 var clickEvent = 'click.' + namespace; 30 var mouseDownEvent = 'mousedown.' + namespace; 31 var interactiveElements = ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA']; 32 var tmpArray = []; 33 var time; 34 35 // Math shorthands 36 var abs = Math.abs; 37 var sqrt = Math.sqrt; 38 var pow = Math.pow; 39 var round = Math.round; 40 var max = Math.max; 41 var min = Math.min; 42 43 // Keep track of last fired global wheel event 44 var lastGlobalWheel = 0; 45 /*$doc.on(wheelEvent, function (event) { 46 var sly = event.originalEvent[namespace]; 47 var time = +new Date(); 48 // Update last global wheel time, but only when event didn't originate 49 // in Sly frame, or the origin was less than scrollHijack time ago 50 if (!sly || sly.options.scrollHijack < time - lastGlobalWheel) lastGlobalWheel = time; 51 });*/ 52 53 /** 54 * Sly. 55 * 56 * @class 57 * 58 * @param {Element} frame DOM element of sly container. 59 * @param {Object} options Object with options. 60 * @param {Object} callbackMap Callbacks map. 61 */ 62 function Sly(frame, options, callbackMap) { 63 if (!(this instanceof Sly)) return new Sly(frame, options, callbackMap); 64 65 // Extend options 66 var o = $.extend({}, Sly.defaults, options); 67 68 // Private variables 69 var self = this; 70 var parallax = isNumber(frame); 71 72 // Frame 73 var $frame = $(frame); 74 var $slidee = o.slidee ? $(o.slidee).eq(0) : $frame.children().eq(0); 75 var frameSize = 0; 76 var slideeSize = 0; 77 var pos = { 78 start: 0, 79 center: 0, 80 end: 0, 81 cur: 0, 82 dest: 0 83 }; 84 85 // Scrollbar 86 var $sb = $(o.scrollBar).eq(0); 87 var $handle = $sb.children().eq(0); 88 var sbSize = 0; 89 var handleSize = 0; 90 var hPos = { 91 start: 0, 92 end: 0, 93 cur: 0 94 }; 95 96 // Pagesbar 97 var $pb = $(o.pagesBar); 98 var $pages = 0; 99 var pages = []; 100 101 // Items 102 var $items = 0; 103 var items = []; 104 var rel = { 105 firstItem: 0, 106 lastItem: 0, 107 centerItem: 0, 108 activeItem: null, 109 activePage: 0 110 }; 111 112 // Styles 113 var frameStyles = new StyleRestorer($frame[0]); 114 var slideeStyles = new StyleRestorer($slidee[0]); 115 var sbStyles = new StyleRestorer($sb[0]); 116 var handleStyles = new StyleRestorer($handle[0]); 117 118 // Navigation type booleans 119 var basicNav = o.itemNav === 'basic'; 120 var forceCenteredNav = o.itemNav === 'forceCentered'; 121 var centeredNav = o.itemNav === 'centered' || forceCenteredNav; 122 var itemNav = !parallax && (basicNav || centeredNav || forceCenteredNav); 123 124 // Miscellaneous 125 var $scrollSource = o.scrollSource ? $(o.scrollSource) : $frame; 126 var $dragSource = o.dragSource ? $(o.dragSource) : $frame; 127 var $forwardButton = $(o.forward); 128 var $backwardButton = $(o.backward); 129 var $prevButton = $(o.prev); 130 var $nextButton = $(o.next); 131 var $prevPageButton = $(o.prevPage); 132 var $nextPageButton = $(o.nextPage); 133 var callbacks = {}; 134 var last = {}; 135 var animation = {}; 136 var move = {}; 137 var dragging = { 138 released: 1 139 }; 140 var scrolling = { 141 last: 0, 142 delta: 0, 143 resetTime: 200 144 }; 145 var renderID = 0; 146 var historyID = 0; 147 var cycleID = 0; 148 var continuousID = 0; 149 var i, l; 150 151 // Normalizing frame 152 if (!parallax) { 153 frame = $frame[0]; 154 } 155 156 // Expose properties 157 self.initialized = 0; 158 self.frame = frame; 159 self.slidee = $slidee[0]; 160 self.pos = pos; 161 self.rel = rel; 162 self.items = items; 163 self.pages = pages; 164 self.isPaused = 0; 165 self.options = o; 166 self.dragging = dragging; 167 168 /** 169 * Loading function. 170 * 171 * Populate arrays, set sizes, bind events, ... 172 * 173 * @param {Boolean} [isInit] Whether load is called from within self.init(). 174 * @return {Void} 175 */ 176 function load(isInit) { 177 // Local variables 178 var lastItemsCount = 0; 179 var lastPagesCount = pages.length; 180 181 // Save old position 182 pos.old = $.extend({}, pos); 183 184 // Reset global variables 185 frameSize = parallax ? 0 : $frame[o.horizontal ? 'width' : 'height'](); 186 sbSize = $sb[o.horizontal ? 'width' : 'height']();
187 slideeSize = parallax ? frame : $slidee[o.horizontal ? 'outerWidth' : 'outerHeight'](); 188 pages.length = 0; 189 190 // Set position limits & relatives 191 pos.start = 0; 192 pos.end = max(slideeSize - frameSize, 0); 193 194 // Sizes & offsets for item based navigations 195 if (itemNav) { 196 // Save the number of current items 197 lastItemsCount = items.length; 198 199 // Reset itemNav related variables 200 $items = $slidee.children(o.itemSelector); 201 items.length = 0; 202 203 // Needed variables 204 var paddingStart = getPx($slidee, o.horizontal ? 'paddingLeft' : 'paddingTop'); 205 var paddingEnd = getPx($slidee, o.horizontal ? 'paddingRight' : 'paddingBottom'); 206 var borderBox = $($items).css('boxSizing') === 'border-box'; 207 var areFloated = $items.css('float') !== 'none'; 208 var ignoredMargin = 0; 209 var lastItemIndex = $items.length - 1; 210 var lastItem; 211 212 // Reset slideeSize 213 slideeSize = 0; 214 215 // Iterate through items 216 $items.each(function (i, element) { 217 // Item 218 var $item = $(element); 219 var rect = element.getBoundingClientRect(); 220 var itemSize = round(o.horizontal ? rect.width || rect.right - rect.left : rect.height || rect.bottom - rect.top); 221 var itemMarginStart = getPx($item, o.horizontal ? 'marginLeft' : 'marginTop'); 222 var itemMarginEnd = getPx($item, o.horizontal ? 'marginRight' : 'marginBottom'); 223 224 if($item.parents('.frame.props').length){ 225 if($item.find('td').eq(0).length){ 226 itemSize -= $item.find('td').eq(0).outerWidth(); 227 } 228 } 229 230 var itemSizeFull = itemSize + itemMarginStart + itemMarginEnd; 231 var singleSpaced = !itemMarginStart || !itemMarginEnd; 232 var item = {}; 233 item.el = element; 234 item.size = singleSpaced ? itemSize : itemSizeFull; 235 item.half = item.size / 2; 236 item.start = slideeSize + (singleSpaced ? itemMarginStart : 0); 237 item.center = item.start - round(frameSize / 2 - item.size / 2); 238 item.end = item.start - frameSize + item.size; 239 240 // Account for slidee padding 241 if (!i) { 242 slideeSize += paddingStart; 243 } 244 245 // Increment slidee size for size of the active element 246 slideeSize += itemSizeFull; 247 248 // Try to account for vertical margin collapsing in vertical mode 249 // It's not bulletproof, but should work in 99% of cases 250 if (!o.horizontal && !areFloated) { 251 // Subtract smaller margin, but only when top margin is not 0, and this is not the first element 252 if (itemMarginEnd && itemMarginStart && i > 0) { 253 slideeSize -= min(itemMarginStart, itemMarginEnd); 254 } 255 } 256 257 // Things to be done on last item 258 if (i === lastItemIndex) { 259 item.end += paddingEnd; 260 slideeSize += paddingEnd; 261 ignoredMargin = singleSpaced ? itemMarginEnd : 0; 262 } 263 264 // Add item object to items array 265 items.push(item); 266 lastItem = item; 267 }); 268 269 // Resize SLIDEE to fit all items 270 $slidee[0].style[o.horizontal ? 'width' : 'height'] = (borderBox ? slideeSize: slideeSize - paddingStart - paddingEnd) + 'px'; 271 272 // Adjust internal SLIDEE size for last margin 273 slideeSize -= ignoredMargin; 274 275 // Set limits 276 if (items.length) { 277 pos.start = items[0][forceCenteredNav ? 'center' : 'start']; 278 pos.end = forceCenteredNav ? lastItem.center : frameSize < slideeSize ? lastItem.end : pos.start; 279 } else { 280 pos.start = pos.end = 0; 281 } 282 } 283 284 // Calculate SLIDEE center position 285 pos.center = round(pos.end / 2 + pos.start / 2); 286 287 // Update relative positions 288 updateRelatives(); 289 290 // Scrollbar 291 if ($handle.length && sbSize > 0) { 292 // Stretch scrollbar handle to represent the visible area 293 if (o.dynamicHandle) { 294 handleSize = pos.start === pos.end ? sbSize : round(sbSize * frameSize / slideeSize); 295 handleSize = within(handleSize, o.minHandleSize, sbSize); 296 $handle[0].style[o.horizontal ? 'width' : 'height'] = handleSize + 'px'; 297 var scrollbar = $($sb).closest('.wrapp_scrollbar'); 298 if(handleSize!=sbSize){ 299 scrollbar.addClass('relative_block'); 300 $($sb).parent().animate({'opacity': 1}, 50); 301 $($sb).fadeIn(); 302 scrollbar.find('.slider_navigation').fadeIn(); 303 //if($($frame).hasClass('props')){ 304 $($frame).addClass('border'); 305 //} 306 }else{ 307 scrollbar.removeClass('relative_block'); 308 $($sb).parent().animate({'opacity': 0}, 50); 309 $($sb).fadeOut(); 310 scrollbar.find('.slider_navigation').fadeOut(); 311 //if($($frame).hasClass('props')){ 312 $($frame).removeClass('border'); 313 //} 314 } 315 } else { 316 handleSize = $handle[o.horizontal ? 'outerWidth' : 'outerHeight'](); 317 } 318 319 hPos.end = sbSize - handleSize; 320 321 if (!renderID) { 322 syncScrollbar(); 323 } 324 } 325 326 // Pages 327 if (!parallax && frameSize > 0) { 328 var tempPagePos = pos.start; 329 var pagesHtml = ''; 330 331 // Populate pages array 332 if (itemNav) { 333 $.each(items, function (i, item) { 334 if (forceCenteredNav) { 335 pages.push(item.center); 336 } else if (item.start + item.size > tempPagePos && tempPagePos <= pos.end) { 337 tempPagePos = item.start; 338 pages.push(tempPagePos); 339 tempPagePos += frameSize; 340 if (tempPagePos > pos.end && tempPagePos < pos.end + frameSize) { 341 pages.push(pos.end); 342 } 343 } 344 }); 345 } else { 346 while (tempPagePos - frameSize < pos.end) { 347 pages.push(tempPagePos); 348 tempPagePos += frameSize; 349 } 350 } 351 352 // Pages bar 353 if ($pb[0] && lastPagesCount !== pages.length) { 354 for (var i = 0; i < pages.length; i++) { 355 pagesHtml += o.pageBuilder.call(self, i); 356 } 357 $pages = $pb.html(pagesHtml).children(); 358 $pages.eq(rel.activePage).addClass(o.activeClass); 359 } 360 } 361 362 // Extend relative variables object with some useful info 363 rel.slideeSize = slideeSize; 364 rel.frameSize = frameSize; 365 rel.sbSize = sbSize; 366 rel.handleSize = handleSize; 367 368 // Activate requested position 369 if (itemNav) { 370 if (isInit && o.startAt != null) { 371 activate(o.startAt); 372 self[centeredNav ? 'toCenter' : 'toStart'](o.startAt); 373 } 374 // Fix possible overflowing 375 var activeItem = items[rel.activeItem]; 376 slideTo(centeredNav && activeItem ? activeItem.center : within(pos.dest, pos.start, pos.end)); 377 } else { 378 if (isInit) { 379 if (o.startAt != null) slideTo(o.startAt, 1); 380 } else { 381 // Fix possible overflowing 382 slideTo(within(pos.dest, pos.start, pos.end)); 383 } 384 } 385 386 // Trigger load event 387 trigger('load'); 388 } 389 self.reload = function () { load(); }; 390 391 /** 392 * Animate to a position. 393 * 394 * @param {Int} newPos New position. 395 * @param {Bool} immediate Reposition immediately without an animation. 396 * @param {Bool}
396 dontAlign Do not align items, use the raw position passed in first argument. 397 * 398 * @return {Void} 399 */ 400 function slideTo(newPos, immediate, dontAlign) { 401 // Align items 402 403 /*if (itemNav && dragging.released && !dontAlign) { 404 var tempRel = getRelatives(newPos); 405 var isNotBordering = newPos > pos.start && newPos < pos.end; 406 407 if (centeredNav) { 408 if (isNotBordering) { 409 newPos = items[tempRel.centerItem].center; 410 } 411 if (forceCenteredNav && o.activateMiddle) { 412 activate(tempRel.centerItem); 413 } 414 } else if (isNotBordering) { 415 newPos = items[tempRel.firstItem].start; 416 } 417 }*/ 418 419 // Handle overflowing position limits 420 if (dragging.init && dragging.slidee && o.elasticBounds) { 421 if (newPos > pos.end) { 422 newPos = pos.end + (newPos - pos.end) / 6; 423 } else if (newPos < pos.start) { 424 newPos = pos.start + (newPos - pos.start) / 6; 425 } 426 } else { 427 newPos = within(newPos, pos.start, pos.end); 428 } 429 430 // Update the animation object 431 animation.start = +new Date(); 432 animation.time = 0; 433 animation.from = pos.cur; 434 animation.to = newPos; 435 animation.delta = newPos - pos.cur; 436 animation.tweesing = dragging.tweese || dragging.init && !dragging.slidee; 437 animation.immediate = !animation.tweesing && (immediate || dragging.init && dragging.slidee || !o.speed); 438 439 // Reset dragging tweesing request 440 dragging.tweese = 0; 441 442 // Start animation rendering 443 if (newPos !== pos.dest) { 444 pos.dest = newPos; 445 trigger('change'); 446 if (!renderID) { 447 render(); 448 } 449 } 450 451 // Reset next cycle timeout 452 resetCycle(); 453 454 // Synchronize states 455 updateRelatives(); 456 updateButtonsState(); 457 syncPagesbar(); 458 } 459 460 /** 461 * Render animation frame. 462 * 463 * @return {Void} 464 */ 465 function render() { 466 if (!self.initialized) { 467 return; 468 } 469 470 // If first render call, wait for next animationFrame 471 if (!renderID) { 472 renderID = rAF(render); 473 if (dragging.released) { 474 trigger('moveStart'); 475 } 476 return; 477 } 478 479 // If immediate repositioning is requested, don't animate. 480 if (animation.immediate) { 481 pos.cur = animation.to; 482 } 483 // Use tweesing for animations without known end point 484 else if (animation.tweesing) { 485 animation.tweeseDelta = animation.to - pos.cur; 486 // Fuck Zeno's paradox 487 /*if (abs(animation.tweeseDelta) < 0.1) { 488 pos.cur = animation.to; 489 } else {*/ 490 pos.cur += animation.tweeseDelta * (dragging.released ? o.swingSpeed : o.syncSpeed); 491 //} 492 } 493 // Use tweening for basic animations with known end point 494 else { 495 animation.time = min(+new Date() - animation.start, o.speed); 496 pos.cur = animation.from + animation.delta * $.easing[o.easing](animation.time/o.speed, animation.time, 0, 1, o.speed); 497 } 498 499 // If there is nothing more to render break the rendering loop, otherwise request new animation frame. 500 if (animation.to === pos.cur) { 501 pos.cur = animation.to; 502 dragging.tweese = renderID = 0; 503 } else { 504 renderID = rAF(render); 505 } 506 507 trigger('move'); 508 509 // Update SLIDEE position 510 if (!parallax) { 511 if (transform) { 512 $slidee[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + (-pos.cur) + 'px)'; 513 } else { 514 $slidee[0].style[o.horizontal ? 'left' : 'top'] = -round(pos.cur) + 'px'; 515 } 516 } 517 518 // When animation reached the end, and dragging is not active, trigger moveEnd 519 if (!renderID && dragging.released) { 520 trigger('moveEnd'); 521 } 522 523 syncScrollbar(); 524 } 525 526 /** 527 * Synchronizes scrollbar with the SLIDEE. 528 * 529 * @return {Void} 530 */ 531 function syncScrollbar() { 532 if ($handle.length) { 533 hPos.cur = pos.start === pos.end ? 0 : (((dragging.init && !dragging.slidee) ? pos.dest : pos.cur) - pos.start) / (pos.end - pos.start) * hPos.end; 534 hPos.cur = within(round(hPos.cur), hPos.start, hPos.end); 535 if (last.hPos !== hPos.cur) { 536 last.hPos = hPos.cur; 537 if (transform) { 538 $handle[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + hPos.cur + 'px)'; 539 } else { 540 $handle[0].style[o.horizontal ? 'left' : 'top'] = hPos.cur + 'px'; 541 } 542 } 543 } 544 } 545 546 /** 547 * Synchronizes pagesbar with SLIDEE. 548 * 549 * @return {Void} 550 */ 551 function syncPagesbar() { 552 if ($pages[0] && last.page !== rel.activePage) { 553 last.page = rel.activePage; 554 $pages.removeClass(o.activeClass).eq(rel.activePage).addClass(o.activeClass); 555 trigger('activePage', last.page); 556 } 557 } 558 559 /** 560 * Returns the position object. 561 * 562 * @param {Mixed} item 563 * 564 * @return {Object} 565 */ 566 self.getPos = function (item) { 567 if (itemNav) { 568 var index = getIndex(item); 569 return index !== -1 ? items[index] : false;
570 } else { 571 var $item = $slidee.find(item).eq(0); 572 573 if ($item[0]) { 574 var offset = o.horizontal ? $item.offset().left - $slidee.offset().left : $item.offset().top - $slidee.offset().top; 575 var size = $item[o.horizontal ? 'outerWidth' : 'outerHeight'](); 576 577 return { 578 start: offset, 579 center: offset - frameSize / 2 + size / 2, 580 end: offset - frameSize + size, 581 size: size 582 }; 583 } else { 584 return false; 585 } 586 } 587 }; 588 589 /** 590 * Continuous move in a specified direction. 591 * 592 * @param {Bool} forward True for forward movement, otherwise it'll go backwards. 593 * @param {Int} speed Movement speed in pixels per frame. Overrides options.moveBy value. 594 * 595 * @return {Void} 596 */ 597 self.moveBy = function (speed) { 598 move.speed = speed; 599 // If already initiated, or there is nowhere to move, abort 600 /*if (dragging.init || !move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) { 601 return; 602 }*/ 603 // Initiate move object 604 move.lastTime = +new Date();
605 move.startPos = pos.cur; 606 // Set dragging as initiated 607 continuousInit('button'); 608 dragging.init = 1; 609 // Start movement 610 trigger('moveStart'); 611 cAF(continuousID); 612 moveLoop(); 613 }; 614 615 /** 616 * Continuous movement loop. 617 * 618 * @return {Void} 619 */ 620 function moveLoop() { 621 // If there is nowhere to move anymore, stop 622 if (!move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) { 623 self.stop(); 624 } 625 // Request new move loop if it hasn't been stopped 626 continuousID = dragging.init ? rAF(moveLoop) : 0; 627 // Update move object 628 move.now = +new Date(); 629 move.pos = pos.cur + (move.now - move.lastTime) / 1000 * move.speed; 630 // Slide 631 slideTo(dragging.init ? move.pos : round(move.pos)); 632 // Normally, this is triggered in render(), but if there 633 // is nothing to render, we have to do it manually here. 634 if (!dragging.init && pos.cur === pos.dest) { 635 trigger('moveEnd'); 636 } 637 // Update times for future iteration 638 move.lastTime = move.now; 639 } 640 641 /** 642 * Stops continuous movement. 643 * 644 * @return {Void} 645 */ 646 self.stop = function () { 647 if (dragging.source === 'button') { 648 dragging.init = 0; 649 dragging.released = 1; 650 } 651 }; 652 653 /** 654 * Activate previous item. 655 * 656 * @return {Void} 657 */ 658 self.prev = function () { 659 self.activate(rel.activeItem == null ? 0 : rel.activeItem - 1); 660 }; 661 662 /** 663 * Activate next item. 664 * 665 * @return {Void} 666 */ 667 self.next = function () { 668 self.activate(rel.activeItem == null ? 0 : rel.activeItem + 1); 669 }; 670 671 /** 672 * Activate previous page. 673 * 674 * @return {Void} 675 */ 676 self.prevPage = function () { 677 self.activatePage(rel.activePage - 1); 678 }; 679 680 /** 681 * Activate next page. 682 * 683 * @return {Void} 684 */ 685 self.nextPage = function () {
686 self.activatePage(rel.activePage + 1); 687 }; 688 689 /** 690 * Slide SLIDEE by amount of pixels. 691 * 692 * @param {Int} delta Pixels/Items. Positive means forward, negative means backward. 693 * @param {Bool} immediate Reposition immediately without an animation. 694 * 695 * @return {Void} 696 */ 697 self.slideBy = function (delta, immediate) { 698 if (!delta) { 699 return; 700 } 701 if (itemNav) { 702 self[centeredNav ? 'toCenter' : 'toStart']( 703 within((centeredNav ? rel.centerItem : rel.firstItem) + o.scrollBy * delta, 0, items.length) 704 ); 705 } else { 706 slideTo(pos.dest + delta, immediate); 707 } 708 }; 709 710 /** 711 * Animate SLIDEE to a specific position. 712 * 713 * @param {Int} pos New position. 714 * @param {Bool} immediate Reposition immediately without an animation. 715 * 716 * @return {Void} 717 */ 718 self.slideTo = function (pos, immediate) { 719 slideTo(pos, immediate); 720 }; 721 722 /** 723 * Core method for handling `toLocation` methods. 724 * 725 * @param {String} location 726 * @param {Mixed} item 727 * @param {Bool} immediate 728 * 729 * @return {Void} 730 */ 731 function to(location, item, immediate) { 732 // Optional arguments logic 733 if (type(item) === 'boolean') { 734 immediate = item; 735 item = undefined; 736 } 737 738 if (item === undefined) { 739 slideTo(pos[location], immediate); 740 } else { 741 // You can't align items to sides of the frame 742 // when centered navigation type is enabled 743 if (centeredNav && location !== 'center') { 744 return; 745 } 746 747 var itemPos = self.getPos(item); 748 if (itemPos) { 749 slideTo(itemPos[location], immediate, !centeredNav); 750 } 751 } 752 } 753 754 /** 755 * Animate element or the whole SLIDEE to the start of the frame. 756 * 757 * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. 758 * @param {Bool} immediate Reposition immediately without an animation. 759 * 760 * @return {Void} 761 */ 762 self.toStart = function (item, immediate) { 763 to('start', item, immediate); 764 }; 765 766 /** 767 * Animate element or the whole SLIDEE to the end of the frame. 768 * 769 * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. 770 * @param {Bool} immediate Reposition immediately without an animation. 771 * 772 * @return {Void} 773 */ 774 self.toEnd = function (item, immediate) { 775 to('end', item, immediate); 776 }; 777 778 /** 779 * Animate element or the whole SLIDEE to the center of the frame. 780 * 781 * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. 782 * @param {Bool} immediate Reposition immediately without an animation. 783 * 784 * @return {Void} 785 */ 786 self.toCenter = function (item, immediate) { 787 to('center', item, immediate); 788 }; 789 790 /** 791 * Get the index of an item in SLIDEE. 792 * 793 * @param {Mixed} item Item DOM element. 794 * 795 * @return {Int} Item index, or -1 if not found. 796 */ 797 function getIndex(item) { 798 return item != null ? 799 isNumber(item) ? 800 item >= 0 && item < items.length ? item : -1 : 801 $items.index(item) : 802 -1; 803 } 804 // Expose getIndex without lowering the compressibility of it, 805 // as it is used quite often throughout Sly. 806 self.getIndex = getIndex; 807 808 /** 809 * Get index of an item in SLIDEE based on a variety of input types. 810 * 811 * @param {Mixed} item DOM element, positive or negative integer. 812 * 813 * @return {Int} Item index, or -1 if not found. 814 */ 815 function getRelativeIndex(item) { 816 return getIndex(isNumber(item) && item < 0 ? item + items.length : item); 817 } 818 819 /** 820 * Activates an item. 821 * 822 * @param {Mixed} item Item DOM element, or index starting at 0. 823 * 824 * @return {Mixed} Activated item index or false on fail. 825 */ 826 function activate(item, force) { 827 var index = getIndex(item); 828 829 if (!itemNav || index < 0) { 830 return false; 831 } 832 833 // Update classes, last active index, and trigger active event only when there 834 // has been a change. Otherwise just return the current active index. 835 if (last.active !== index || force) { 836 // Update classes 837 $items.eq(rel.activeItem).removeClass(o.activeClass); 838 $items.eq(index).addClass(o.activeClass); 839 840 last.active = rel.activeItem = index; 841 842 updateButtonsState(); 843 trigger('active', index); 844 } 845 846 return index; 847 } 848 849 /** 850 * Activates an item and helps with further navigation when o.smart is enabled. 851 * 852 * @param {Mixed} item Item DOM element, or index starting at 0. 853 * @param {Bool} immediate Whether to reposition immediately in smart navigation. 854 * 855 * @return {Void} 856 */ 857 self.activate = function (item, immediate) { 858 var index = activate(item); 859 860 // Smart navigation 861 if (o.smart && index !== false) { 862 // When centeredNav is enabled, center the element. 863 // Otherwise, determine where to position the element based on its current position. 864 // If the element is currently on the far end side of the frame, assume that user is 865 // moving forward and animate it to the start of the visible frame, and vice versa. 866 if (centeredNav) { 867 self.toCenter(index, immediate); 868 } else if (index >= rel.lastItem) { 869 self.toStart(index, immediate); 870 } else if (index <= rel.firstItem) { 871 self.toEnd(index, immediate); 872 } else { 873 resetCycle(); 874 } 875 } 876 }; 877 878 /** 879 * Activates a page. 880 * 881 * @param {Int} index Page index, starting from 0. 882 * @param {Bool} immediate Whether to reposition immediately without animation. 883 * 884 * @return {Void} 885 */ 886 self.activatePage = function (index, immediate) { 887 if (isNumber(index)) { 888 slideTo(pages[within(index, 0, pages.length - 1)], immediate); 889 } 890 }; 891 892 /** 893 * Return relative positions of items based on their visibility within FRAME. 894 * 895 * @param {Int} slideePos Position of SLIDEE. 896 * 897 * @return {Void} 898 */ 899 function getRelatives(slideePos) {
900 slideePos = within(isNumber(slideePos) ? slideePos : pos.dest, pos.start, pos.end); 901 902 var relatives = {}; 903 var centerOffset = forceCenteredNav ? 0 : frameSize / 2; 904 905 // Determine active page 906 if (!parallax) { 907 for (var p = 0, pl = pages.length; p < pl; p++) { 908 if (slideePos >= pos.end || p === pages.length - 1) { 909 relatives.activePage = pages.length - 1; 910 break; 911 } 912 913 if (slideePos <= pages[p] + centerOffset) { 914 relatives.activePage = p; 915 break; 916 } 917 } 918 } 919 920 // Relative item indexes 921 if (itemNav) { 922 var first = false; 923 var last = false; 924 var center = false; 925 926 // From start 927 for (var i = 0, il = items.length; i < il; i++) { 928 // First item 929 if (first === false && slideePos <= items[i].start + items[i].half) { 930 first = i; 931 } 932 933 // Center item 934 if (center === false && slideePos <= items[i].center + items[i].half) { 935 center = i; 936 } 937 938 // Last item 939 if (i === il - 1 || slideePos <= items[i].end + items[i].half) { 940 last = i; 941 break; 942 } 943 } 944 945 // Safe assignment, just to be sure the false won't be returned 946 relatives.firstItem = isNumber(first) ? first : 0; 947 relatives.centerItem = isNumber(center) ? center : relatives.firstItem; 948 relatives.lastItem = isNumber(last) ? last : relatives.centerItem; 949 } 950 951 return relatives; 952 } 953 954 /** 955 * Update object with relative positions. 956 * 957 * @param {Int} newPos 958 * 959 * @return {Void} 960 */ 961 function updateRelatives(newPos) { 962 $.extend(rel, getRelatives(newPos)); 963 } 964 965 /** 966 * Disable navigation buttons when needed. 967 * 968 * Adds disabledClass, and when the button is <button> or <input>, activates :disabled state. 969 * 970 * @return {Void} 971 */ 972 function updateButtonsState() { 973 var isStart = pos.dest <= pos.start; 974 var isEnd = pos.dest >= pos.end; 975 var slideePosState = (isStart ? 1 : 0) | (isEnd ? 2 : 0); 976 977 // Update paging buttons only if there has been a change in SLIDEE position 978 if (last.slideePosState !== slideePosState) { 979 last.slideePosState = slideePosState; 980 981 if ($prevPageButton.is('button,input')) { 982 $prevPageButton.prop('disabled', isStart); 983 } 984 985 if ($nextPageButton.is('button,input')) { 986 $nextPageButton.prop('disabled', isEnd); 987 } 988 989 $prevPageButton.add($backwardButton)[isStart ? 'addClass' : 'removeClass'](o.disabledClass); 990 $nextPageButton.add($forwardButton)[isEnd ? 'addClass' : 'removeClass'](o.disabledClass); 991 } 992 993 // Forward & Backward buttons need a separate state caching because we cannot "property disable" 994 // them while they are being used, as disabled buttons stop emitting mouse events. 995 if (last.fwdbwdState !== slideePosState && dragging.released) { 996 last.fwdbwdState = slideePosState; 997 998 if ($backwardButton.is('button,input')) { 999 $backwardButton.prop('disabled', isStart); 1000 } 1001 1002 if ($forwardButton.is('button,input')) { 1003 $forwardButton.prop('disabled', isEnd); 1004 } 1005 } 1006 1007 // Item navigation 1008 if (itemNav && rel.activeItem != null) { 1009 var isFirst = rel.activeItem === 0; 1010 var isLast = rel.activeItem >= items.length - 1; 1011 var itemsButtonState = (isFirst ? 1 : 0) | (isLast ? 2 : 0); 1012 1013 if (last.itemsButtonState !== itemsButtonState) { 1014 last.itemsButtonState = itemsButtonState; 1015 1016 if ($prevButton.is('button,input')) { 1017 $prevButton.prop('disabled', isFirst); 1018 } 1019 1020 if ($nextButton.is('button,input')) { 1021 $nextButton.prop('disabled', isLast); 1022 } 1023 1024 $prevButton[isFirst ? 'addClass' : 'removeClass'](o.disabledClass); 1025 $nextButton[isLast ? 'addClass' : 'removeClass'](o.disabledClass); 1026 } 1027 } 1028 } 1029 1030 /** 1031 * Resume cycling. 1032 * 1033 * @param {Int} priority Resume pause with priority lower or equal than this. Used internally for pauseOnHover. 1034 * 1035 * @return {Void} 1036 */ 1037 self.resume = function (priority) { 1038 if (!o.cycleBy || !o.cycleInterval || o.cycleBy === 'items' && (!items[0] || rel.activeItem == null) || priority < self.isPaused) { 1039 return; 1040 } 1041 1042 self.isPaused = 0; 1043 1044 if (cycleID) { 1045 cycleID = clearTimeout(cycleID); 1046 } else { 1047 trigger('resume'); 1048 } 1049 1050 cycleID = setTimeout(function () { 1051 trigger('cycle'); 1052 switch (o.cycleBy) { 1053 case 'items': 1054 self.activate(rel.activeItem >= items.length - 1 ? 0 : rel.activeItem + 1); 1055 break; 1056 1057 case 'pages': 1058 self.activatePage(rel.activePage >
1058= pages.length - 1 ? 0 : rel.activePage + 1); 1059 break; 1060 } 1061 }, o.cycleInterval); 1062 }; 1063 1064 /** 1065 * Pause cycling. 1066 * 1067 * @param {Int} priority Pause priority. 100 is default. Used internally for pauseOnHover. 1068 * 1069 * @return {Void} 1070 */ 1071 self.pause = function (priority) { 1072 if (priority < self.isPaused) { 1073 return; 1074 } 1075 1076 self.isPaused = priority || 100; 1077 1078 if (cycleID) { 1079 cycleID = clearTimeout(cycleID); 1080 trigger('pause'); 1081 } 1082 }; 1083 1084 /** 1085 * Toggle cycling. 1086 * 1087 * @return {Void} 1088 */ 1089 self.toggle = function () { 1090 self[cycleID ? 'pause' : 'resume'](); 1091 }; 1092 1093 /** 1094 * Updates a signle or multiple option values. 1095 * 1096 * @param {Mixed} name Name of the option that should be updated, or object that will extend the options. 1097 * @param {Mixed} value New option value. 1098 * 1099 * @return {Void} 1100 */ 1101 self.set = function (name, value) { 1102 if ($.isPlainObject(name)) { 1103 $.extend(o, name); 1104 } else if (o.hasOwnProperty(name)) { 1105 o[name] = value; 1106 } 1107 }; 1108 1109 /** 1110 * Add one or multiple items to the SLIDEE end, or a specified position index. 1111 * 1112 * @param {Mixed} element Node element, or HTML string. 1113 * @param {Int} index Index of a new item position. By default item is appended at the end. 1114 * 1115 * @return {Void} 1116 */ 1117 self.add = function (element, index) { 1118 var $element = $(element); 1119 1120 if (itemNav) { 1121 // Insert the element(s) 1122 if (index == null || !items[0] || index >= items.length) { 1123 $element.appendTo($slidee); 1124 } else if (items.length) { 1125 $element.insertBefore(items[index].el); 1126 } 1127 1128 // Adjust the activeItem index 1129 if (rel.activeItem != null && index <= rel.activeItem) { 1130 last.active = rel.activeItem += $element.length; 1131 } 1132 } else { 1133 $slidee.append($element); 1134 } 1135 1136 // Reload 1137 load(); 1138 }; 1139 1140 /** 1141 * Remove an item from SLIDEE. 1142 * 1143 * @param {Mixed} element Item index, or DOM element. 1144 * @param {Int} index Index of a new item position. By default item is appended at the end. 1145 * 1146 * @return {Void} 1147 */ 1148 self.remove = function (element) { 1149 if (itemNav) { 1150 var index = getRelativeIndex(element); 1151 1152 if (index > -1) { 1153 // Remove the element 1154 $items.eq(index).remove(); 1155 1156 // If the current item is being removed, activate new one after reload 1157 var reactivate = index === rel.activeItem; 1158 1159 // Adjust the activeItem index 1160 if (rel.activeItem != null && index < rel.activeItem) { 1161 last.active = --rel.activeItem; 1162 } 1163 1164 // Reload 1165 load(); 1166 1167 // Activate new item at the removed position 1168 if (reactivate) { 1169 last.active = null; 1170 self.activate(rel.activeItem); 1171 } 1172 } 1173 } else { 1174 $(element).remove(); 1175 load(); 1176 } 1177 }; 1178 1179 /** 1180 * Helps re-arranging items. 1181 * 1182 * @param {Mixed} item Item DOM element, or index starting at 0. Use negative numbers to select items from the end. 1183 * @param {Mixed} position Item insertion anchor. Accepts same input types as item argument. 1184 * @param {Bool} after Insert after instead of before the anchor. 1185 * 1186 * @return {Void} 1187 */ 1188 function moveItem(item, position, after) { 1189 item = getRelativeIndex(item); 1190 position = getRelativeIndex(position); 1191 1192 // Move only if there is an actual change requested 1193 if (item > -1 && position > -1 && item !== position && (!after || position !== item - 1) && (after || position !== item + 1)) { 1194 $items.eq(item)[after ? 'insertAfter' : 'insertBefore'](items[position].el); 1195 1196 var shiftStart = item < position ? item : (after ? position : position - 1); 1197 var shiftEnd = item > position ? item : (after ? position + 1 : position); 1198 var shiftsUp = item > position; 1199 1200 // Update activeItem index 1201 if (rel.activeItem != null) { 1202 if (item === rel.activeItem) { 1203 last.active = rel.activeItem = after ? (shiftsUp ? position + 1 : position) : (shiftsUp ? position : position - 1); 1204 } else if (rel.activeItem > shiftStart && rel.activeItem < shiftEnd) { 1205 last.active = rel.activeItem += shiftsUp ? 1 : -1; 1206 } 1207 } 1208 1209 // Reload 1210 load(); 1211 } 1212 } 1213 1214 /** 1215 * Move item after the target anchor. 1216 * 1217 * @param {Mixed} item Item to be moved. Can be DOM element or item index. 1218 * @param {Mixed} position Target position anchor. Can be DOM element or item index. 1219 * 1220 * @return {Void} 1221 */ 1222 self.moveAfter = function (item, position) { 1223 moveItem(item, position, 1); 1224 }; 1225 1226 /** 1227 * Move item before the target anchor. 1228 * 1229 * @param {Mixed} item Item to be moved. Can be DOM element or item index. 1230 * @param {Mixed} position Target position anchor. Can be DOM element or item index. 1231 * 1232 * @return {Void} 1233 */ 1234 self.moveBefore = function (item, position) { 1235 moveItem(item, position); 1236 }; 1237 1238 /** 1239 * Registers callbacks. 1240 * 1241 * @param {Mixed}
1241 name Event name, or callbacks map. 1242 * @param {Mixed} fn Callback, or an array of callback functions. 1243 * 1244 * @return {Void} 1245 */ 1246 self.on = function (name, fn) { 1247 // Callbacks map 1248 if (type(name) === 'object') { 1249 for (var key in name) { 1250 if (name.hasOwnProperty(key)) { 1251 self.on(key, name[key]); 1252 } 1253 } 1254 // Callback 1255 } else if (type(fn) === 'function') { 1256 var names = name.split(' '); 1257 for (var n = 0, nl = names.length; n < nl; n++) { 1258 callbacks[names[n]] = callbacks[names[n]] || []; 1259 if (callbackIndex(names[n], fn) === -1) { 1260 callbacks[names[n]].push(fn); 1261 } 1262 } 1263 // Callbacks array 1264 } else if (type(fn) === 'array') { 1265 for (var f = 0, fl = fn.length; f < fl; f++) { 1266 self.on(name, fn[f]); 1267 } 1268 } 1269 }; 1270 1271 /** 1272 * Registers callbacks to be executed only once. 1273 * 1274 * @param {Mixed} name Event name, or callbacks map. 1275 * @param {Mixed} fn Callback, or an array of callback functions. 1276 * 1277 * @return {Void} 1278 */ 1279 self.one = function (name, fn) { 1280 function proxy() { 1281 fn.apply(self, arguments); 1282 self.off(name, proxy); 1283 } 1284 self.on(name, proxy); 1285 }; 1286 1287 /** 1288 * Remove one or all callbacks. 1289 * 1290 * @param {String} name Event name. 1291 * @param {Mixed} fn Callback, or an array of callback functions. Omit to remove all callbacks. 1292 * 1293 * @return {Void} 1294 */ 1295 self.off = function (name, fn) { 1296 if (fn instanceof Array) { 1297 for (var f = 0, fl = fn.length; f < fl; f++) { 1298 self.off(name, fn[f]); 1299 } 1300 } else { 1301 var names = name.split(' '); 1302 for (var n = 0, nl = names.length; n < nl; n++) { 1303 callbacks[names[n]] = callbacks[names[n]] || []; 1304 if (fn == null) { 1305 callbacks[names[n]].length = 0; 1306 } else { 1307 var index = callbackIndex(names[n], fn); 1308 if (index !== -1) { 1309 callbacks[names[n]].splice(index, 1); 1310 } 1311 } 1312 } 1313 } 1314 }; 1315 1316 /** 1317 * Returns callback array index. 1318 * 1319 * @param {String} name Event name. 1320 * @param {Function} fn Function 1321 * 1322 * @return {Int} Callback array index, or -1 if isn't registered. 1323 */ 1324 function callbackIndex(name, fn) { 1325 for (var i = 0, l = callbacks[name].length; i < l; i++) { 1326 if (callbacks[name][i] === fn) { 1327 return i; 1328 } 1329 } 1330 return -1; 1331 } 1332 1333 /** 1334 * Reset next cycle timeout. 1335 * 1336 * @return {Void} 1337 */ 1338 function resetCycle() { 1339 if (dragging.released && !self.isPaused) { 1340 self.resume(); 1341 } 1342 } 1343 1344 /** 1345 * Calculate SLIDEE representation of handle position. 1346 * 1347 * @param {Int} handlePos 1348 * 1349 * @return {Int} 1350 */ 1351 function handleToSlidee(handlePos) { 1352 return round(within(handlePos, hPos.start, hPos.end) / hPos.end * (pos.end - pos.start)) + pos.start; 1353 } 1354 1355 /** 1356 * Keeps track of a dragging delta history. 1357 * 1358 * @return {Void} 1359 */ 1360 function draggingHistoryTick() { 1361 // Looking at this, I know what you're thinking :) But as we need only 4 history states, doing it this way 1362 // as opposed to a proper loop is ~25 bytes smaller (when minified with GCC), a lot faster, and doesn't 1363 // generate garbage. The loop version would create 2 new variables on every tick. Unexaptable! 1364 dragging.history[0] = dragging.history[1]; 1365 dragging.history[1] = dragging.history[2]; 1366 dragging.history[2] = dragging.history[3]; 1367 dragging.history[3] = dragging.delta; 1368 } 1369 1370 /** 1371 * Initialize continuous movement. 1372 * 1373 * @return {Void} 1374 */ 1375 function continuousInit(source) { 1376 dragging.released = 0; 1377 dragging.source = source; 1378 dragging.slidee = source === 'slidee'; 1379 } 1380 1381 /** 1382 * Dragging initiator. 1383 * 1384 * @param {Event} event 1385 * 1386 * @return {Void} 1387 */ 1388 function dragInit(event) { 1389 var isTouch = event.type === 'touchstart'; 1390 var source = event.data.source; 1391 var isSlidee = source === 'slidee'; 1392 1393 // Ignore when already in progress, or interactive element in non-touch navivagion 1394 if (dragging.init || !isTouch && isInteractive(event.target)) { 1395 return; 1396 } 1397 1398 // Handle dragging conditions 1399 if (source === 'handle' && (!o.dragHandle || hPos.start === hPos.end)) { 1400 return; 1401 } 1402 1403 // SLIDEE dragging conditions 1404 if (isSlidee && !(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) { 1405 return; 1406 } 1407 1408 if (!isTouch) { 1409 // prevents native image dragging in Firefox 1410 stopDefault(event); 1411 } 1412 1413 // Reset dragging object
1414 continuousInit(source); 1415 1416 // Properties used in dragHandler 1417 dragging.init = 0; 1418 dragging.$source = $(event.target); 1419 dragging.touch = isTouch; 1420 dragging.pointer = isTouch ? event.originalEvent.touches[0] : event; 1421 dragging.initX = dragging.pointer.pageX; 1422 dragging.initY = dragging.pointer.pageY; 1423 dragging.initPos = isSlidee ? pos.cur : hPos.cur; 1424 dragging.start = +new Date(); 1425 dragging.time = 0; 1426 dragging.path = 0; 1427 dragging.delta = 0; 1428 dragging.locked = 0; 1429 dragging.history = [0, 0, 0, 0]; 1430 dragging.pathToLock = isSlidee ? isTouch ? 30 : 10 : 0; 1431 1432 // Bind dragging events 1433 $doc.on(isTouch ? dragTouchEvents : dragMouseEvents, dragHandler); 1434 1435 // Pause ongoing cycle 1436 self.pause(1); 1437 1438 // Add dragging class 1439 (isSlidee ? $slidee : $handle).addClass(o.draggedClass); 1440 1441 // Trigger moveStart event 1442 trigger('moveStart'); 1443 1444 // Keep track of a dragging path history. This is later used in the 1445 // dragging release swing calculation when dragging SLIDEE. 1446 if (isSlidee) { 1447 historyID = setInterval(draggingHistoryTick, 10); 1448 } 1449 } 1450 1451 /** 1452 * Handler for dragging scrollbar handle or SLIDEE. 1453 * 1454 * @param {Event} event 1455 * 1456 * @return {Void} 1457 */ 1458 function dragHandler(event) { 1459 dragging.released = event.type === 'mouseup' || event.type === 'touchend'; 1460 dragging.pointer = dragging.touch ? event.originalEvent[dragging.released ? 'changedTouches' : 'touches'][0] : event; 1461 dragging.pathX = dragging.pointer.pageX - dragging.initX; 1462 dragging.pathY = dragging.pointer.pageY - dragging.initY; 1463 dragging.path = sqrt(pow(dragging.pathX, 2) + pow(dragging.pathY, 2)); 1464 dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY; 1465 1466 if (!dragging.released && dragging.path < 1) return; 1467 1468 // We haven't decided whether this is a drag or not... 1469 if (!dragging.init) { 1470 // If the drag path was very short, maybe it's not a drag? 1471 if (dragging.path < o.dragThreshold) { 1472 // If the pointer was released, the path will not become longer and it's 1473 // definitely not a drag. If not released yet, decide on next iteration 1474 return dragging.released ? dragEnd() : undefined; 1475 } 1476 else { 1477 // If dragging path is sufficiently long we can confidently start a drag 1478 // if drag is in different direction than scroll, ignore it 1479 if (o.horizontal ? abs(dragging.pathX) > abs(dragging.pathY) : abs(dragging.pathX) < abs(dragging.pathY)) { 1480 dragging.init = 1; 1481 } else { 1482 return dragEnd(); 1483 } 1484 } 1485 } 1486 1487 stopDefault(event); 1488 1489 // Disable click on a source element, as it is unwelcome when dragging 1490 if (!dragging.locked && dragging.path > dragging.pathToLock && dragging.slidee) { 1491 dragging.locked = 1; 1492 dragging.$source.on(clickEvent, disableOneEvent); 1493 } 1494 1495 // Cancel dragging on release 1496 if (dragging.released) { 1497 dragEnd(); 1498 1499 // Adjust path with a swing on mouse release 1500 if (o.releaseSwing && dragging.slidee) { 1501 dragging.swing = (dragging.delta - dragging.history[0]) / 40 * 300; 1502 dragging.delta += dragging.swing; 1503 dragging.tweese = abs(dragging.swing) > 10; 1504 } 1505 } 1506 1507 slideTo(dragging.slidee ? round(dragging.initPos - dragging.delta) : handleToSlidee(dragging.initPos + dragging.delta)); 1508 } 1509 1510 /** 1511 * Stops dragging and cleans up after it. 1512 * 1513 * @return {Void} 1514 */ 1515 function dragEnd() { 1516 clearInterval(historyID); 1517 dragging.released = true; 1518 $doc.off(dragging.touch ? dragTouchEvents : dragMouseEvents, dragHandler); 1519 (dragging.slidee ? $slidee : $handle).removeClass(o.draggedClass); 1520 1521 // Make sure that disableOneEvent is not active in next tick. 1522 setTimeout(function () { 1523 dragging.$source.off(clickEvent, disableOneEvent); 1524 }); 1525 1526 // Normally, this is triggered in render(), but if there 1527 // is nothing to render, we have to do it manually here. 1528 if (pos.cur === pos.dest && dragging.init) { 1529 trigger('moveEnd'); 1530 } 1531 1532 // Resume ongoing cycle 1533 self.resume(1); 1534 1535 dragging.init = 0; 1536 } 1537 1538 /** 1539 * Check whether element is interactive. 1540 * 1541 * @return {Boolean} 1542 */ 1543 function isInteractive(element) { 1544 return ~$.inArray(element.nodeName, interactiveElements) || $(element).is(o.interactive); 1545 } 1546 1547 /** 1548 * Continuous movement cleanup on mouseup. 1549 * 1550 * @return {Void} 1551 */ 1552 function movementReleaseHandler() { 1553 self.stop(); 1554 $doc.off('mouseup', movementReleaseHandler); 1555 } 1556 1557 /** 1558 * Buttons navigation handler. 1559 * 1560 * @param {Event} event 1561 * 1562 * @return {Void} 1563 */ 1564 function buttonsHandler(event) { 1565 /*jshint validthis:true */ 1566 stopDefault(event); 1567 switch (this) { 1568 case $forwardButton[0]: 1569 case $backwardButton[0]: 1570 self.moveBy($forwardButton.is(this) ? o.moveBy : -o.moveBy); 1571 $doc.on('mouseup', movementReleaseHandler); 1572 break; 1573
1574 case $prevButton[0]: 1575 self.prev(); 1576 break; 1577 1578 case $nextButton[0]: 1579 self.next(); 1580 break; 1581 1582 case $prevPageButton[0]: 1583 self.prevPage(); 1584 break; 1585 1586 case $nextPageButton[0]: 1587 self.nextPage(); 1588 break; 1589 } 1590 } 1591 1592 /** 1593 * Mouse wheel delta normalization. 1594 * 1595 * @param {Event} event 1596 * 1597 * @return {Int} 1598 */ 1599 function normalizeWheelDelta(event) { 1600 // wheelDelta needed only for IE8- 1601 scrolling.curDelta = ((o.horizontal ? event.deltaY || event.deltaX : event.deltaY) || -event.wheelDelta); 1602 scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100; 1603 if (!itemNav) { 1604 return scrolling.curDelta; 1605 } 1606 time = +new Date(); 1607 if (scrolling.last < time - scrolling.resetTime) { 1608 scrolling.delta = 0; 1609 } 1610 scrolling.last = time; 1611 scrolling.delta += scrolling.curDelta; 1612 if (abs(scrolling.delta) < 1) { 1613 scrolling.finalDelta = 0; 1614 } else { 1615 scrolling.finalDelta = round(scrolling.delta / 1); 1616 scrolling.delta %= 1; 1617 } 1618 return scrolling.finalDelta; 1619 } 1620 1621 /** 1622 * Mouse scrolling handler. 1623 * 1624 * @param {Event} event 1625 * 1626 * @return {Void} 1627 */ 1628 function scrollHandler(event) { 1629 // Mark event as originating in a Sly instance 1630 event.originalEvent[namespace] = self; 1631 // Don't hijack global scrolling 1632 var time = +new Date(); 1633 if (lastGlobalWheel + o.scrollHijack > time && $scrollSource[0] !== document && $scrollSource[0] !== window) { 1634 lastGlobalWheel = time; 1635 return; 1636 } 1637 // Ignore if there is no scrolling to be done 1638 if (!o.scrollBy || pos.start === pos.end) { 1639 return; 1640 } 1641 var delta = normalizeWheelDelta(event.originalEvent); 1642 // Trap scrolling only when necessary and/or requested 1643 if (o.scrollTrap || delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) { 1644 stopDefault(event, 1); 1645 } 1646 self.slideBy(o.scrollBy * delta); 1647 } 1648 1649 /** 1650 * Scrollbar click handler. 1651 * 1652 * @param {Event} event 1653 * 1654 * @return {Void} 1655 */ 1656 function scrollbarHandler(event) { 1657 // Only clicks on scroll bar. Ignore the handle. 1658 if (o.clickBar && event.target === $sb[0]) { 1659 stopDefault(event); 1660 // Calculate new handle position and sync SLIDEE to it 1661 slideTo(handleToSlidee((o.horizontal ? event.pageX - $sb.offset().left : event.pageY - $sb.offset().top) - handleSize / 2)); 1662 } 1663 } 1664 1665 /** 1666 * Keyboard input handler. 1667 * 1668 * @param {Event} event 1669 * 1670 * @return {Void} 1671 */ 1672 function keyboardHandler(event) { 1673 if (!o.keyboardNavBy) { 1674 return; 1675 } 1676 1677 switch (event.which) { 1678 // Left or Up 1679 case o.horizontal ? 37 : 38: 1680 stopDefault(event); 1681 self[o.keyboardNavBy === 'pages' ? 'prevPage' : 'prev'](); 1682 break; 1683 1684 // Right or Down 1685 case o.horizontal ? 39 : 40: 1686 stopDefault(event); 1687 self[o.keyboardNavBy === 'pages' ? 'nextPage' : 'next'](); 1688 break; 1689 } 1690 } 1691 1692 /** 1693 * Click on item activation handler. 1694 * 1695 * @param {Event} event 1696 * 1697 * @return {Void} 1698 */ 1699 function activateHandler(event) { 1700 /*jshint validthis:true */ 1701 1702 // Ignore clicks on interactive elements. 1703 if (isInteractive(this)) { 1704 event.originalEvent[namespace + 'ignore'] = true; 1705 return; 1706 } 1707 1708 // Ignore events that: 1709 // - are not originating from direct SLIDEE children 1710 // - originated from interactive elements 1711 if (this.parentNode !== $slidee[0] || event.originalEvent[namespace + 'ignore']) return; 1712 1713 self.activate(this); 1714 } 1715 1716 /** 1717 * Click on page button handler. 1718 * 1719 * @param {Event} event 1720 * 1721 * @return {Void} 1722 */ 1723 function activatePageHandler() { 1724 /*jshint validthis:true */ 1725 // Accept only events from direct pages bar children. 1726 if (this.parentNode === $pb[0]) { 1727 self.activatePage($pages.index(this)); 1728 } 1729 } 1730 1731 /** 1732 * Pause on hover handler. 1733 * 1734 * @param {Event} event 1735 * 1736 * @return {Void} 1737 */ 1738 function pauseOnHoverHandler(event) { 1739 if (o.pauseOnHover) { 1740 self[event.type === 'mouseenter' ? 'pause' : 'resume'](2); 1741 } 1742 } 1743 1744 /** 1745 * Trigger callbacks for event. 1746 * 1747 * @param {String} name Event name. 1748 * @param {Mixed} argX Arguments passed to callbacks. 1749 * 1750 * @return {Void} 1751 */ 1752 function trigger(name, arg1) { 1753 if (callbacks[name]) { 1754 l = callbacks[name].length; 1755 // Callbacks will be stored and executed from a temporary array to not
1756 // break the execution queue when one of the callbacks unbinds itself. 1757 tmpArray.length = 0; 1758 for (i = 0; i < l; i++) { 1759 tmpArray.push(callbacks[name][i]); 1760 } 1761 // Execute the callbacks 1762 for (i = 0; i < l; i++) { 1763 tmpArray[i].call(self, name, arg1); 1764 } 1765 } 1766 } 1767 1768 /** 1769 * Destroys instance and everything it created. 1770 * 1771 * @return {Void} 1772 */ 1773 self.destroy = function () { 1774 // Remove the reference to itself 1775 Sly.removeInstance(frame); 1776 1777 // Unbind all events 1778 $scrollSource 1779 .add($handle) 1780 .add($sb) 1781 .add($pb) 1782 .add($forwardButton) 1783 .add($backwardButton) 1784 .add($prevButton) 1785 .add($nextButton) 1786 .add($prevPageButton) 1787 .add($nextPageButton) 1788 .off('.' + namespace); 1789 1790 // Unbinding specifically as to not nuke out other instances 1791 $doc.off('keydown', keyboardHandler); 1792 1793 // Remove classes 1794 $prevButton 1795 .add($nextButton) 1796 .add($prevPageButton) 1797 .add($nextPageButton) 1798 .removeClass(o.disabledClass); 1799 1800 if ($items && rel.activeItem != null) { 1801 $items.eq(rel.activeItem).removeClass(o.activeClass); 1802 } 1803 1804 // Remove page items 1805 $pb.empty(); 1806 1807 if (!parallax) { 1808 // Unbind events from frame 1809 $frame.off('.' + namespace); 1810 // Restore original styles 1811 frameStyles.restore(); 1812 slideeStyles.restore(); 1813 sbStyles.restore(); 1814 handleStyles.restore(); 1815 // Remove the instance from element data storage 1816 $.removeData(frame, namespace); 1817 } 1818 1819 // Clean up collections 1820 items.length = pages.length = 0; 1821 last = {}; 1822 1823 // Reset initialized status and return the instance 1824 self.initialized = 0; 1825 return self; 1826 }; 1827 1828 /** 1829 * Initialize. 1830 * 1831 * @return {Object} 1832 */ 1833 self.init = function () { 1834 if (self.initialized) { 1835 return; 1836 } 1837 1838 // Disallow multiple instances on the same element 1839 if (Sly.getInstance(frame)) throw new Error('There is already a Sly instance on this element'); 1840 1841 // Store the reference to itself 1842 Sly.storeInstance(frame, self); 1843 1844 // Register callbacks map 1845 self.on(callbackMap); 1846 1847 // Save styles 1848 var holderProps = ['overflow', 'position']; 1849 var movableProps = ['position', 'webkitTransform', 'msTransform', 'transform', 'left', 'top', 'width', 'height']; 1850 frameStyles.save.apply(frameStyles, holderProps); 1851 sbStyles.save.apply(sbStyles, holderProps); 1852 slideeStyles.save.apply(slideeStyles, movableProps); 1853 handleStyles.save.apply(handleStyles, movableProps); 1854 1855 // Set required styles 1856 var $movables = $handle; 1857 if (!parallax) { 1858 $movables = $movables.add($slidee); 1859 $frame.css('overflow', 'hidden'); 1860 if (!transform && $frame.css('position') === 'static') { 1861 $frame.css('position', 'relative'); 1862 } 1863 } 1864 if (transform) { 1865 if (gpuAcceleration) { 1866 $movables.css(transform, gpuAcceleration); 1867 } 1868 } else { 1869 if ($sb.css('position') === 'static') { 1870 $sb.css('position', 'relative'); 1871 } 1872 $movables.css({ position: 'absolute' }); 1873 } 1874 1875 // Navigation buttons 1876 if (o.forward) { 1877 $forwardButton.on(mouseDownEvent, buttonsHandler); 1878 } 1879 if (o.backward) { 1880 $backwardButton.on(mouseDownEvent, buttonsHandler); 1881 } 1882 if (o.prev) { 1883 $prevButton.on(clickEvent, buttonsHandler); 1884 } 1885 if (o.next) { 1886 $nextButton.on(clickEvent, buttonsHandler); 1887 } 1888 if (o.prevPage) { 1889 $prevPageButton.on(clickEvent, buttonsHandler); 1890 } 1891 if (o.nextPage) { 1892 $nextPageButton.on(clickEvent, buttonsHandler); 1893 } 1894 1895 // Scrolling navigation 1896 //$scrollSource.on(wheelEvent, scrollHandler); 1897 1898 // Clicking on scrollbar navigation 1899 if ($sb[0]) { 1900 $sb.on(clickEvent, scrollbarHandler); 1901 } 1902 1903 // Click on items navigation 1904 if (itemNav && o.activateOn) { 1905 $frame.on(o.activateOn + '.' + namespace, '*', activateHandler); 1906 } 1907 1908 // Pages navigation 1909 if ($pb[0] && o.activatePageOn) { 1910 $pb.on(o.activatePageOn + '.' + namespace, '*', activatePageHandler); 1911 } 1912 1913 // Dragging navigation 1914 $dragSource.on(dragInitEvents, { source: 'slidee' }, dragInit); 1915 1916 // Scrollbar dragging navigation 1917 if ($handle) { 1918 $handle.on(dragInitEvents, { source: 'handle' }, dragInit); 1919 } 1920 1921 // Keyboard navigation 1922 $doc.on('keydown', keyboardHandler); 1923 1924 if (!parallax) { 1925 // Pause on hover 1926 $frame.on('mouseenter.' + namespace + ' mouseleave.' + namespace, pauseOnHoverHandler); 1927 // Reset native FRAME element scroll 1928 $frame.on('scroll.' + namespace, resetScroll); 1929 } 1930 1931 // Mark instance as initialized 1932 self.initialized = 1; 1933 1934 // Load 1935 load(true); 1936 1937 // Initiate automatic cycling 1938 if (o.cycleBy && !parallax) { 1939 self[o.startPaused ? 'pause' : 'resume'](); 1940 } 1941 1942 // Return instance 1943 return self; 1944 }; 1945 } 1946 1947 Sly.getInstance = function (element) { 1948 return $.data(element, namespace); 1949 }; 1950 1951 Sly.storeInstance = function (element, sly) { 1952 return $.data(element, namespace, sly); 1953 }; 1954 1955 Sly.removeInstance = function (element) { 1956 return $.removeData(element, namespace); 1957 }; 1958 1959 /** 1960 * Return type of the value. 1961 * 1962 * @param {Mixed} value 1963 * 1964 * @return {String} 1965 */ 1966 function type(value) { 1967 if (value == null) { 1968 return String(value); 1969 } 1970 1971 if (typeof value === 'object' || typeof value === 'function') { 1972 return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object'; 1973 } 1974 1975 return typeof value; 1976 } 1977 1978 /** 1979 * Event preventDefault & stopPropagation helper. 1980 * 1981 * @param {Event} event Event object. 1982 * @param {Bool} noBubbles Cancel event bubbling. 1983 * 1984 * @return {Void} 1985 */ 1986 function stopDefault(event, noBubbles) { 1987 event.preventDefault(); 1988 if (noBubbles) { 1989 event.stopPropagation(); 1990 } 1991 } 1992
1993 /** 1994 * Disables an event it was triggered on and unbinds itself. 1995 * 1996 * @param {Event} event 1997 * 1998 * @return {Void} 1999 */ 2000 function disableOneEvent(event) { 2001 /*jshint validthis:true */ 2002 stopDefault(event, 1); 2003 $(this).off(event.type, disableOneEvent); 2004 } 2005 2006 /** 2007 * Resets native element scroll values to 0. 2008 * 2009 * @return {Void} 2010 */ 2011 function resetScroll() { 2012 /*jshint validthis:true */ 2013 this.scrollLeft = 0; 2014 this.scrollTop = 0; 2015 } 2016 2017 /** 2018 * Check if variable is a number. 2019 * 2020 * @param {Mixed} value 2021 * 2022 * @return {Boolean} 2023 */ 2024 function isNumber(value) { 2025 return !isNaN(parseFloat(value)) && isFinite(value); 2026 } 2027 2028 /** 2029 * Parse style to pixels. 2030 * 2031 * @param {Object} $item jQuery object with element. 2032 * @param {Property} property CSS property to get the pixels from. 2033 * 2034 * @return {Int} 2035 */ 2036 function getPx($item, property) { 2037 return 0 | round(String($item.css(property)).replace(/[^\-0-9.]/g, '')); 2038 } 2039 2040 /** 2041 * Make sure that number is within the limits. 2042 * 2043 * @param {Number} number 2044 * @param {Number} min 2045 * @param {Number} max 2046 * 2047 * @return {Number} 2048 */ 2049 function within(number, min, max) { 2050 return number < min ? min : number > max ? max : number; 2051 } 2052 2053 /** 2054 * Saves element styles for later restoration. 2055 * 2056 * Example: 2057 * var styles = new StyleRestorer(frame); 2058 * styles.save('position'); 2059 * element.style.position = 'absolute'; 2060 * styles.restore(); // restores to state before the assignment above 2061 * 2062 * @param {Element} element 2063 */ 2064 function StyleRestorer(element) { 2065 var self = {}; 2066 self.style = {}; 2067 self.save = function () { 2068 if (!element || !element.nodeType) return; 2069 for (var i = 0; i < arguments.length; i++) { 2070 self.style[arguments[i]] = element.style[arguments[i]]; 2071 } 2072 return self; 2073 }; 2074 self.restore = function () { 2075 if (!element || !element.nodeType) return; 2076 for (var prop in self.style) { 2077 if (self.style.hasOwnProperty(prop)) element.style[prop] = self.style[prop]; 2078 } 2079 return self; 2080 }; 2081 return self; 2082 } 2083 2084 // Local WindowAnimationTiming interface polyfill 2085 (function (w) { 2086 rAF = w.requestAnimationFrame 2087 || w.webkitRequestAnimationFrame 2088 || fallback; 2089 2090 /** 2091 * Fallback implementation. 2092 */ 2093 var prev = new Date().getTime(); 2094 function fallback(fn) { 2095 var curr = new Date().getTime(); 2096 var ms = Math.max(0, 16 - (curr - prev)); 2097 var req = setTimeout(fn, ms); 2098 prev = curr; 2099 return req; 2100 } 2101 2102 /** 2103 * Cancel. 2104 */ 2105 var cancel = w.cancelAnimationFrame 2106 || w.webkitCancelAnimationFrame 2107 || w.clearTimeout; 2108 2109 cAF = function(id){ 2110 cancel.call(w, id); 2111 }; 2112 }(window)); 2113 2114 // Feature detects 2115 (function () { 2116 var prefixes = ['', 'Webkit', 'Moz', 'ms', 'O']; 2117 var el = document.createElement('div'); 2118 2119 function testProp(prop) { 2120 for (var p = 0, pl = prefixes.length; p < pl; p++) { 2121 var prefixedProp = prefixes[p] ? prefixes[p] + prop.charAt(0).toUpperCase() + prop.slice(1) : prop; 2122 if (el.style[prefixedProp] != null) { 2123 return prefixedProp; 2124 } 2125 } 2126 } 2127 2128 // Global support indicators 2129 transform = testProp('transform'); 2130 gpuAcceleration = testProp('perspective') ? 'translateZ(0) ' : ''; 2131 }()); 2132 2133 // Expose class globally 2134 w[className] = Sly; 2135 2136 // jQuery proxy 2137 $.fn[pluginName] = function (options, callbackMap) { 2138 var method, methodArgs; 2139 2140 // Attributes logic 2141 if (!$.isPlainObject(options)) { 2142 if (type(options) === 'string' || options === false) { 2143 method = options === false ? 'destroy' : options; 2144 methodArgs = Array.prototype.slice.call(arguments, 1); 2145 } 2146 options = {}; 2147 } 2148 2149 // Apply to all elements 2150 return this.each(function (i, element) { 2151 // Call with prevention against multiple instantiations 2152 var plugin = Sly.getInstance(element); 2153 2154 if (!plugin && !method) { 2155 // Create a new object if it doesn't exist yet 2156 plugin = new Sly(element, options, callbackMap).init(); 2157 } else if (plugin && method) { 2158 // Call method 2159 if (plugin[method]) { 2160 plugin[method].apply(plugin, methodArgs); 2161 } 2162 } 2163 }); 2164 }; 2165 2166 // Default options 2167 Sly.defaults = { 2168 slidee: null, // Selector, DOM element, or jQuery object with DOM element representing SLIDEE. 2169 horizontal: false, // Switch to horizontal mode. 2170 2171 // Item based navigation 2172 itemNav: null, // Item navigation type. Can be: 'basic', 'centered', 'forceCentered'. 2173 itemSelector: null, // Select only items that match this selector. 2174 smart: false, // Repositions the activated item to help with further navigation. 2175 activateOn: null, // Activate an item on this event. Can be: 'click', 'mouseenter', ... 2176 activateMiddle: false, // Always activate the item in the middle of the FRAME. forceCentered only. 2177 2178 // Scrolling 2179 scrollSource: null, // Element for catching the mouse wheel scrolling. Default is FRAME. 2180 scrollBy: 0, // Pixels or items to move per one mouse scroll. 0 to disable scrolling. 2181 scrollHijack: 300, // Milliseconds since last wheel event after which it is acceptable to hijack global scroll. 2182 scrollTrap: false, // Don't bubble scrolling when hitting scrolling limits. 2183 2184 // Dragging 2185 dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME. 2186 mouseDragging: false, // Enable navigation by dragging the SLIDEE with mouse cursor. 2187 touchDragging: false, // Enable navigation by dragging the SLIDEE with touch events. 2188 releaseSwing: false, // Ease out on dragging swing release. 2189 swingSpeed: 0.2, // Swing synchronization speed, where: 1 = instant, 0 = infinite. 2190 elasticBounds: false, // Stretch SLIDEE position limits when dragging past FRAME boundaries. 2191 dragThreshold: 3, // Distance in pixels before Sly recognizes dragging. 2192 interactive: null, // Selector for special interactive elements. 2193 2194 // Scrollbar 2195 scrollBar: null, // Selector or DOM element for scrollbar container. 2196 dragHandle: false, // Whether the scrollbar handle should be draggable. 2197 dynamicHandle: false, // Scrollbar handle represents the ratio between hidden and visible content. 2198 minHandleSize: 50, // Minimal height or width (depends on sly direction) of a handle in pixels. 2199 clickBar: false, // Enable navigation by clicking on scrollbar. 2200 syncSpeed: 0.5, // Handle =>
2200 SLIDEE synchronization speed, where: 1 = instant, 0 = infinite. 2201 2202 // Pagesbar 2203 pagesBar: null, // Selector or DOM element for pages bar container. 2204 activatePageOn: null, // Event used to activate page. Can be: click, mouseenter, ... 2205 pageBuilder: // Page item generator. 2206 function (index) { 2207 return '<li>' + (index + 1) + '</li>'; 2208 }, 2209 2210 // Navigation buttons 2211 forward: null, // Selector or DOM element for "forward movement" button. 2212 backward: null, // Selector or DOM element for "backward movement" button. 2213 prev: null, // Selector or DOM element for "previous item" button. 2214 next: null, // Selector or DOM element for "next item" button. 2215 prevPage: null, // Selector or DOM element for "previous page" button. 2216 nextPage: null, // Selector or DOM element for "next page" button. 2217 2218 // Automated cycling 2219 cycleBy: null, // Enable automatic cycling by 'items' or 'pages'. 2220 cycleInterval: 5000, // Delay between cycles in milliseconds. 2221 pauseOnHover: false, // Pause cycling when mouse hovers over the FRAME. 2222 startPaused: false, // Whether to start in paused sate. 2223 2224 // Mixed options 2225 moveBy: 300, // Speed in pixels per second used by forward and backward buttons. 2226 speed: 0, // Animations speed in milliseconds. 0 to disable animations. 2227 easing: 'swing', // Easing for duration based (tweening) animations. 2228 startAt: null, // Starting offset in pixels or items. 2229 keyboardNavBy: null, // Enable keyboard navigation by 'items' or 'pages'. 2230 2231 // Classes 2232 draggedClass: 'dragged', // Class for dragged elements (like SLIDEE or scrollbar handle). 2233 activeClass: 'active', // Class for active items and pages. 2234 disabledClass: 'disabled' // Class for disabled navigation elements. 2235 }; 2236}(jQuery, window));
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.