Skip to content

Instantly share code, notes, and snippets.

Created February 23, 2018 06:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/0682a0a2e5e8f9ad96371e7f5df549cd to your computer and use it in GitHub Desktop.
Save anonymous/0682a0a2e5e8f9ad96371e7f5df549cd to your computer and use it in GitHub Desktop.
slider script
/************************************
* jQuery Extend
*************************************/
/** @external "jQuery.fn" */
$.fn.extend({
/**
* 指定した要素がDOM上にある時だけ引数の関数を実行
* @param {function} fn 指定した要素がある時に実行する関数
* @param {function} else_fn 指定した要素がない時に実行する関数
* @example
* $("#page_name").dispatch(function($page){
* // $("#page_name")がある時に実行する処理
* // 引数$pageには$("#page_name")を格納
*
* }, function(){
* // $("#page_name")がない時に実行する処理(任意)
* });
* @function external:"jQuery.fn".dispatch
*/
dispatch: function(fn, else_fn){
if(this.length) typeof fn==="function" && fn(this);
else typeof else_fn==="function" && else_fn(this);
},
/**
* スライドショークラス「multiSlider」のインスタンス生成、スライドショー実行
* @param {obj} options multiSliderのオプションを格納
* @see MultiSlider
* @example
* $("#slide_box").multiSlider({
* liquid: true,
* touch: true,
* speed: 500,
* interval: 3000
* });
* @function external:"jQuery.fn".multiSlider
*/
multiSlider: function(options){
options = $.extend({}, MultiSlider._default_options, options);
this.each(function(){
this.__multislider = new MultiSlider($(this), typeof options=="object" ? options : {});
});
return this;
}
});
// Slideshow Plugin
/**
* スライドショークラス「multiSlider」用定数
* @memberof MultiSlider
* @const MLTCLASSNAMES {object}
* @property {string} frame スライドショーフレーム要素のクラス名
* @property {string} slider スライドショースライド項目格納要素のクラス名
* @property {string} controller スライドショーコントローラ要素のクラス名
* @property {string} indicator スライドショーインジケータ要素のクラス名
* @property {string} thumbnail スライドショーサムナイル要素のクラス名
*/
var MLTCLASSNAMES = {
frame: "slideframe",
slider: "slider",
controller: "controller",
indicator: "indicator",
thumbnail: "thumbnail"
};
/**
* @constructor MultiSlider
* @param {object} container jQueryオブジェクト。<br>スライドショーを格納しているコンテナ
* @param {object} options MultiSliderオプション
*/
var MultiSlider = function(container, options){
this._init(container, options);
return this;
};
/**
* デフォルトオプション
* @memberof MultiSlider
* @const MultiSlider._default_options {object}
* @property {string} slidemode スライドショーの動作(横スライド or 縦スライド)
* @property {string} item_name スライド項目の要素名(クラス名などでも可)
* @property {boolean} indicate インジケータを出力するか否か
* @property {boolean} thumbnail サムネイルクリックでのスライドを行うか否か
* @property {boolean} touch タッチイベントでのスライドを行うか否か
* @property {boolean} liquid スライドエリアの幅、高さが変わった時にスライド項目なども自動でリサイズするか否か
* @property {boolean} autoslide 自動スライド機能を有効かするか否か
* @property {integer} speed スライドの速度(ms)
* @property {string} easing スライドのイージング
* @property {integer} delay ウィンドウリサイズ終了後、何ミリ秒後にスライドを再開するか
* @property {integer} interval 自動スライドの間隔
* @property {function} callback スライド終了後のコールバック関数
* @property {string} eventname カスタムイベントのイベント名
*/
MultiSlider._default_options = {
slidemode: "horizon",// or vertical
item_name: "li",
indicate: false,
thumbnail: false,
touch: false,
liquid: true,
autoslide: true,
speed: 500,
easing: "swing",
delay: 10,
interval: 4000,
callback: false,
eventname: "ms"
};
MultiSlider.toggleOnOff = function(i, target){
var length = target.find("a").length;
target.find(".on").removeClass("on");
target.find("a").eq(i%length).addClass("on");
};
MultiSlider.prototype = {
_init: function(container, options){
var _this = this;
/**
* 現在表示している項目のインデックス
* @memberof MultiSlider
*/
this.current = 0;
this.$next = null;
this.locked = false;
this.timeout_id = null;
this.resizetimer = false;
this.options = options;
this.$elements = {
container: container,
frame: container.find("." + MLTCLASSNAMES.frame),
slider: container.find("." + MLTCLASSNAMES.slider),
controller: container.find("." + MLTCLASSNAMES.controller),
indicator: container.find("." + MLTCLASSNAMES.indicator),
thumbnail: container.find("." + MLTCLASSNAMES.thumbnail)
};
this.item = this.options.item_name;
this.item_length = this.$elements.slider.find(this.item).length;
this.set_dummy_item();
this.set_slider_width();
this.set_indicator();
setTimeout(function(){
_this.placement(_this.current);
_this.interfaces();
_this.onresize();
_this.resize();
_this.trigger("oninit", [_this, {current: _this.current}]);
}, 0);
},
trigger: function(eventname, data){
var result = this.$elements.container.triggerHandler(this.options.eventname + "." + eventname, data);
return result === undefined ? true : result;
},
/**
* 現在表示している項目の両隣のインデックスを取得
* @example
* var sides = $mlt.getsides(); // {prev: 0, next: 2}
* @memberof MultiSlider
* @method MultiSlider.getsides
*/
getsides: function(){
var length = this.$elements.slider.find(this.item).length;
return {
prev: (this.current + length - 1) % length,
next: (this.current + 1) % length
};
},
/**
* 指定インデックスのスライド項目の要素を取得
* @example
* var $item = $mlt.getitem(1); // jQueryオブジェクト
* @memberof MultiSlider
* @param {integer} i 取得したいスライド項目のインデックス
* @method MultiSlider.getitem
*/
getitem: function(i){
return this.$elements.slider.find("[data-slide-id='" + i + "']");
},
/**
* スライド項目のpadding、marginを含めた幅を取得
* @example
* var width = $mlt.get_item_width(); // Integer
* @memberof MultiSlider
* @method MultiSlider.get_item_width
*/
get_item_width: function(){
var $items = this.$elements.slider.find(this.item);
var margin = parseInt($items.css("margin-right")) + parseInt($items.css("margin-left"));
return $items.outerWidth() + margin;
},
set_next_slide: function(){
var _this = this;
return function(){
_this.slideshow(_this.getsides().next);
};
},
set_slider_width: function(){
if(this.options.slidemode==="vertical") return;
var $items = this.$elements.slider.find(this.item);
var item_width = this.get_item_width();
this.$elements.slider.width(item_width * $items.length);
},
set_frame_height: function(){
var _this = this;
var h = 0;
this.$elements.slider.find(this.item).each(function(){
h = Math.max(h, $(this).outerHeight());
});
this.$elements.frame.height(h);
},
set_indicator: function(){
var html = '<ul>';
if(this.options.indicate){
for(var i=0; i<this.item_length; i++)
html += '<li><a href="#">'+i+'</a></li>';
html += '</ul>';
this.$elements.indicator.html(html);
this.$elements.indicator.find("li:first-child a").addClass("on");
}
},
set_dummy_item: function(){
var _this = this;
var $append_obj = $("");
var $prepend_obj = $("");
var cnt_id = this.item_length;
this.$elements.slider.find(this.item).each(function(i){
var $org = $(this);
var $append_clone = $org.clone();
var $prepend_clone = $org.clone();
$org.attr("data-slide-id", i).addClass("org-item");
$append_clone.attr("data-slide-id", cnt_id);
$prepend_clone.attr("data-slide-id", (cnt_id + _this.item_length));
cnt_id++;
$append_obj = $append_obj.add($append_clone.get(0));
$prepend_obj = $prepend_obj.add($prepend_clone.get(0));
});
this.$elements.slider.append($append_obj);
this.$elements.slider.prepend($prepend_obj);
},
interfaces: function(){
var _this = this;
var touch_mode = 2;
var touch_x, touch_y;
var top, left;
var cb = function(){ touch_mode = 2; };
var click_controller = function(e){
e.preventDefault();
var sides = _this.getsides();
var is_next = _this.$elements.controller.find("a").index(this);
var next = is_next ? sides.next : sides.prev;
if(!_this.trigger("onclick_controller", [_this, {next: next}, is_next])) return;
_this.slideshow(next);
};
var click_indicator = function(e){
e.preventDefault();
var next = _this.$elements.indicator.find("a").index(this);
if(_this.current===next || !_this.trigger("onclick_indicator", [_this, {next: next}])) return;
_this.slideshow(next);
};
var click_thumbnail = function(e){
e.preventDefault();
var next = _this.$elements.thumbnail.find("a").index(this);
if(_this.current===next || !_this.trigger("onclick_thumbnail", [_this, {next: next}])) return;
_this.slideshow(next);
};
var touchstart = function(e){
touch_mode = 0;
touch_x = event.changedTouches[0].pageX;
touch_y = event.changedTouches[0].pageY;
if(!_this.trigger("ontouchstart", [_this, {x: touch_x, y: touch_y}])) touch_mode = 2;
};
var touchmove = function(e){
var x = event.changedTouches[0].pageX;
var y = event.changedTouches[0].pageY;
var dx = x - touch_x;
var dy = y - touch_y;
touch_x = x;
touch_y = y;
if(!_this.trigger("ontouchmove", [_this, {x: x, y: y, dx: dx, dy: dy}])) return;
if(touch_mode == 0){
if((
(_this.options.slidemode==="horizon" && Math.abs(dx)>Math.abs(dy)) ||
(_this.options.slidemode==="vertical" && Math.abs(dx)<Math.abs(dy))
) && !_this.locked){
touch_mode = 1;
clearTimeout(_this.timeout_id);
}else{
touch_mode = 2;
}
}
if(touch_mode == 1){
e.preventDefault();
top = _this.$elements.slider.position().top;
left = _this.$elements.slider.position().left;
if(_this.options.slidemode==="vertical"){
_this.$elements.slider.css({top: top + dy});
}else if(_this.options.slidemode==="horizon"){
_this.$elements.slider.css({left: left + dx});
}
}
};
var touchend = function(e){
if(_this.locked || touch_mode!=1) return;
e.preventDefault();
var current_pos = 0, abs_pos = 0, base = 0, diff;
var sides = _this.getsides();
if(_this.options.slidemode=="vertical"){
current_pos = _this.getitem(_this.current).position().top;
abs_pos = Math.abs(top);
base = _this.$elements.frame.height() / 20;
}else if(_this.options.slidemode=="horizon"){
current_pos = _this.getitem(_this.current).position().left;
abs_pos = Math.abs(left);
base = _this.$elements.frame.width() / 20;
}
diff = Math.abs(abs_pos - current_pos);
if(!_this.trigger("ontouchend", [_this, {x: touch_x, y: touch_y, current_pos: current_pos, slider_pos: abs_pos, diff: diff, base: base, callback: cb}])) return;
if(abs_pos>current_pos && diff>base){
_this.slideshow(sides.next, cb);
}else if(abs_pos<current_pos && diff>base){
_this.slideshow(sides.prev, cb);
}else{
_this.slideshow(_this.current, cb);
}
};
this.$elements.controller.on("click", "a", click_controller);
this.$elements.indicator.on("click", "a", click_indicator);
this.$elements.thumbnail.on("click", "a", click_thumbnail);
if(this.options.touch && window.TouchEvent){
this.$elements.container.on("touchstart.multislider", touchstart)
.on("touchmove.multislider", touchmove).on("touchend.multislider", touchend);
}
},
/**
* 指定したインデックスのアイテムにアニメーションをせずに移動
* @memberof MultiSlider
* @param {integer} i 表示したい項目のインデックス
* @method MultiSlider.placement
*/
placement: function(i){
this.current = i%this.item_length;
var active = this.getitem(this.current);
this.$elements.slider.stop(true, true).css({
top: -active.position().top,
left: -active.position().left
});
},
resize: function(){
if(this.resizetimer!==false){
clearTimeout(this.resizetimer);
}
var _this = this;
clearTimeout(this.timeout_id);
this.locked = true;
this.resizetimer = setTimeout(function(){
if(_this.options.liquid){
_this.$elements.slider.find(_this.item).outerWidth(_this.$elements.frame.width());
_this.set_frame_height();
}
_this.set_slider_width();
_this.placement(_this.current);
_this.locked = false;
_this.resizetimer = false;
if(_this.options.autoslide){
setTimeout(function(){ _this.autoslide(); }, _this.options.delay);
}
_this.trigger("onresize", [_this, {current: _this.current}]);
}, 10);
},
onresize: function(){
var resize = this.resize.bind(this);
$(window).on("load", resize)
.on("resize.multislider", resize);
},
marking: function(i){
if(this.options.indicate) MultiSlider.toggleOnOff(i, this.$elements.indicator);
if(this.options.thumbnail) MultiSlider.toggleOnOff(i, this.$elements.thumbnail);
},
autoslide: function(){
clearTimeout(this.timeout_id);
this.timeout_id = setTimeout(this.set_next_slide(), this.options.interval);
},
slideafter: function(i, fn){
this.locked = false;
this.placement(i);
typeof fn==="function" && fn();
this.options.callback && typeof this.options.callback==="function"
&& this.options.callback.call(this.$elements.container, this);
if(!this.trigger("onslideafter", [this, {current: this.current, prev: this.getsides().prev % this.item_length}])) return;
this.options.autoslide && this.autoslide();
},
slidebefore: function(i){
if(this.locked) return false;
this.locked = true;
clearTimeout(this.timeout_id);
this.$next = this.getitem(i);
if(!this.trigger("onslidebefore", [this, {current: this.current, next: i % this.item_length }])){
this.locked = false;
return false;
}
return true;
},
slide: function(i, complete){
this.marking(i);
this.$elements.slider.stop(true, true).animate(
{
top: -this.$next.position().top,
left: -this.$next.position().left
},
{
duration: this.options.speed,
easing: this.options.easing,
complete: complete
}
);
},
/**
* スライドショーを実行
* @memberof MultiSlider
* @param {integer} i 表示したい項目のインデックス
* @param {function} fn コールバック関数
* @method MultiSlider.slideshow
*/
slideshow: function(i, fn){
var _this = this;
if(this.slidebefore(i) && this.$next.length){
this.slide(i, function(){ _this.slideafter(i, fn); });
}
}
};
/**
* カスタマイズイベント
* 引数に自信のインスタンスと基本的にスライド項目の「current」「prev」「next」のいずれかを持ち、<br>
* {@code return false;}で通常の処理をキャンセルすることができます。
* @example
* $(".slidebox").multiSlider().on("ms.oninit", function(e, $mlt, indexes){
* // e: eventオブジェクト
* // $mlt: スライドショーインスタンス
* // indexes: オブジェクト{current: Int}
*
* }).on("ms.onslidebefore", function(e, $mlt, indexes){
* // e: eventオブジェクト
* // $mlt: スライドショーインスタンス
* // indexes: オブジェクト{current: Int, next: Int}
*
* }).on("ms.onslideafter", function(e, $mlt, indexes){
* // e: eventオブジェクト
* // $mlt: スライドショーインスタンス
* // indexes: オブジェクト{current: Int, prev: Int}
* });
* @memberof MultiSlider
* @method MultiSlider.Events
* @property {function} oninit イニシャライズ完了時のコールバック
* @property {function} onclick_controller コントローラクリック時のコールバック
* @property {function} onclick_indicator インジケータクリック時のコールバック
* @property {function} onclick_thumbnail サムネイルクリック時のコールバック
* @property {function} ontouchstart touchstartのコールバック
* @property {function} ontouchmove touchmoveのコールバック
* @property {function} ontouchend touchendのコールバック
* @property {function} onresize リサイズ時のコールバック
* @property {function} onslideafter スライドが完了した時のコールバック
* @property {function} onslidebefore スライドが開始する直前のコールバック
*/
/************************************
* Polyfill
*************************************/
// Function bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
// Object keys
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
if (!Object.keys) {
Object.keys = (function() {
'use strict';
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function(obj) {
if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
throw new TypeError('Object.keys called on non-object');
}
var result = [], prop, i;
for (prop in obj) {
if (hasOwnProperty.call(obj, prop)) {
result.push(prop);
}
}
if (hasDontEnumBug) {
for (i = 0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) {
result.push(dontEnums[i]);
}
}
}
return result;
};
}());
}
// Array Filter
if (!Array.prototype.filter) {
Array.prototype.filter = function(fun /*, thisp */) {
"use strict";
if (this == null) throw new TypeError();
var t = Object(this),
len = t.length >>> 0;
if (typeof fun != "function") throw new TypeError();
var res = [],
thisp = arguments[1];
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i]; // fun が this を変化させた場合に備えて
if (fun.call(thisp, val, i, t)) res.push(val);
}
}
return res;
};
}
// Array indexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
"use strict";
if (this == null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 0) {
n = Number(arguments[1]);
if (n != n) { // shortcut for verifying if it's NaN
n = 0;
} else if (n != 0 && n != Infinity && n != -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
};
}
// Array Map
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
var O = Object(this);
var len = O.length >>> 0;
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
T = thisArg;
}
A = new Array(len);
k = 0;
while (k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[k];
mappedValue = callback.call(T, kValue, k, O);
A[k] = mappedValue;
}
k++;
}
return A;
};
}
// The processing for HTML5 incompatible browser
if(/*@cc_on!@*/false){
var e = "abbr,article,aside,audio,bb,canvas,datagrid,datalist,details,dialog,eventsource,figure,footer,header,hgroup,mark,menu,meter,nav,output,progress,section,time,video,main".split(",");
for(var i = 0; i < e.length; i++) document.createElement(e[i]);
}
})(window, document, jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment