grgw_old_java/heatsupply2/WebContent/static/index/js/iSlider.js

2076 lines
59 KiB
JavaScript

/**
* A simple, efficent mobile slider solution
* @file iSlider.js
* @author BE-FE Team
* qbaty qbaty.qi@gmail.com
* xieyu33333 xieyu33333@gmail.com
* shinate shine.wangrs@gmail.com
*
* @LICENSE https://github.com/BE-FE/iSlider/blob/master/LICENSE
*/
(function (global) {
'use strict';
/**
* noop function
*/
function noop() {
}
/**
* Check in array
* @param {*} o
* @param {Array} arr
* @returns {Boolean}
*/
function inArray(o, arr) {
return arr.indexOf(o) > -1;
}
/**
* Check is array
* @param {*} o
* @returns {Boolean}
*/
function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]';
}
/**
* Check is object
* @param {*} o
* @returns {Boolean}
*/
function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]';
}
/**
* @param {HTMLElement} el
* @param {String} cls
* @returns {Array|{index: number, input: string}}
*/
function hasClass(el, cls) {
return el.className.match(new RegExp('(\\s|^)(' + cls + ')(\\s|$)'));
}
/**
* @param {HTMLElement} el
* @param {String} cls
*/
function addClass(el, cls) {
if (!hasClass(el, cls)) {
el.className += ' ' + cls;
}
}
/**
* @param {HTMLElement} el
* @param {String} cls
*/
function removeClass(el, cls) {
if (hasClass(el, cls)) {
el.className = el.className.replace(RegExp('(\\s|^)(' + cls + ')(\\s|$)'), '$3');
}
}
/**
* Check is url
* @param {String} url
* @returns {Boolean}
*/
function isUrl(url) {
if (/<\/?[^>]*>/.test(url))
return false;
return /^(?:(https|http|ftp|rtsp|mms):)?(\/\/)?(\w+:{0,1}\w*@)?([^\?#:\/]+\.[a-z]+|\d+\.\d+\.\d+\.\d+)?(:[0-9]+)?((?:\.?\/)?([^\?#]*)?(\?[^#]+)?(#.+)?)?$/.test(url);
}
/**
* Check is dom object
* @param {object} dom
* @returns {Boolean}
*/
function isDom(obj) {
try {
return obj instanceof HTMLElement;
}
catch (e) {
return (typeof obj === "object") &&
(obj.nodeType === 1) && (typeof obj.style === "object") &&
(typeof obj.ownerDocument === "object");
}
}
/**
* Parse arguments to array
*
* @param {Arguments} a
* @param {Number|null} start
* @param {Number|null} end
* @returns {Array}
*/
function _A(a) {
return Array.prototype.slice.apply(a, Array.prototype.slice.call(arguments, 1));
}
function IU(word) {
return word.replace(/^[a-z]/, function (t) {
return t.toUpperCase();
});
}
/**
* @constructor
*
* iSlider([[{HTMLElement} container,] {Array} datalist,] {Object} options)
*
* @param {HTMLElement} container
* @param {Array} datalist
* @param {Object} options
*
* @description
* options.dom > container
* options.data > datalist
*/
var iSlider = function () {
var args = _A(arguments, 0, 3);
if (!args.length) {
throw new Error('Parameters required!');
}
var opts = isObject(args.slice(-1)[0]) ? args.pop() : {};
switch (args.length) {
case 2:
opts.data = opts.data || args[1];
case 1:
opts.dom = opts.dom || args[0];
}
if (!opts.dom) {
throw new Error('Container can not be empty!');
}
else if (!isDom(opts.dom)) {
throw new Error('Container must be a HTMLElement instance!');
}
if (!opts.data || !opts.data.length) {
throw new Error('Data must be an array and must have more than one element!');
}
/**
* Options
* @private
*/
this._opts = opts;
opts = null, args = null;
this._setting();
this.fire('initialize');
this._renderWrapper();
this._initPlugins();
this._bindHandler();
this.fire('initialized');
// Autoplay mode
this._autoPlay();
};
/**
* version
* @type {string}
*/
iSlider.VERSION = '2.2.2';
/**
* Event white list
* @type {Array}
* @protected
*/
iSlider.EVENTS = [
'initialize',
'initialized',
'pluginInitialize',
'pluginInitialized',
'renderComplete',
'slide',
'slideStart',
'slideEnd',
'slideChange',
'slideChanged',
'slideRestore',
'slideRestored',
'loadData',
'reset',
'destroy'
];
/**
* Easing white list
* @type [Array, RegExp[]]
* @protected
*/
iSlider.EASING = [
['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'],
/cubic-bezier\(([^\d]*(\d+.?\d*)[^\,]*\,?){4}\)/
];
/**
* TAGS whitelist on fixpage mode
* @type {Array}
* @protected
*/
iSlider.FIX_PAGE_TAGS = ['SELECT', 'INPUT', 'TEXTAREA', 'BUTTON', 'LABEL'];
/**
* Scene node types
* @type {Object}
* @protected
* TODO Prepare for the migration Symbol ES6.
*/
iSlider.NODE_TYPE = {
unknown: 'unknown',
empty: 'empty',
pic: 'pic',
dom: 'dom',
html: 'html',
node: 'node',
element: 'element'
};
/**
* @returns {String}
* @private
*/
iSlider.TRANSITION_END_EVENT = null;
iSlider.BROWSER_PREFIX = null;
(function () {
var e = document.createElement('fakeElement');
[
['WebkitTransition', 'webkitTransitionEnd', 'webkit'],
['transition', 'transitionend', null],
['MozTransition', 'transitionend', 'moz'],
['OTransition', 'oTransitionEnd', 'o']
].some(function (t) {
if (e.style[t[0]] !== undefined) {
iSlider.TRANSITION_END_EVENT = t[1];
iSlider.BROWSER_PREFIX = t[2];
return true;
}
});
})();
/**
* Event match depending on the browser supported
* @type {{hasTouch, startEvt, moveEvt, endEvt, cancelEvt, resizeEvt}}
*/
iSlider.DEVICE_EVENTS = (function () {
// IOS desktop has touch events, make them busting
var hasTouch = !!(('ontouchstart' in global && !/Mac OS X /.test(global.navigator.userAgent)) || global.DocumentTouch && document instanceof global.DocumentTouch);
return {
hasTouch: hasTouch,
startEvt: hasTouch ? 'touchstart' : 'mousedown',
moveEvt: hasTouch ? 'touchmove' : 'mousemove',
endEvt: hasTouch ? 'touchend' : 'mouseup',
cancelEvt: hasTouch ? 'touchcancel' : 'mouseout',
resizeEvt: 'onorientationchange' in global ? 'orientationchange' : 'resize'
};
})();
/**
* Extend
* @public
*/
iSlider.extend = function () {
var main, extend, args = arguments;
switch (args.length) {
case 0:
return;
case 1:
main = iSlider.prototype;
extend = args[0];
break;
case 2:
main = args[0];
extend = args[1];
break;
}
for (var property in extend) {
if (extend.hasOwnProperty(property)) {
main[property] = extend[property];
}
}
};
/**
* Plugins
* @type {{}}
* @protected
*/
iSlider.plugins = {};
/**
* @param name
* @param plugin
* @public
*/
iSlider.regPlugin = function (name, plugin) {
iSlider.plugins[name] = iSlider.plugins[name] || plugin;
};
/**
* @param {String} prop
* @param {String} value
* @returns {String}
* @public
*/
iSlider.styleProp = function (prop, isDP) {
if (iSlider.BROWSER_PREFIX) {
if (!!isDP) {
return iSlider.BROWSER_PREFIX + IU(prop);
} else {
return '-' + iSlider.BROWSER_PREFIX + '-' + prop;
}
} else {
return prop;
}
};
/**
* @param {String} prop
* @param {HTMLElement} dom
* @param {String} value
* @public
*/
iSlider.setStyle = function (dom, prop, value) {
dom.style[iSlider.styleProp(prop, 1)] = value;
};
/**
* @param {String} prop
* @param {HTMLElement} dom
* @param {String} value
* @public
*/
iSlider.getStyle = function (dom, prop) {
return dom.style[iSlider.styleProp(prop, 1)];
};
/**
* @type {Object}
*
* @param {HTMLElement} dom The wrapper <li> element
* @param {String} axis Animate direction
* @param {Number} scale Outer wrapper
* @param {Number} i Wrapper's index
* @param {Number} offset Move distance
* @protected
*/
iSlider._animateFuncs = {
normal: (function () {
function normal(dom, axis, scale, i, offset) {
iSlider.setStyle(dom, 'transform', 'translateZ(0) translate' + axis + '(' + (offset + scale * (i - 1)) + 'px)');
}
normal.effect = iSlider.styleProp('transform');
return normal;
})()
};
/**
* This is a alias, conducive to compression
* @type {Object}
*/
var iSliderPrototype = iSlider.prototype;
/**
* & iSlider.extend
* @public
*/
iSliderPrototype.extend = iSlider.extend;
/**
* setting parameters for slider
* @private
*/
iSliderPrototype._setting = function () {
var self = this;
// --------------------------------
// - Status
// --------------------------------
/**
* The plugins
* @type {Array|{}|*}
* @private
*/
self._plugins = iSlider.plugins;
/**
* Extend animations
* @type {{default: Function}|*}
* @private
*/
self._animateFuncs = iSlider._animateFuncs;
/**
* @type {Boolean}
* @private
*/
self._holding = false;
/**
* @type {Boolean}
* @private
*/
self._locking = false;
/**
* @type {Array}
* @private
*/
self._intermediateScene = null;
/**
* @type {null}
* @private
*/
self._transitionEndHandler = null;
/**
* listener
* @type {{autoPlay: null, resize: null, transitionEnd: null}}
* @private
*/
self._LSN = {
autoPlay: null,
resize: null,
transitionEnd: null
};
/**
* current scene element: this.els[1]
* @type {null}
* @public
*/
self.currentEl = null;
/**
* Event handle
* @type {Object}
* @private
*/
self._EventHandle = {};
/**
* is on Moving
* @type {Boolean}
* @private
*/
self.onMoving = false;
/**
* is on Sliding
* @type {Boolean}
* @private
*/
self.onSliding = false;
/**
* animate direction
* @type {Number|null}
* @private
*/
self.direction = null;
// --------------------------------
// - Set options
// --------------------------------
var opts = this._opts;
/**
* dom element wrapping content
* @type {HTMLElement}
* @public
*/
self.wrap = opts.dom;
/**
* Data list
* @type {Array}
* @public
*/
self.data = opts.data;
/**
* default slide direction
* @type {Boolean}
* @public
*/
self.isVertical = !!opts.isVertical;
/**
* Overspread mode
* @type {Boolean}
* @public
*/
self.isOverspread = !!opts.isOverspread;
/**
* Play time gap
* @type {Number}
* @public
*/
self.duration = opts.duration || 2000;
/**
* start from initIndex or 0
* @type {Number}
* @public
*/
self.initIndex = opts.initIndex > 0 && opts.initIndex <= opts.data.length - 1 ? opts.initIndex : 0;
/**
* touchstart prevent default to fixPage
* @type {Boolean}
* @public
*/
self.fixPage = (function () {
var fp = opts.fixPage;
if (fp === false || fp === 0) {
return false;
}
if (isArray(fp) && fp.length > 0 || typeof fp === 'string' && fp !== '') {
return [].concat(fp).toString();
}
return true;
})();
/**
* Fill seam when render
* Default is false
* @type {Boolean}
* @public
*/
self.fillSeam = !!opts.fillSeam;
/**
* slideIndex
* @type {Number}
* @private
*/
self.slideIndex = self.slideIndex || self.initIndex || 0;
/**
* Axis
* @type {String}
* @public
*/
self.axis = self.isVertical ? 'Y' : 'X';
/**
* reverseAxis
* @type {String}
* @private
*/
self.reverseAxis = self.axis === 'Y' ? 'X' : 'Y';
/**
* Wrapper width
* @type {Number}
* @private
*/
self.width = typeof opts.width === 'number' ? opts.width : self.wrap.offsetWidth;
/**
* Wrapper height
* @type {Number}
* @private
*/
self.height = typeof opts.height === 'number' ? opts.height : self.wrap.offsetHeight;
/**
* Ratio height:width
* @type {Number}
* @private
*/
self.ratio = self.height / self.width;
/**
* Scale, size rule
* @type {Number}
* @private
*/
self.scale = self.isVertical ? self.height : self.width;
/**
* On slide offset position
* @type {{X: number, Y: number}}
* @private
*/
self.offset = self.offset || {X: 0, Y: 0};
/**
* Enable/disable touch events
* @type {Boolean}
* @private
*/
self.isTouchable = opts.isTouchable == null ? true : !!opts.isTouchable;
/**
* looping logic adjust
* @type {Boolean}
* @private
*/
self.isLooping = opts.isLooping && self.data.length > 1 ? true : false;
/**
* Damping force
* Effect in non-looping mode
* Range 0 ~ 1
* @type {Number}
* @private
*/
self.dampingForce = Math.max(0, Math.min(1, parseFloat(opts.dampingForce) || 0));
/**
* AutoPlay waitting milsecond to start
* @type {Number}
* @private
*/
self.delay = opts.delay || 0;
/**
* autoplay logic adjust
* @type {Boolean}
* @private
*/
self.isAutoplay = opts.isAutoplay && self.data.length > 1 ? true : false;
/**
* When autoplay is enabled.
* User click/tap behavior(eg: active a link), or if the page loses focus will stop autoplay.
* This configuration will attempt to restart autoplay after N milliseconds.
* ! AutoPlay will be forced to wake up, even when the user fill in a form item
* ! It will be blocked by "lock()"
* @type {number}
*/
self.wakeupAutoplayDazetime = opts.wakeupAutoplayDazetime > -1 ? parseInt(opts.wakeupAutoplayDazetime) : -1;
/**
* Animate type
* @type {String}
* @private
*/
self.animateType = opts.animateType in self._animateFuncs ? opts.animateType : 'normal';
/**
* @protected
*/
self._animateFunc = self._animateFuncs[self.animateType];
/**
* @private
*/
self._animateReverse = (function () {
var _ = [];
for (var type in self._animateFuncs) {
if (self._animateFuncs.hasOwnProperty(type) && self._animateFuncs[type].reverse) {
_.push(type);
}
}
return _;
})();
// little trick set, when you chooce tear & vertical same time
// iSlider overspread mode will be set true autometicly
if (self.isVertical && self.animateType === 'card') {
self.isOverspread = true;
}
/**
* Debug mode
* @type {Function}
* @private
*/
self.log = opts.isDebug ? function () {
global.console.log.apply(global.console, arguments);
} : noop;
/**
* Enable damping when slider meet the edge
* @param distance
* @returns {*}
* @private
*/
self._damping = (function () {
return function (distance) {
return Math.atan(Math.abs(distance) / self.scale) * 0.62 * (1 - self.dampingForce) * self.scale * (distance > 0 ? 1 : -1);
}
})();
/**
* animate process time (ms), default: 300ms
* @type {Number}
* @public
*/
self.animateTime = opts.animateTime != null && opts.animateTime > -1 ? opts.animateTime : 300;
/**
* animate effects, default: ease
* @type {String}
* @public
*/
self.animateEasing =
inArray(opts.animateEasing, iSlider.EASING[0])
|| iSlider.EASING[1].test(opts.animateEasing)
? opts.animateEasing
: 'ease';
/**
* Fix touch/mouse events
* @type {{hasTouch, startEvt, moveEvt, endEvt}}
* @private
*/
self.deviceEvents = iSlider.DEVICE_EVENTS;
/**
* Finger recognition range, prevent inadvertently touch
* @type {Number}
* @private
*/
self.fingerRecognitionRange = opts.fingerRecognitionRange > -1 ? parseInt(opts.fingerRecognitionRange) : 10;
/**
* Init events
* @type {{}}
* @private
*/
self.events = {};
// --------------------------------
// - Register events
// --------------------------------
iSlider.EVENTS.forEach(function (eventName) {
// TODO callback name of All-Lower-Case will be discarded
var fn = opts['on' + eventName.replace(/^\w{1}/, function (m) {
return m.toUpperCase();
})] || opts['on' + eventName.toLowerCase()];
typeof fn === 'function' && self.on(eventName, fn, 1);
});
// --------------------------------
// - Plugins
// --------------------------------
/**
* @type {Object}
* @private
*/
self.pluginConfig = (function () {
var config = {};
if (isArray(opts.plugins)) {
opts.plugins.forEach(function pluginConfigEach(plugin) {
if (isArray(plugin)) {
config[plugin[0]] = plugin.slice(1);
} else if (typeof plugin === 'string') {
config[plugin] = [];
}
});
}
return config;
})();
};
/**
* Init plugins
* @private
*/
iSliderPrototype._initPlugins = function () {
var config = this.pluginConfig;
var plugins = this._plugins;
for (var i in config) {
if (config.hasOwnProperty(i) && plugins.hasOwnProperty(i)) {
this.log('[INIT PLUGIN]:', i, plugins[i]);
plugins[i]
&& typeof plugins[i] === 'function'
&& typeof plugins[i].apply
&& plugins[i].apply(this, config[i]);
}
}
this.fire('pluginInitialized');
};
/**
* Get item type
* @param {Number} index
* @returns {String}
* @private
*/
iSliderPrototype._itemType = function (item) {
if (!isNaN(item)) {
item = this.data[item];
}
if (item.hasOwnProperty('type')) {
return item.type;
}
var content = item.content;
var _NT = iSlider.NODE_TYPE;
var type;
if (content == null) {
type = _NT.empty;
} else {
if (Boolean(content.nodeName) && Boolean(content.nodeType)) {
type = _NT.node;
} else if (typeof content === 'string') {
if (isUrl(content)) {
type = _NT.pic;
} else {
type = _NT.html;
}
} else {
type = _NT.unknown;
}
}
item.type = type;
return type;
};
/**
* render single item html by idx
* @param {HTMLElement} el ..
* @param {Number} dataIndex ..
* @private
*/
iSliderPrototype._renderItem = function (el, dataIndex) {
var item,
self = this,
len = this.data.length;
var insertImg = function renderItemInsertImg() {
var simg = ' src="' + item.content + '"';
// auto scale to full screen
if (item.height / item.width > self.ratio) {
simg += ' height="100%"';
} else {
simg += ' width="100%"';
}
if (self.isOverspread) {
el.style.cssText += 'background-image:url(' + item.content + ');background-repeat:no-repeat;background-position:50% 50%;background-size:cover';
simg += ' style="display:block;opacity:0;height:100%;width:100%;"';
}
// for right button, save picture
el.innerHTML = '<img' + simg + ' />';
};
// clean scene
el.innerHTML = '';
el.style.background = '';
// get the right item of data
if (!this.isLooping && this.data[dataIndex] == null) {
// Stop slide when item is empty
return;
}
else {
dataIndex = (len /* * Math.ceil(Math.abs(dataIndex / len))*/ + dataIndex) % len;
item = this.data[dataIndex];
}
var type = this._itemType(item);
var _NT = iSlider.NODE_TYPE;
this.log('[RENDER]:', type, dataIndex, item);
addClass(el, 'islider-' + type);
switch (type) {
case _NT.pic:
if (item.load === 2) {
insertImg();
}
else {
var currentImg = new Image();
currentImg.src = item.content;
currentImg.onload = function () {
item.height = currentImg.height;
item.width = currentImg.width;
insertImg();
item.load = 2;
};
}
break;
case _NT.dom:
case _NT.html:
el.innerHTML = item.content;
break;
case _NT.node:
case _NT.element:
// fragment, create container
if (item.content.nodeType === 11) {
var entity = document.createElement('div');
entity.appendChild(item.content);
item.content = entity;
}
el.appendChild(item.content);
break;
default:
// do nothing
break;
}
};
/**
* Postponing the intermediate scene rendering
* until the target scene is completely rendered (render in event slideChanged)
* to avoid a jumpy feel when switching between scenes
* given that the distance of sliding is more than 1.
* e.g. ```this.slideTo(>+-1)```
*
* @private
*/
iSliderPrototype._renderIntermediateScene = function () {
if (this._intermediateScene != null) {
this._renderItem.apply(this, this._intermediateScene);
this._intermediateScene = null;
}
};
/**
* Apply styles on changed
* @private
*/
iSliderPrototype._changedStyles = function () {
var slideStyles = ['islider-prev', 'islider-active', 'islider-next'];
this.els.forEach(function changeStypeEach(el, index) {
removeClass(el, slideStyles.join('|'));
addClass(el, slideStyles[index]);
this.fillSeam && this.originScale(el);
}.bind(this));
};
/**
* render list html
* @private
*/
iSliderPrototype._renderWrapper = function () {
//this.wrap.style.overflow = 'hidden';
// initail outer element
var outer;
if (this.outer) {
outer = this.outer;
outer.innerHTML = '';
} else {
outer = document.createElement('ul');
}
outer.className = 'islider-outer';
//outer.style.overflow = 'hidden';
// no need...
// outer.style.cssText += 'width:' + this.width + 'px;height:' + this.height + 'px';
// storage li elements, only store 3 elements to reduce memory usage
/**
* Slider elements x3
* @type {Array}
* @public
*/
this.els = [];
for (var i = 0; i < 3; i++) {
var li = document.createElement('li');
outer.appendChild(li);
this.els.push(li);
// prepare style animation
this._animateFunc(li, this.axis, this.scale, i, 0);
// auto overflow in none fixPage mode
if (!this.fixPage) {
li.style.overflow = 'auto';
}
this.isVertical && (this.animateType === 'rotate' || this.animateType === 'flip')
? this._renderItem(li, 1 - i + this.slideIndex)
: this._renderItem(li, i - 1 + this.slideIndex);
}
this._changedStyles();
if (this.fillSeam) {
this.els.forEach(function (el, i) {
addClass(el, 'islider-sliding' + (i === 1 ? '-focus' : ''));
});
}
// Preload picture [ may be pic :) ]
global.setTimeout(function () {
this._preloadImg(this.slideIndex);
}.bind(this), 200);
// append ul to div#canvas
if (!this.outer) {
/**
* @type {HTMLElement}
* @public
*/
this.outer = outer;
this.wrap.appendChild(outer);
}
this.currentEl = this.els[1];
this.fire('renderComplete', this.slideIndex, this.currentEl, this);
};
/**
* resetAnimation, slideTo每次切换使用不同的动画时调用
* @private
*/
iSliderPrototype._resetAnimation = function () {
var els = this.els;
for (var i = 0; i < 3; i++) {
els[i].style.cssText = '';
this._animateFunc(els[i], this.axis, this.scale, i, 0);
this.isVertical && (this.animateType === 'rotate' || this.animateType === 'flip')
? this._renderItem(els[i], 1 - i + this.slideIndex)
: this._renderItem(els[i], i - 1 + this.slideIndex);
}
}
/**
* Preload img when slideChange
* From current index +2, -2 scene
* @param {Number} dataIndex means which image will be load
* @private
*/
iSliderPrototype._preloadImg = function (dataIndex) {
if (this.data.length > 3) {
var data = this.data;
var len = data.length;
var self = this;
var loadImg = function preloadImgLoadingProcess(index) {
var item = data[index];
if (self._itemType(item) === 'pic' && !item.load) {
var preloadImg = new Image();
preloadImg.src = item.content;
preloadImg.onload = function () {
item.width = preloadImg.width;
item.height = preloadImg.height;
item.load = 2;
};
item.load = 1;
}
};
loadImg((dataIndex + 2) % len);
loadImg((dataIndex - 2 + len) % len);
}
};
/**
* watch transition end
* @private
*/
iSliderPrototype._watchTransitionEnd = function (squeezeTime, eventType) {
var cb = function () {
this._unWatchTransitionEnd();
if (eventType === 'slideChanged') {
this._changedStyles();
}
this.fire.call(this, eventType, this.slideIndex, this.currentEl, this);
this._renderIntermediateScene();
this.play();
this.onSliding = false;
this.direction = null;
}.bind(this);
if (iSlider.TRANSITION_END_EVENT) {
this.currentEl.addEventListener(iSlider.TRANSITION_END_EVENT, cb);
// keep handler and element
this._transitionEndHandler = {el: this.currentEl, handler: cb};
}
this._LSN.transitionEnd = global.setTimeout(cb, squeezeTime);
};
/**
* unwatch transition end
* @private
*/
iSliderPrototype._unWatchTransitionEnd = function () {
if (this._LSN.transitionEnd) {
global.clearTimeout(this._LSN.transitionEnd);
}
if (this._transitionEndHandler !== null) {
this._transitionEndHandler.el.removeEventListener(iSlider.TRANSITION_END_EVENT, this._transitionEndHandler.handler);
this._transitionEndHandler = null;
}
//this.onSliding = false;
};
/**
* bind all event handler, when on PC, disable drag event
* @private
*/
iSliderPrototype._bindHandler = function () {
var outer = this.outer;
var device = this.deviceEvents;
if (this.isTouchable) {
if (!device.hasTouch) {
outer.style.cursor = 'pointer';
// disable drag
outer.ondragstart = function (evt) {
if (evt) {
return false;
}
return true;
};
}
outer.addEventListener(device.startEvt, this);
outer.addEventListener(device.moveEvt, this);
outer.addEventListener(device.endEvt, this);
// Viscous drag adaptation
!device.hasTouch && outer.addEventListener('mouseout', this);
}
global.addEventListener(device.resizeEvt, this);
// Fix android device
global.addEventListener('focus', this, false);
global.addEventListener('blur', this, false);
};
/**
* Uniformity admin event
* Event router
* @param {Object} evt event object
* @protected
*/
iSliderPrototype.handleEvent = function (evt) {
var device = this.deviceEvents;
switch (evt.type) {
case 'mousedown':
// block mouse buttons except left
if (evt.button !== 0) break;
case 'touchstart':
this.startHandler(evt);
break;
case device.moveEvt:
this.moveHandler(evt);
break;
case device.endEvt:
case device.cancelEvt: // mouseout, touchcancel event, trigger endEvent
this.endHandler(evt);
break;
case device.resizeEvt:
this.resizeHandler();
break;
case 'focus':
this.play();
break;
case 'blur':
this.pause();
this._tryToWakeupAutoplay();
break;
}
};
/**
* touchstart callback
* @param {Object} evt event object
* @public
*/
iSliderPrototype.startHandler = function (evt) {
if (this.fixPage && iSlider.FIX_PAGE_TAGS.indexOf(evt.target.tagName.toUpperCase()) < 0 && !this._isItself(evt.target)) {
evt.preventDefault();
}
if (this._holding || this._locking) {
return;
}
var device = this.deviceEvents;
this.onMoving = true;
this.pause();
this.log('[EVENT]: start');
this.fire('slideStart', evt, this);
/**
* @type {Number}
* @private
*/
this.startTime = new Date().getTime();
/**
* @type {Number}
* @private
*/
this.startX = device.hasTouch ? evt.targetTouches[0].pageX : evt.pageX;
/**
* @type {Number}
* @private
*/
this.startY = device.hasTouch ? evt.targetTouches[0].pageY : evt.pageY;
};
/**
* touchmove callback
* @param {Object} evt event object
* @public
*/
iSliderPrototype.moveHandler = function (evt) {
if (!this.onMoving) {
return;
}
this.log('[EVENT]: moving');
var device = this.deviceEvents;
var len = this.data.length;
var axis = this.axis;
var reverseAxis = this.reverseAxis;
var offset = {};
if (evt.hasOwnProperty('offsetRatio')) {
offset[axis] = evt.offsetRatio * this.scale;
offset[reverseAxis] = 0;
} else {
offset.X = device.hasTouch ? (evt.targetTouches[0].pageX - this.startX) : (evt.pageX - this.startX);
offset.Y = device.hasTouch ? (evt.targetTouches[0].pageY - this.startY) : (evt.pageY - this.startY);
}
this.offset = offset;
evt.offsetRatio = offset[axis] / this.scale;
if (Math.abs(offset[axis]) - Math.abs(offset[reverseAxis]) > 10) {
evt.preventDefault();
this._unWatchTransitionEnd();
if (!this.isLooping) {
if (offset[axis] > 0 && this.slideIndex === 0 || offset[axis] < 0 && this.slideIndex === len - 1) {
offset[axis] = this._damping(offset[axis]);
}
}
this.els.forEach(function (item, i) {
item.style.visibility = 'visible';
iSlider.setStyle(item, 'transition', 'none');
this._animateFunc(item, axis, this.scale, i, offset[axis], offset[axis]);
this.fillSeam && this.seamScale(item);
}.bind(this));
this.fire('slide', evt, this);
}
};
/**
* touchend callback
* @param {Object} evt event object
* @public
*/
iSliderPrototype.endHandler = function (evt) {
if (!this.onMoving) {
return;
}
this.log('[EVENT]: end');
this.onMoving = false;
var offset = this.offset;
var axis = this.axis;
var boundary = this.scale / 2;
var endTime = new Date().getTime();
var FRR = this.fingerRecognitionRange;
// a quick slide time must under 300ms
// a quick slide should also slide at least 14 px
boundary = endTime - this.startTime > 300 ? boundary : 14;
var absOffset = Math.abs(offset[axis]);
var absReverseOffset = Math.abs(offset[this.reverseAxis]);
function dispatchLink(el) {
if (el != null) {
if (el.tagName === 'A') {
if (el.href) {
if (el.getAttribute('target') === '_blank') {
global.open(el.href);
} else {
global.location.href = el.href;
}
evt.preventDefault();
return false;
}
}
else if (el.tagName === 'LI' && el.className.search(/^islider\-/) > -1) {
return false;
}
else {
dispatchLink(el.parentNode);
}
}
}
this.fire('slideEnd', evt, this);
if (offset[axis] >= boundary && absReverseOffset < absOffset) {
this.slideTo(this.slideIndex - 1);
}
else if (offset[axis] < -boundary && absReverseOffset < absOffset) {
this.slideTo(this.slideIndex + 1);
}
else {
if (Math.abs(this.offset[axis]) >= FRR) {
this.slideTo(this.slideIndex);
}
}
// create sim tap event if offset < this.fingerRecognitionRange
if (Math.abs(this.offset[axis]) < FRR && this.fixPage && evt.target) {
dispatchLink(evt.target);
}
this.offset.X = this.offset.Y = 0;
this._tryToWakeupAutoplay();
};
/**
* resize callback
* @public
*/
iSliderPrototype.resizeHandler = function () {
var _L = this._LSN.resize;
var startTime = +new Date, _W, _H;
if (this.deviceEvents.hasTouch) {
// Fuck Android
_L && global.clearInterval(_L);
_L = global.setInterval(function () {
if (this.height !== this.wrap.offsetHeight || this.width !== this.wrap.offsetWidth) {
_L && global.clearInterval(_L);
_L = global.setInterval(function () {
if (_W === this.wrap.offsetWidth && _H === this.wrap.offsetHeight) {
_L && global.clearInterval(_L);
this.reset();
this.log('[EVENT]: resize');
} else {
_W = this.wrap.offsetWidth, _H = this.wrap.offsetHeight;
}
}.bind(this), 12);
} else {
if (+new Date - startTime >= 1000) {
_L && global.clearInterval(_L);
}
}
}.bind(this), 12);
} else {
_L && global.clearTimeout(_L);
_L = global.setTimeout(function () {
if (this.height !== this.wrap.offsetHeight || this.width !== this.wrap.offsetWidth) {
_L && global.clearInterval(_L);
this.reset();
this.log('[EVENT]: resize');
}
}.bind(this), 200);
}
};
/**
* slide logical, goto data index
* @param {Number} dataIndex the goto index
* @public
*/
iSliderPrototype.slideTo = function (dataIndex, opts) {
// stop auto play
if (this.isAutoplay) {
this.pause();
}
if (this._locking) {
return;
}
this.unhold();
this.onSliding = true;
var animateTime = this.animateTime;
var animateType = this.animateType;
var animateFunc = this._animateFunc;
var data = this.data;
var els = this.els;
var axis = this.axis;
var idx = dataIndex;
var n = dataIndex - this.slideIndex;
var offset = this.offset;
var eventType;
var squeezeTime = 0;
if (typeof opts === 'object') {
if (opts.animateTime > -1) {
animateTime = opts.animateTime;
}
if (typeof opts.animateType === 'string' && opts.animateType in this._animateFuncs) {
animateType = opts.animateType;
animateFunc = this._animateFuncs[animateType];
this._animateFunc = animateFunc;
this.animateType = animateType;
this._resetAnimation();
}
}
// In the slide process, animate time is squeezed
if (offset[axis] !== 0) {
squeezeTime = Math.abs(offset[axis]) / this.scale * animateTime;
}
if (Math.abs(n) > 1) {
this._renderItem(n > 0 ? this.els[2] : this.els[0], idx);
}
// preload when slide
this._preloadImg(idx);
// get right item of data
if (data[idx]) {
this.slideIndex = idx;
}
else {
if (this.isLooping) {
this.slideIndex = n > 0 ? 0 : data.length - 1;
}
else {
n = 0;
}
}
this.log('[SLIDE TO]: ' + this.slideIndex);
// keep the right order of items
var headEl, tailEl, direction;
// slidechange should render new item
// and change new item style to fit animation
if (n === 0) {
// Restore to current scene
eventType = 'slideRestore';
} else {
if ((this.isVertical && (inArray(animateType, this._animateReverse))) ^ (n > 0)) {
els.push(els.shift());
headEl = els[2];
tailEl = els[0];
direction = 1;
} else {
els.unshift(els.pop());
headEl = els[0];
tailEl = els[2];
direction = -1;
}
this.currentEl = els[1];
if (Math.abs(n) === 1) {
this._renderIntermediateScene();
this._renderItem(headEl, idx + n);
} else if (Math.abs(n) > 1) {
if ((this.isVertical && (inArray(animateType, this._animateReverse)))) {
this._renderItem(tailEl, idx + direction);
this._renderItem(els[1], idx);
this._intermediateScene = [headEl, idx - direction];
}
else {
this._renderItem(headEl, idx + direction);
this._intermediateScene = [tailEl, idx - direction];
}
}
iSlider.setStyle(headEl, 'transition', 'none');
// Minus squeeze time
squeezeTime = animateTime - squeezeTime;
eventType = 'slideChange';
// For seams
if (this.fillSeam) {
els.forEach(function (el) {
removeClass(el, 'islider-sliding|islider-sliding-focus');
});
addClass(this.currentEl, 'islider-sliding-focus');
addClass(headEl, 'islider-sliding');
}
this.direction = direction;
}
// do the trick animation
for (var i = 0; i < 3; i++) {
if (els[i] !== headEl) {
// Only applies their effects
iSlider.setStyle(els[i], 'transition', (animateFunc.effect || 'all') + ' ' + squeezeTime + 'ms ' + this.animateEasing);
}
animateFunc.call(this, els[i], axis, this.scale, i, 0, direction);
this.fillSeam && this.seamScale(els[i]);
}
this._watchTransitionEnd(squeezeTime, eventType + 'd');
this.fire(eventType, this.slideIndex, this.currentEl, this);
};
/**
* Slide to next scene
* @public
*/
iSliderPrototype.slideNext = function () {
this.slideTo.apply(this, [this.slideIndex + 1].concat(_A(arguments)));
};
/**
* Slide to previous scene
* @public
*/
iSliderPrototype.slidePrev = function () {
this.slideTo.apply(this, [this.slideIndex - 1].concat(_A(arguments)));
};
/**
* Register plugin (run time mode)
* @param {String} name
* @param {Function} plugin
* @param {...}
* @public
*/
iSliderPrototype.regPlugin = function () {
var args = _A(arguments);
var name = args.shift(),
plugin = args[0];
if (!this._plugins.hasOwnProperty(name) && typeof plugin !== 'function') {
return;
}
if (typeof plugin === 'function') {
this._plugins[name] = plugin;
args.shift();
}
// Auto enable and init plugin when at run time
if (!inArray(name, this._opts.plugins)) {
this._opts.plugins.push(args.length ? [].concat([name], args) : name);
typeof this._plugins[name] === 'function' && this._plugins[name].apply(this, args);
}
};
/**
* simple event delegate method
*
* @param {String} evtType event name
* @param {String} selector the simple css selector like jQuery
* @param {Function} callback event callback
* @public
*
* @alias iSliderPrototype.bind
*/
iSliderPrototype.bind = iSliderPrototype.delegate = function (evtType, selector, callback) {
function delegatedEventCallbackHandle(e) {
var evt = global.event ? global.event : e;
var target = evt.target;
var eleArr = document.querySelectorAll(selector);
for (var i = 0; i < eleArr.length; i++) {
if (target === eleArr[i]) {
callback.call(target);
break;
}
}
}
this.wrap.addEventListener(evtType, delegatedEventCallbackHandle, false);
var key = evtType + ';' + selector;
if (!this._EventHandle.hasOwnProperty(key)) {
this._EventHandle[key] = [
[callback],
[delegatedEventCallbackHandle]
];
} else {
this._EventHandle[key][0].push(callback);
this._EventHandle[key][1].push(delegatedEventCallbackHandle);
}
};
/**
* remove event delegate from wrap
*
* @param {String} evtType event name
* @param {String} selector the simple css selector like jQuery
* @param {Function} callback event callback
* @public
*
* @alias iSliderPrototype.unbind
*/
iSliderPrototype.unbind = iSliderPrototype.unDelegate = function (evtType, selector, callback) {
var key = evtType + ';' + selector;
if (this._EventHandle.hasOwnProperty(key)) {
var i = this._EventHandle[key][0].indexOf(callback);
if (i > -1) {
this.wrap.removeEventListener(evtType, this._EventHandle[key][1][i]);
this._EventHandle[key][0][i] = this._EventHandle[key][1][i] = null;
// delete this._EventHandle[key][0][i];
// delete this._EventHandle[key][1][i];
return true;
}
}
return false;
};
/**
* removeEventListener to release the memory
* @public
*/
iSliderPrototype.destroy = function () {
var outer = this.outer;
var device = this.deviceEvents;
this.fire('destroy');
// Clear events
if (this.isTouchable) {
outer.removeEventListener(device.startEvt, this);
outer.removeEventListener(device.moveEvt, this);
outer.removeEventListener(device.endEvt, this);
// Viscous drag unbind
!device.hasTouch && outer.removeEventListener('mouseout', this);
}
global.removeEventListener(device.resizeEvt, this);
global.removeEventListener('focus', this);
global.removeEventListener('blur', this);
var n;
// Clear delegate events
for (n in this._EventHandle) {
var handList = this._EventHandle[n][1];
for (var i = 0; i < handList.length; i++) {
if (typeof handList[i] === 'function') {
this.wrap.removeEventListener(n.substr(0, n.indexOf(';')), handList[i]);
}
}
}
this._EventHandle = null;
// Clear timer
for (n in this._LSN) {
this._LSN.hasOwnProperty(n) && this._LSN[n] && global.clearTimeout(this._LSN[n]);
}
this._LSN = null;
this.wrap.innerHTML = '';
};
/**
* Register event callback
* @param {String} eventName
* @param {Function} func
* @returns {Object} return this instance of iSlider
* @public
*/
iSliderPrototype.on = function (eventName, func, force) {
if (inArray(eventName, iSlider.EVENTS) && typeof func === 'function') {
!(eventName in this.events) && (this.events[eventName] = []);
if (!force) {
this.events[eventName].push(func);
} else {
this.events[eventName].unshift(func);
}
}
return this;
};
/**
* Find callback function position
* @param {String} eventName
* @param {Function} func
* @returns {Boolean}
* @public
*/
iSliderPrototype.has = function (eventName, func) {
if (eventName in this.events) {
return -1 < this.events[eventName].indexOf(func);
}
return false;
};
/**
* Remove event callback
* @param {String} eventName
* @param {Function} func
* @public
*/
iSliderPrototype.off = function (eventName, func) {
if (eventName in this.events) {
var index = this.events[eventName].indexOf(func);
if (index > -1) {
delete this.events[eventName][index];
}
}
};
/**
* Trigger event callbacks
* @param {String} eventName
* @param {*} args
* @public
*/
iSliderPrototype.fire = function (eventNames) {
var args = _A(arguments, 1);
eventNames.split(/\x20+/).forEach(function (eventName) {
this.log('[EVENT FIRE]:', eventName, args);
if (eventName in this.events) {
var funcs = this.events[eventName];
for (var i = 0; i < funcs.length; i++) {
typeof funcs[i] === 'function'
&& funcs[i].apply
&& funcs[i].apply(this, args);
}
}
}.bind(this));
};
/**
* reset & rerender
* @public
*/
iSliderPrototype.reset = function () {
this.pause();
this._unWatchTransitionEnd();
//this._setting();
this.width = typeof this._opts.width === 'number' ? this._opts.width : this.wrap.offsetWidth;
this.height = typeof this._opts.height === 'number' ? this._opts.height : this.wrap.offsetHeight;
this.ratio = this.height / this.width;
this.scale = this.isVertical ? this.height : this.width;
this._renderWrapper();
this._autoPlay();
this.fire('reset slideRestored', this.slideIndex, this.currentEl, this);
};
/**
* Reload Data & render
*
* @param {Array} data
* @param {Number} initIndex
* @public
*/
iSliderPrototype.loadData = function (data, initIndex) {
this.pause();
this._unWatchTransitionEnd();
this.slideIndex = initIndex || 0;
this.data = data;
this._renderWrapper();
this._autoPlay();
this.fire('loadData slideChanged', this.slideIndex, this.currentEl, this);
};
/**
* Add scenes to the end of the data datasheets
*
* @param {Object|Array} sceneData
* @description
* Object:
* {content:...}
* Array:
* [{content:...}, {content:...}, ...]
*/
iSliderPrototype.pushData = function (sceneData) {
if (sceneData == null) {
return;
}
var len = this.data.length;
this.data = this.data.concat(sceneData);
if (this.isLooping && this.slideIndex === 0) {
this._renderItem(this.els[0], this.data.length - 1);
} else if (len - 1 === this.slideIndex) {
this._renderItem(this.els[2], len);
this._autoPlay(); // restart
}
};
/**
* Add scenes to the head of the data datasheets
*
* @param {Object|Array} sceneData
* @description
* Object:
* {content:...}
* Array:
* [{content:...}, {content:...}, ...]
*/
iSliderPrototype.unshiftData = function (sceneData) {
if (sceneData == null) {
return;
}
if (!isArray(sceneData)) {
sceneData = [sceneData];
}
var n = sceneData.length;
this.data = sceneData.concat(this.data);
if (this.slideIndex === 0) {
this._renderItem(this.els[0], n - 1);
}
this.slideIndex += n;
};
/**
* auto check to play and bind events
* @private
*/
iSliderPrototype._autoPlay = function () {
this.delay > 0 ? global.setTimeout(this.play.bind(this), this.delay) : this.play();
};
/**
* try to restart autoplay
* @private
*/
iSliderPrototype._tryToWakeupAutoplay = function () {
if (~this.wakeupAutoplayDazetime) {
this.wakeupAutoplayDazetime > 0 ? global.setTimeout(this.play.bind(this), this.wakeupAutoplayDazetime) : this.play();
}
};
/**
* Start autoplay
* @public
*/
iSliderPrototype.play = function () {
this.pause();
// If not looping, stop playing when meet the end of data
if (this.isAutoplay && (this.isLooping || this.slideIndex < this.data.length - 1)) {
this._LSN.autoPlay = global.setTimeout(this.slideNext.bind(this), this.duration);
}
};
/**
* pause autoplay
* @public
*/
iSliderPrototype.pause = function () {
this._LSN.autoPlay && global.clearTimeout(this._LSN.autoPlay);
};
/**
* Maintaining the current scene
* Disable touch events, except for the native method.
* @public
*/
iSliderPrototype.hold = function () {
this._holding = true;
};
/**
* Release current scene
* unlock at same time
* @public
*/
iSliderPrototype.unhold = function () {
this._holding = false;
this.unlock();
};
/**
* You can't do anything on this scene
* lock native method calls
* hold at same time
* @public
*/
iSliderPrototype.lock = function () {
this.hold();
this._locking = true;
};
/**
* unlock native method calls
* @public
*/
iSliderPrototype.unlock = function () {
this._locking = false;
};
/**
* Fill the seam
* @param {HTMLElement} el
* @private
*/
iSliderPrototype.seamScale = function (el) {
var regex = /scale([XY]?)\(([^\)]+)\)/;
var transformStyle = iSlider.getStyle(el, 'transform');
if (regex.test(transformStyle)) {
iSlider.setStyle(el, 'transform', transformStyle.replace(regex, function (res, axis, scale) {
var sc = {};
if (axis) {
sc[axis] = parseFloat(scale);
return 'scale' + this.axis + '(' + (axis === this.axis ? 1.001 * sc[this.axis] : 1.001) + ')';
} else {
if (scale.indexOf(',') > -1) {
scale = scale.split(',');
sc.X = parseFloat(scale[0]);
sc.Y = parseFloat(scale[1]);
} else {
sc.Y = sc.X = parseFloat(scale);
}
sc[this.axis] *= 1.001;
return 'scale(' + sc.X + ', ' + sc.Y + ')';
}
}.bind(this)));
} else {
iSlider.setStyle(el, 'transform', transformStyle + ' scale' + this.axis + '(1.001)');
}
};
/**
* @param {HTMLElement} el
* @private
*/
iSliderPrototype.originScale = function (el) {
var regex = /([\x20]?scale)([XY]?)\(([^\)]+)\)/;
iSlider.setStyle(el, 'transform', iSlider.getStyle(el, 'transform').replace(regex, function (sc, res, axis, scale) {
sc = {};
if (axis) {
if (scale === '1.001') {
return '';
} else {
sc[axis] = parseFloat(scale);
return 'scale' + this.axis + '(' + (axis === this.axis ? sc[this.axis] / 1.001 : 1) + ')';
}
} else {
if (scale.indexOf(',') > -1) {
scale = scale.split(',');
sc.X = parseFloat(scale[0]);
sc.Y = parseFloat(scale[1]);
} else {
sc.Y = sc.X = parseFloat(scale);
}
sc[this.axis] /= 1.001;
return 'scale(' + sc.X + ', ' + sc.Y + ')';
}
}.bind(this)));
};
/**
* Whether this node in rule
*
* @param {HTMLElement} target
* @param {Array} rule
* @returns {boolean}
*/
iSliderPrototype._isItself = function (target) {
var rule = this.fixPage;
if (typeof rule === 'string') {
//get dom path
var path = [], parent = target, selector;
while (!hasClass(parent, 'islider-outer') && parent !== this.wrap) {
path.push(parent);
parent = parent.parentNode;
}
parent = path.pop();
if (path.length) {
try {
selector = Array.prototype.slice.call(parent.querySelectorAll(rule));
if (selector.length) {
for (var i = 0; i < path.length; i++) {
if (selector.indexOf(path[i]) > -1) {
return true;
}
}
}
} catch (e) {
return false;
}
}
}
return false;
}
/**
* Let target islider controls this one
*
* @param {iSlider} target
* @param {Object} how
* @public
*/
iSliderPrototype.subjectTo = function (target, how) {
if (!target instanceof iSlider) {
return;
}
var self = this;
self.animateTime = target.animateTime;
self.isLooping = target.isLooping;
self.isAutoplay = false;
target.on('slideStart', function (evt) {
self.startHandler(evt);
});
target.on('slide', function (evt) {
self.moveHandler(evt);
});
target.on('slideEnd', function (evt) {
self.endHandler(evt);
});
target.on('slideChange', function (i) {
var l = self.data.length;
var d = self.direction;
if (d > 0 && (i - self.slideIndex + l) % l === 1) {
self.slideNext();
} else if (d < 0 && (i - self.slideIndex - l) % l === -1) {
self.slidePrev();
}
});
target.on('slideRestore', function (i) {
if (self.slideIndex !== i) {
self.slideTo(i);
}
});
};
/* CommonJS */
if (typeof require === 'function' && typeof module === 'object' && module && typeof exports === 'object' && exports)
module.exports = iSlider;
/* AMD */
else if (typeof define === 'function' && define['amd'])
define(function () {
return iSlider;
});
/* Global */
else
global['iSlider'] = global['iSlider'] || iSlider;
})(window || this);