// x slider (function($) { 'use strict'; if ( !$ || $.fn.xlider ) { return; } var $win = $(window), $doc = $(document.documentElement), $body = $(document.body), isMobile = $.browser.mobile, isIOS = $.browser.ios, isAndroid = $.browser.android, supportTransform = $.support.transform, supportTransition = $.support.transition, getEventType = $._event.gettype, getEventPoint = $._event.getpoint, regExpIsUL = /ul/i, windowWidth, resizeFunctions = [], last; /* * option { defaultPage: 숫자(0~) - 처음 표시할 슬라이드 endless: true|false - 무한 슬라이드 paging: true|false - 페이징 사용

형식으로 $box에 추가됨. arrows: [$이전버튼, $다음버튼](외부에 정의된 jQuery객체) | tag명 string

형식으로 $box에 추가됨. useSwipe: true|false - swipe 기능 사용 animate: true|false - 애니매이션 사용 useTransition: true|false - css transition 사용 autoPlay: 밀리세컨드 - 자동 플레이 설정(시간간격을 밀리초단위로 지정) onMove: 위치에 변화가 있을 때마다 호출. useTransition이 true일 경우에는 호출 안함 onChange: 페이지가 바뀌기 전 호출 onChangeEnd: 페이지가 바뀌고 난 후 호출 } * methods - 공통 인수 noAnimation으로 애니매이션 여부 설정 { prev(noAnimation): 이전 페이지 next(noAnimation): 다음 페이지 jump(page, pretendTo, noAnimation): page로 점프(중간 페이지는 보이지 않음). pretendTo('prev'|'next')로 애니매이션 방향 지정. change(page, noAnimation): page로 이동, toggleAuto(): autoPlay 재생/정지 토글 } */ $.fn.xlider = function(_option, value, pretendTo, noAnimation) { var isAutoPlaying; if (!_option || $.isPlainObject(_option)) { this.each(function() { xlider($(this), _option); }); } else if (typeof(_option) == 'string') { if (_option == 'prev' || _option == 'next') { noAnimation = value; } else if (_option == 'toggleAuto') { isAutoPlaying = {v: 0}; $(this).trigger('xlider-'+_option, isAutoPlaying); return isAutoPlaying.v; } else if (_option == 'change' || typeof pretendTo != 'string') { noAnimation = pretendTo; } this.trigger('xlider-'+_option, {to: value, pretendTo: pretendTo, noAnimation: noAnimation}); } return this; } $win.resize(function() { windowWidth = $doc[0].clientWidth; $.each(resizeFunctions, function() { this(); }); }); function xlider($box, option) { var option = option || {}, endless = option.endless, arrows = option.arrows, arrowsTag = arrows ? arrows.constructor == Array ? 'pre' : typeof arrows == 'string' ? arrows : 'button' : '', paging = option.paging, pagingTag = paging ? paging.length && paging[0].nodeType ? 'pre' : typeof paging == 'string' ? paging : 'button' : '', useSwipe = option.swipe !== false, animate = option.animate !== false, autoPlay = option.autoPlay, useTransition = option.useTransition && supportTransition, numDisplay = option.numDisplay || 1, eventMove = option.onMove, eventChange = option.onChange, eventChangeEnd = option.onChangeEnd, $wrapper, $arrows, $prev, $next, $paging, $pagingItems, $items = $box.children(), $fakeItem, $blocker, boxWidth, itemWidth, moveSize, downX, downY, baseX, swipeAngle, startTime, moved, multiMode = numDisplay > 1, classicMode = false, clickAble = true, blockerAdded = false, autoPlaying = !!autoPlay, autoPlayTimer = null, wrapperClassName = 'xlider-wrapper', i = 0, max = $items.length, nowPage = option.defaultPage && option.defaultPage > -1 ? Math.min(max-1, option.defaultPage) : 0, aniOption = { slide: {queue: false, step: onMove, easing: 'easeOutCubic', complete: changeEnd}, back: {queue: false, step: onMove, easing: 'easeOutCubic', complete: changeEnd} }; if (!$body.length) { $body = $(document.body); } if (!max) { return; } // box 자체가 ul 태그이면 ul을 wrapper로 하고 parent를 box로 변경 if (regExpIsUL.test($box[0].nodeName)) { $wrapper = $box.addClass(wrapperClassName); $box = $wrapper.parent(); // 아이템 하나이고 ul 태그면 ul내의 li를 아이템으로 } else if (2 > max && regExpIsUL.test($items[0].nodeName)) { $wrapper = $items.addClass(wrapperClassName); $items = $items.find('> li'); max = $items.length; } // wrapper추가 if (!$wrapper) { $wrapper = $('
').appendTo($box); } if ($wrapper.css('position') == 'static') { $wrapper.css('position', 'relative'); } $box.css('overflow', 'hidden'); // endless 는 2개 이상일때만 if (endless && 2 > max) { endless = false; } // arrow 버튼 추가 if (arrows && max > 1) { // 미리 정해놓은 방향버튼 엘리먼트 if (arrowsTag == 'pre') { $prev = arrows[0]; $next = arrows[1]; } else { $arrows = $('

').appendTo($box); $prev = $('<'+ arrows +' class="prev">Prev').appendTo($arrows); $next = $('<'+ arrows +' class="next">Next').appendTo($arrows); } $prev.attr('data-flag', 'prev').click(toPrev); $next.attr('data-flag', 'next').click(toNext); } else if (2 > max) { // arrow를 외부에서 미리 정해놓은 경우 제거. if (arrows == 'pre') { arrows[0].remove(); arrows[1].remove(); } arrows = null; } // paging 추가 if (paging) { // 미리 정해놓은 페이징 엘리먼트 if (pagingTag == 'pre') { $paging = $(paging); $pagingItems = $paging.children(); } else { $paging = $('

').appendTo($box); $pagingItems = []; for (i = 0; i < max; i++) { $pagingItems[i] = $('<'+pagingTag+' />'); $pagingItems[i][0].innerHTML = i+1; } } } for (i = 0; i < max; i++) { $items[i] = $($items[i]).css({left: '200%', top: 0, width: '100%', display: i == nowPage ? '' : 'none'}); $items[i].appendTo($wrapper); if (i != nowPage) { $items[i].hide(); } if (paging) { $pagingItems[i] = $($pagingItems[i]).attr('data-page', i) .click(change) .appendTo($paging); } } if (useSwipe) { if ($.browser.ie) { // ie tablet fix if (window.navigator.pointerEnabled) { $wrapper[0].style.cssText += 'touch-action: pan-y;'; } else if (window.navigator.msPointerEnabled ){ $wrapper[0].style.cssText += '-ms-touch-action: pan-y;'; } } $wrapper.bind(getEventType({mousedown: down})); $wrapper.bind('selectstart dragstart', function() { return false; }); } // 외부 제어용 $box.bind('xlider-prev', function(e, option) { toPrev(e, false, option.noAnimation); }); $box.bind('xlider-next', function(e, option) { toNext(e, false, option.noAnimation); }); $box.bind('xlider-jump', function(e, option) { jump(option); }); $box.bind('xlider-change', function(e, option) { change(option); }); $box.bind('xlider-toggleAuto', function(e, forState) { forState.v = toggleAuto(); }); $box.bind('xlider-remove', function(e) { for (i = 0; i < max; i++) { if (!regExpIsUL.test($wrapper[0].nodeName)) { $items[i].appendTo($box); } if (paging) { if (pagingTag != 'pre') { $pagingItems[i].remove(); } else { $pagingItems[i].unbind('click'); } } } $wrapper._css({position: '', translate3dX: ''}).removeClass(wrapperClassName); $wrapper.unbind(getEventType({mousedown: down})); if (!regExpIsUL.test($wrapper[0].nodeName)) { $wrapper.remove(); } if (paging && pagingTag != 'pre') { $paging.remove(); } if (arrows) { if (arrowsTag != 'pre') { $prev.remove(); $next.remove(); } else { $prev.unbind('click'); $next.unbind('click'); } } $fakeItem && $fakeItem.remove(); $blocker.remove(); $box.unbind('xlider-prev xlider-next xlider-jump xlider-change xlider-toggleAuto xlider-remove'); $box = $wrapper = $prev = $next = $pagingItems = null; for (i = 0, max = resizeFunctions.length; i < max; i++) { if (resizeFunctions[i] == resize) { resizeFunctions.splice(i, 1); break; } } }); // mouse로 드래그 시 콘텐츠 차단용 엘리먼트 $blocker = $('

').css('opacity', 0); setButtons(); resize(); changeEnd(); resizeFunctions.push(resize); function down(e) { clearAutoPlay(); if (!clickAble) { return false; } downX = baseX = getEventPoint(e)[0]; // ios 7 이상의 사파리에서 양 끝으로 이전/다음 히스토리 이동시 화면이 깨져서.. 좌우 15픽셀 내에서는 취소 if (isIOS && (15 > downX || downX > windowWidth-15)) { return true; } downY = getEventPoint(e)[1]; startTime = new Date().getTime(); moved = 0; swipeAngle = false; $doc.bind(getEventType({mousemove: move, mouseup: up})); } function move(e) { var x = getEventPoint(e)[0], y = getEventPoint(e)[1], nowTime = new Date().getTime(); // 드래그 각도체크 좌우 90도(><) 이내일때만 동작 if (swipeAngle === false) { swipeAngle = Math.abs((Math.atan2(downX-x, downY-y)*180)/Math.PI); if (45 > swipeAngle || swipeAngle > 135) { $doc.unbind(getEventType({mousemove: move, mouseup: up})); return true; } } moved = x-downX; if (!endless && (!nowPage || nowPage == max-1)) { moved /= 2; } $wrapper._css('translate3dX', moved); onMove(moved); if (nowTime-300 > startTime) { startTime = nowTime; baseX = x; } // mobile 환경이 아닌 경우 콘텐츠 접근 차단 if (!isMobile && !blockerAdded) { $blocker.appendTo($box); blockerAdded = true; } e.preventDefault(); } function onMove(v, moveOnly) { moveOnly !== true && eventMove && eventMove.call($box, $.isPlainObject(v) ? v.translate3dX : v); } function up(e) { var x = getEventPoint(e)[0], movedvalue = x - baseX; if (x != downX) { if (10 > Math.abs(x-downX)) { back(); } else if (!movedvalue || new Date().getTime()-startTime > 300) { if (moved > boxWidth/2 && (endless || nowPage)) { toPrev(false, true); } else if (-boxWidth/2 > moved && (endless || nowPage != max-1)) { toNext(false, true); } else { back(); } } else { if (movedvalue > 0 && (endless || nowPage)) { toPrev(false, true); } else if (0 > movedvalue && (endless || nowPage != max-1)) { toNext(false, true); } else { back(); } } } if (!isMobile && blockerAdded) { $blocker.detach(); blockerAdded = false; } $doc.unbind(getEventType({mousemove: move, mouseup: up})); } function back() { wrapperMove(0, 'back'); } function change(option) { var nextPage, moveTo, page = parseInt(option.to); if (!clickAble) { return false; } nextPage = typeof page == 'number' && !isNaN(page) ? page : this.getAttribute ? parseInt(this.getAttribute('data-page')) : null; if (nextPage !== null && nextPage != nowPage && nextPage > -1 && max > nextPage) { resize(); moveTo = (nowPage-nextPage)*moveSize; readyToMove(nextPage); wrapperMove(moveTo, 'slide', option.noAnimation); } return false; } function toPrev(e, fromSwipe, noAnimation) { if (!clickAble || (!endless && !nowPage)) { return false; } readyToMove(!nowPage ? max-1 : nowPage-1, 'prev', fromSwipe); wrapperMove(moveSize, 'slide', noAnimation); e && e.preventDefault(); return true; } function toNext(e, fromSwipe, noAnimation) { if (!clickAble || (!endless && nowPage == max-1)) { return false; } readyToMove(nowPage == max-1 ? 0 : nowPage+1, 'next', fromSwipe); wrapperMove(-moveSize, 'slide', noAnimation); e && e.preventDefault(); return true; } function jump(option) { var direction, page = option.to; if (!clickAble || page == nowPage) { return false; } for (i = 0; i < max; i++) { i != page && i != nowPage && $items[i].hide(); } direction = option.pretendTo ? option.pretendTo : nowPage > page ? 'prev' : 'next'; readyToMove(page, direction); wrapperMove(direction == 'prev' ? moveSize : -moveSize, 'slide', option.noAnimation); } function readyToMove(nextPage, prevOrNext, withoutDisplays) { var from, to; clearAutoPlay(); // 이전, 다음 페이지 display 설정. swipe 할 때는 자체 해결. if (!withoutDisplays) { if (prevOrNext) { appendItem(nextPage, prevOrNext == 'next' ? '100%' : '-100%'); } else { from = Math.min(nowPage, nextPage); to = Math.max(nowPage, nextPage); for (i = from; i <= to; i++) { if (i != nowPage) { appendItem(i, (i-nowPage)*100+'%'); } } } } clickAble = false; nowPage = nextPage; setButtons(); eventChange && eventChange.call($box, nowPage, max); resize(); } function appendItem(target, left) { (typeof target == 'number' ? $items[target] : target).css({position: 'absolute', left: left}).show(); } function wrapperMove(value, aniOptionKey, noAnimation) { if (animate && !noAnimation) { if (useTransition) { $wrapper._transition({translate3dX: value}, aniOption[aniOptionKey]); } else { $wrapper._animate({translate3dX: value}, aniOption[aniOptionKey]); } } else { changeEnd(); } } function setButtons() { for (i = 0; i < max; i++) { if (paging) { $pagingItems[i][ ( i == nowPage )? 'addClass' : 'removeClass' ]('on'); } } if (!endless && arrows) { $prev[!nowPage ? 'addClass' : 'removeClass' ]('disabled'); $next[nowPage == max-1 ? 'addClass' : 'removeClass' ]('disabled'); } } function changeEnd() { var $prevItem, $nextItem, $nowItem; for (i = 0; i < max; i++) { if (i == nowPage) { $items[i].css({position: 'relative', left: 0}).addClass('xlider-current'); } else { $items[i].hide().removeClass('xlider-current'); } } if ($fakeItem) { $fakeItem.remove(); $fakeItem = null; } $wrapper._css('translate3dX', 0); clickAble = true; $prevItem = $items[!nowPage ? endless ? max-1 : -1 : nowPage-1], $nextItem = $items[nowPage == max-1 ? endless ? 0 : max : nowPage+1], $nowItem = $items[nowPage]; // endless가 true인데, item이 2개 이하면 우측에 가짜 아이템 추가. if (endless && $prevItem[0] == $nextItem[0]) { $fakeItem = $prevItem.clone().addClass('xlider-fake'); $fakeItem.appendTo($wrapper); appendItem($fakeItem, itemWidth, true); } // 양쪽 사이드 아이템들 추가 for (i = 0; i < max; i++) { if ($prevItem && $items[i][0] == $prevItem[0]) { appendItem(i, '-'+ itemWidth); } else if ($nextItem && $items[i][0] == $nextItem[0]) { appendItem(i, itemWidth); } } eventChangeEnd && eventChangeEnd.call($box, nowPage, max); if (autoPlaying) { autoPlayTimer = setTimeout(autoPlayAction, autoPlay); } } function autoPlayAction() { jump({to: nowPage == max-1 ? 0 : nowPage+1, pretendTo: 'next'}); } function clearAutoPlay() { clearTimeout(autoPlayTimer); } function toggleAuto() { if (autoPlaying) { autoPlaying = false; clearAutoPlay(); } else { autoPlaying = true; autoPlayTimer = setTimeout(autoPlayAction, autoPlay); } return autoPlaying; } function setWrapperMS() { if (animate) { aniOption.slide.duration = Math.min(moveSize, 750); aniOption.back.duration = aniOption.slide.duration*0.75; } } function resize() { boxWidth = $box[0].offsetWidth; itemWidth = multiMode ? $items[0][0].offsetWidth : '100%'; moveSize = multiMode ? itemWidth : boxWidth; setWrapperMS(); } } function cancelEvent(e) { e.preventDefault(); } })(window.jQuery);