////////////////////////////////////////////////////////////////////////////////
//
// 處理 transition 動畫
//
// (簡化版)
// 把每次的動畫屬性都設為 all
// 就不用處理 css 與 transitionProperty 間的對應
//
// 2017-03-30
//
//
// 最大的問題是 >>
// 如何得知 transition 是否順利執行
// transitionend 能否正確啟動
//
////////////////////////////////////////////////////////////////////////////////
if (typeof Area88_ == 'undefined') {
if (typeof window.jQuery != 'undefined') {
Area88_ = window.jQuery;
} else {
Area88_ = {};
}
}
////////////////////////////////////////////////////////////////////////////////
(function ($) {
// class
// 處理動畫的類別
function SetTransition(dom) {
this.fn = SetTransition;
this.dom = dom;
// 使用者對動畫的設定
this.setting = {};
this.next = (typeof next === 'function' ? next : undefined);
this.timeHandle = {
delay: undefined,
before: undefined,
end: undefined
};
// 防止 endEvent 太早激發
// false 為解鎖
this.timeLock = false;
// 備份 this.setting.css
this.cssSetting = {};
}
/* ====================================================================== */
(function () {
// this._API = function(){};
// 只能 stop 自身下的動畫命令
this.stop = function (jumpToEnd) {
if (!this.dom.dataset.area88Animate) {
alert('no animation');
// 元素已經沒在運動
return;
}
this._resetEndEvent();
this._resetTimeHandle();
/* ---------------------------------- */
var style = this.dom.style;
if (jumpToEnd) {
// 下面順序不能變換
style.transition = 'none';
$(this.dom).redraw_();
} else {
// 從 style 取得非 transition 的屬性與質
// var styleProperty = this._getCurrentStyleProperty();
// 運動中的動畫屬性
var styleProperty = this.cssSetting;
var css = getComputedStyle(this.dom, null);
// 將 style 換成 現在的 css 值
for (var key in styleProperty) {
if (styleProperty.hasOwnProperty(key)) {
styleProperty[key] = css[key] || '';
}
}
$(this.dom).redraw_();
// 最重要的步驟
$(this.dom).css(styleProperty);
}
/* ---------------------------------- */
// 呼叫結束程序,但不執行 complete
this.setting.complete = undefined;
this._whenTransitionEnd();
};
/* ------------------------------------------------------------------ */
this.animate = function (setting, next) {
// debugger;
this.next = (typeof next === 'function' ? next : undefined);
this.setting = ($.isPlainObject(setting) ? setting : {});
// 過濾 this.setting.css 的設定
// 不能留有 transition 相關的設定
this._cssSetFilter();
/* ---------------------------------- */
if (!this._willAnimate()) {
// 若不符合動畫條件
console.log('cant animate');
this._animateNext();
return;
}
if(this.dom.dataset.area88Animate){
console.dir(this.setting.css);
console.dir(this.cssSetting);
console.log('-----------------');
// 若在移動中,又下命令
// 需判斷與先前的 style 設定是否相符
// 若完全相符,就不用往下
if(!this._willContinueAnimate()){
console.log('the same before .... no new setting');
return;
}
}
/* ================================== */
// 以下為確定動畫
// 複製 cssSetting
this.cssSetting = $.extend({}, this.setting.css);
// 前置動作
// 清理之前檢查(transitionEnd)的計時器
// 若不用 queue 在第一個動作尚未結束時,加入第2個動作
// 第一個的檢查計時器的時間將不對
this._resetTimeHandle();
this._resetEndEvent();
if (typeof this.setting.start === 'function') {
this.setting.start.call(this.dom);
}
// dom 未隱藏,且有設置動畫時間
// 執行運動
// 沒有設置動畫時間,就不會有動畫
if (this.setting.delay && !this.next) {
// 非隊列,有設定 delay
this.fn.delayTimeHandle = setTimeout(function () {
this._animate();
}.bind(this), this.setting.delay);
} else {
this._animate();
}
};
}).call(SetTransition.prototype);
/* ====================================================================== */
(function () {
// 執行運動
this._animate = function () {
var self = this;
// 符合動畫條件
// 若有設置顯示時間
// 若有顯示
// 若 opacity > 0
this._setTansition();
this._bindEvent();
/* ---------------------------------- */
// 計時器
var time_1 = this.setting.duration;
var time_2 = this.setting.duration + 50;
// 預防 endEvent 太早起動
this.timeLock = true;
this.timeHandle.before = setTimeout(function () {
self.timeHandle.before = undefined;
// 開鎖
self.timeLock = false;
// alert('unlock');
}, time_1);
// 預防 endEvent 過時沒啟動
this.timeHandle.end = setTimeout(function () {
// alert('check');
self.timeHandle.end = undefined;
// 觸發 endEvent
self._trigger($.support.transitionEnd);
}, time_2);
/* ---------------------------------- */
// 標注動畫中
// 才會知道正在動畫中
this.dom.dataset.area88Animate = (this.next ? 'queue' : 'noqueue');
$(this.dom).redraw_();
// 下面開始動畫
$(this.dom).css(this.cssSetting);
};
/* ------------------------------------------------------------------ */
// animate end
this._animateNext = function () {
this.setting = {};
if (typeof this.next === 'function') {
this.next();
} else {
return;
}
};
}).call(SetTransition.prototype);
/* ====================================================================== */
(function () {
// $.tsEndCheck_ 會設置計時器來檢查 transitionEnd 是否正常執行
// 在每次執行前,要清除上次設定的計時器
// 刪除之前(tsEndCheck_)的檢查
this._resetTimeHandle = function () {
clearTimeout(this.timeHandle.before);
clearTimeout(this.timeHandle.end);
clearTimeout(this.timeHandle.delay);
this.timeHandle.before = undefined;
this.timeHandle.end = undefined;
this.timeHandle.delay = undefined;
};
/* ------------------------------------------------------------------ */
// 移除之前設定的事件
// 萬一之前的(transition)尚未完成
// 又突然給一個命令
// 會造成之前的一些註冊事件重複發生
this._resetEndEvent = function () {
// transitionEnd 會執行的任務
$(this.dom).off($.support.transitionEnd + '.area88.complete');
};
/* ------------------------------------------------------------------ */
// 設置 transition
this._setTansition = function () {
// debugger;
var cssEase = $.transitionOption_.cssEase;
if (typeof cssEase[this.setting.easing] === 'string') {
this.setting.easing = cssEase[this.setting.easing];
}
var set = 'all';
// duration
set += ' ' + ((this.setting.duration / 1000) + 's');
// easing
set += ' ' + this.setting.easing;
// delay
set += ' 0s';
this.dom.style[$.support.transition] = set;
};
/* ------------------------------------------------------------------ */
this._whenTransitionEnd = function () {
// 清除 endEvent check
clearTimeout(this.timeHandle.end);
this.timeHandle.end = undefined;
$(this.dom).off($.support.transitionEnd + '.area88.complete');
// 去除動畫標記
// 表示動畫結束
delete this.dom.dataset.area88Animate;
// 還原原本的 transition 設置
this._resetTransitionSetting();
/* -----------------------------------*/
if (typeof this.setting.complete === 'function') {
// complete
// alert(self.setting.complete.toString());
this.setting.complete.call(self.dom);
}
/* -----------------------------------*/
this._animateNext();
};
/* ------------------------------------------------------------------ */
/**
* 綁定 transitionEnd 事件,才能順利驅動 complete
* @return [type] [description]
*/
this._bindEvent = function () {
var self = this;
// 綁定(transitionend.area88.complete)事件
$(this.dom).on($.support.transitionEnd + '.area88.complete', function (e) {
// alert('fire: ' + self.fn.timeLock);
if (self.timeLock) {
// 時間鎖未打開,太早起動
// e.stopPropagation();
return;
}
self._whenTransitionEnd();
});
};
/* ------------------------------------------------------------------ */
// 過濾 this.setting.css 的設定
this._cssSetFilter = function () {
// debugger;
var css2TransitionMap = $.transitionOption_.css2TransitionMap;
var keyList = Object.keys(this.setting.css);
keyList.forEach(function (key) {
// debugger;
this.setting.css[key] = this.setting.css[key].trim();
// 不能在css裡置動畫屬性
if (/transition/gi.test(key)) {
delete this.setting.css[key];
}
/* ---------------------------------- */
// 若是諸如 padding, margin 之類
// 必須轉換,主要方便與 css 判別,與 stop
// padding -> padding-top, padding-right.......
// style.padding 可能在 css 找不到正確數質...無法 stop
if (typeof css2TransitionMap[key] !== 'undefined') {
var tmpValue = this.setting.css[key];
delete this.setting.css[key];
css2TransitionMap[key].forEach(function (_key) {
this.setting.css[_key] = tmpValue;
});
}
}, this);
};
}).call(SetTransition.prototype);
/* ====================================================================== */
(function () {
// 判斷是否合乎動畫規則
// 若 cssSetting 的數質等於當前
// 若隱藏起來
this._willAnimate = function () {
// debugger;
var self = this,
css = getComputedStyle(this.dom, null);
var res = [];
// 是否隱藏中
if (/none/gi.test(css.display)) {
res.push(false);
}
/* ---------------------------------- */
// duration <= 0 不能動畫
if (this.setting.duration <= 0) {
res.push(false);
}
/* ---------------------------------- */
// 設定的數質是否與當前不同 syle => css
// 要不同才能動畫
var judge_1 = (function (self) {
// debugger;
for (var key in self.setting.css) {
// 紀錄 cssSetting 對應當前的 css 數值
var styleValue = self.setting.css[key];
var cssValue = css.getPropertyValue(key).trim();
var reg = new RegExp('^' + cssValue, 'i');
if (reg.test(styleValue)) {
continue;
} else {
// 有數值不同
return true;
}
} // for-loop
return false;
})(this);
res.push(judge_1);
/* ---------------------------------- */
return (res.indexOf(false) >= 0 ? false : true);
};
/* ------------------------------------------------------------------ */
// 若 dom 正在移動中
// 而 style 設定與上次相同
// 也沒有必要設定動畫
this._willContinueAnimate = function(){
debugger;
var judge_2 = (function (self) {
var settingNum_1 = Object.keys(self.cssSetting).length;
var settingNum_2 = Object.keys(self.setting.css).length;
if (settingNum_1 !== settingNum_2) {
// 若與上次 style 設定的數量不同
// 會動畫
return true;
} else {
for (var key in self.setting.css) {
var nowValue = self.setting.css[key];
var prevValue = self.cssSetting[key];
if (/\+=|-=/i.test(nowValue)) {
// style 的設定有與上次不同
return true;
} else {
// 用現在的 style 設定,比對上次的 style 設定
var reg = new RegExp(('^' + nowValue), 'i');
if (reg.test(prevValue)) {
continue;
} else {
return true;
}
}
}
return false;
}
})(this);
return judge_2;
};
/* ------------------------------------------------------------------ */
// 處理 trigger 功能
this._trigger = function (evenName, data) {
try {
$(this.dom).trigger_(evenName, data);
} catch (error) {
$(this.dom).trigger(evenName, data);
}
};
/* ------------------------------------------------------------------ */
// 清除動畫設定
this._resetTransitionSetting = function () {
var style = this.dom.style;
style.transitionProperty = '';
style.transitionDuration = '';
style.transitionDelay = '';
style.transitionTimingFunction = '';
style.transition = '';
};
}).call(SetTransition.prototype);
////////////////////////////////////////////////////////////////////////////
// 處理 .transition_() 進來的參數
// 方法的重載
function _getArguments(cssSetting, duration, easing, delay, callback) {
// debugger;
var option;
var arg = $.makeArray(arguments).filter(function (value) {
if (value == null) {
return false;
}
return true;
});
if (!$.isPlainObject(cssSetting)) {
throw new TypeError('arguments[0] must be plainObject');
return;
}
if (arg.length == 2 && $.isPlainObject(duration)) {
// .transition({}, option)
option = duration;
duration = undefined;
} else {
option = undefined;
if (typeof duration === 'function') {
callback = duration;
duration = undefined;
} else if (typeof easing === 'function') {
// .transition({}, duration, callback)
callback = easing;
easing = undefined;
} else if (typeof delay === 'function') {
// .transition({}, duration, easing, callback)
callback = delay;
delay = undefined;
} else if (typeof option === 'function') {
// .transition({}, duration, easing, delay, callback)
option = undefined;
callback = option;
}
}
/* ================================================================== */
var res = {
css: cssSetting,
queue: true,
duration: $.fx.speeds._default,
easing: $.transitionOption_.cssEase._default,
delay: 0,
start: function () { },
complete: function () { }
};
if ($.isPlainObject(option)) {
res = $.extend({}, res, option);
} else {
res.duration = (typeof duration === 'number' ? duration : res.duration);
res.easing = (typeof easing === 'string' ? easing : res.easing);
res.delay = (typeof delay === 'number' ? delay : res.delay);
res.complete = (typeof callback === 'function' ? callback : res.complete);
}
/* ---------------------------------- */
// 檢查 easing
var easingKeyList = Object.getOwnPropertyNames($.transitionOption_.cssEase);
if (easingKeyList.indexOf(res.easing) < 0 && !/^cubic-bezier\(.+\)&/gi.test(res.easing)) {
throw new TypeError('easing setting no support');
}
/* ---------------------------------- */
return res;
}
/* ====================================================================== */
// here
$.fn.transition_ = function (cssSetting, duration, easing, delay, option, callback) {
var self = this;
var setting = _getArguments(cssSetting, duration, easing, delay, option, callback);
this.each(function (index, dom) {
setting.dom = dom;
$.transition_(setting);
});
return this;
};
/* ====================================================================== */
// here
$.transition_ = function (dom, cssSetting, duration, easing, delay, option, callback) {
var setting;
var _cssSetting, _queue, _duration, _easing, _delay, _complete;
if ($.isPlainObject(dom)) {
// 內部傳送
setting = dom;
dom = setting.dom;
} else {
setting = _getArguments(cssSetting, duration, easing, delay, option, callback);
setting.dom = dom;
}
/* ---------------------------------- */
// debugger;
var xman;
if (xman = $(dom).data('_setTransition_')) { } else {
xman = new SetTransition(dom);
$(dom).data('_setTransition_', xman);
}
if (setting.queue) {
// 若要設置 queue
if (setting.delay) {
// 若有設置 delay
$(dom).delay(setting.delay);
}
$(dom).queue(function (next) {
xman.animate(setting, next);
});
} else {
// 若不用 queue
if (setting.delay) {
setTimeout(function () {
xman.animate(setting);
}, setting.delay);
} else {
xman.animate(setting);
}
}
};
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/**
* @param {string} queue: 隊列的名稱
*/
$.fn.transition_stop_ = function (queue, clearQueue, jumpToEnd) {
return this.each(function (i, dom) {
$.transition_stop_(dom, queue, clearQueue, jumpToEnd);
});
};
/* ---------------------------------------------------------------------- */
/**
* 必須清除計時器
* 清除 transiotnEnd.area88.complete 事件
* 清除 delay 計時器
*
* @param {[type]} dom [description]
* @param {[type]} queue [queueName]
* @param {[type]} clearQueue [是否要清除 queue]
* @param {[type]} jumpToEnd [是否直接跳到設定值]
* @return [type] [description]
*/
$.transition_stop_ = function (dom, queue, clearQueue, jumpToEnd) {
// 清空隊列
if (typeof queue === 'string') {
// ( [queue ] [, clearQueue ] [, jumpToEnd ] )
clearQueue = clearQueue || false;
jumpToEnd = jumpToEnd || false;
} else {
// ( [clearQueue ] [, jumpToEnd ] )
queue = 'fx';
jumpToEnd = clearQueue || false;
clearQueue = queue || false;
}
/* ---------------------------------- */
if (clearQueue) {
$(dom).clearQueue(queue);
}
/* ---------------------------------- */
var xman = $(dom).data('_setTransition_');
// 暫停
if (jumpToEnd) {
xman && (xman.stop(true));
} else {
xman && (xman.stop());
}
}
})(Area88_);
////////////////////////////////////////////////////////////////////////////////