/*! * jQuery Animate v1.8.9 - By CSS3 transition * (c) 2014-2017 BaiJunjie * MIT Licensed. * * https://github.com/baijunjie/jquery.animate */ (function(root, factory) { 'use strict'; if (typeof module === 'object' && typeof exports === 'object') { module.exports = factory(require('jquery')); } else if (typeof define === 'function' && define.amd) { define(['jquery'], factory); } else { factory(root.jQuery); } }(this, function($) { 'use strict'; // jQuery 3.0.0 以及之后的版本已经兼容 requestAnimationFrame API if (compareVersion('3.0.0', $.fn.jquery) > 0) { // Integration jQuery requestAnimationFrame - v0.1.3pre // - https://github.com/gnarf37/jquery-requestAnimationFrame (function(jQuery) { var animating, lastTime = 0, vendors = ['webkit', 'moz'], requestAnimationFrame = window.requestAnimationFrame, cancelAnimationFrame = window.cancelAnimationFrame; for(; lastTime < vendors.length && !requestAnimationFrame; lastTime++) { requestAnimationFrame = window[ vendors[lastTime] + "RequestAnimationFrame" ]; cancelAnimationFrame = cancelAnimationFrame || window[ vendors[lastTime] + "CancelAnimationFrame" ] || window[ vendors[lastTime] + "CancelRequestAnimationFrame" ]; } function raf() { if ( animating ) { requestAnimationFrame( raf ); jQuery.fx.tick(); } } if ( requestAnimationFrame ) { // use rAF window.requestAnimationFrame = requestAnimationFrame; window.cancelAnimationFrame = cancelAnimationFrame; jQuery.fx.timer = function( timer ) { if ( timer() && jQuery.timers.push( timer ) && !animating ) { animating = true; raf(); } }; jQuery.fx.stop = function() { animating = false; }; } }($)); } // Integration jQuery Easing v1.3 // - http://gsgd.co.uk/sandbox/jquery/easing/ $.easing['jswing'] = $.easing['swing']; $.extend( $.easing, { def: 'easeOutQuad', swing: function (x, t, b, c, d) { return $.easing[$.easing.def](x, t, b, c, d); }, easeInQuad: function (x, t, b, c, d) { return c*(t/=d)*t + b; }, easeOutQuad: function (x, t, b, c, d) { return -c *(t/=d)*(t-2) + b; }, easeInOutQuad: function (x, t, b, c, d) { if ((t/=d/2) < 1) return c/2*t*t + b; return -c/2 * ((--t)*(t-2) - 1) + b; }, easeInCubic: function (x, t, b, c, d) { return c*(t/=d)*t*t + b; }, easeOutCubic: function (x, t, b, c, d) { return c*((t=t/d-1)*t*t + 1) + b; }, easeInOutCubic: function (x, t, b, c, d) { if ((t/=d/2) < 1) return c/2*t*t*t + b; return c/2*((t-=2)*t*t + 2) + b; }, easeInQuart: function (x, t, b, c, d) { return c*(t/=d)*t*t*t + b; }, easeOutQuart: function (x, t, b, c, d) { return -c * ((t=t/d-1)*t*t*t - 1) + b; }, easeInOutQuart: function (x, t, b, c, d) { if ((t/=d/2) < 1) return c/2*t*t*t*t + b; return -c/2 * ((t-=2)*t*t*t - 2) + b; }, easeInQuint: function (x, t, b, c, d) { return c*(t/=d)*t*t*t*t + b; }, easeOutQuint: function (x, t, b, c, d) { return c*((t=t/d-1)*t*t*t*t + 1) + b; }, easeInOutQuint: function (x, t, b, c, d) { if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b; return c/2*((t-=2)*t*t*t*t + 2) + b; }, easeInSine: function (x, t, b, c, d) { return -c * Math.cos(t/d * (Math.PI/2)) + c + b; }, easeOutSine: function (x, t, b, c, d) { return c * Math.sin(t/d * (Math.PI/2)) + b; }, easeInOutSine: function (x, t, b, c, d) { return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; }, easeInExpo: function (x, t, b, c, d) { return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; }, easeOutExpo: function (x, t, b, c, d) { return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; }, easeInOutExpo: function (x, t, b, c, d) { if (t==0) return b; if (t==d) return b+c; if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; }, easeInCirc: function (x, t, b, c, d) { return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; }, easeOutCirc: function (x, t, b, c, d) { return c * Math.sqrt(1 - (t=t/d-1)*t) + b; }, easeInOutCirc: function (x, t, b, c, d) { if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; }, easeInElastic: function (x, t, b, c, d) { var s=1.70158;var p=0;var a=c; if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; if (a < Math.abs(c)) { a=c; var s=p/4; } else var s = p/(2*Math.PI) * Math.asin (c/a); return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; }, easeOutElastic: function (x, t, b, c, d) { var s=1.70158;var p=0;var a=c; if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; if (a < Math.abs(c)) { a=c; var s=p/4; } else var s = p/(2*Math.PI) * Math.asin (c/a); return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b; }, easeInOutElastic: function (x, t, b, c, d) { var s=1.70158;var p=0;var a=c; if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5); if (a < Math.abs(c)) { a=c; var s=p/4; } else var s = p/(2*Math.PI) * Math.asin (c/a); if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b; }, easeInBack: function (x, t, b, c, d, s) { if (s == undefined) s = 1.70158; return c*(t/=d)*t*((s+1)*t - s) + b; }, easeOutBack: function (x, t, b, c, d, s) { if (s == undefined) s = 1.70158; return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; }, easeInOutBack: function (x, t, b, c, d, s) { if (s == undefined) s = 1.70158; if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; }, easeInBounce: function (x, t, b, c, d) { return c - $.easing.easeOutBounce (x, d-t, 0, c, d) + b; }, easeOutBounce: function (x, t, b, c, d) { if ((t/=d) < (1/2.75)) { return c*(7.5625*t*t) + b; } else if (t < (2/2.75)) { return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; } else if (t < (2.5/2.75)) { return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; } else { return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; } }, easeInOutBounce: function (x, t, b, c, d) { if (t < d/2) return $.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b; return $.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b; } }); // 当浏览器不支持 transition 时,如果设置了以下的 Easing,可以使 animate 不至于报错,达到向下兼容的目的 $.extend( $.easing, { ease: function (x, t, b, c, d) { return $.easing.easeInOutCubic(x, t, b, c, d); }, easeIn: function (x, t, b, c, d) { return $.easing.easeInSine(x, t, b, c, d); }, easeOut: function (x, t, b, c, d) { return $.easing.easeOutSine(x, t, b, c, d); }, easeInOut: function (x, t, b, c, d) { return $.easing.easeInOutSine(x, t, b, c, d); } }); var testElem = document.createElement('div'), $testElem = $(testElem); // 返回支持的属性名 function getSupportPropertyName(prop) { if (prop in testElem.style) return prop; var testProp = prop.charAt(0).toUpperCase() + prop.substr(1), prefixs = [ 'Webkit', 'Moz', 'ms', 'O' ]; for (var i = 0, l = prefixs.length; i < l; i++) { var prefixProp = prefixs[i] + testProp; if (prefixProp in testElem.style) { return prefixProp; } } } // 检查是否支持3D function checkTransform3dSupport() { testElem.style[support.transform] = ''; testElem.style[support.transform] = 'rotateY(90deg)'; return testElem.style[support.transform] !== ''; } // 检查浏览器的 transition 支持 var support = {}; support.transform = getSupportPropertyName('transform'); if (!support.transform) return $; support.transformOrigin = getSupportPropertyName('transformOrigin'); support.transformStyle = getSupportPropertyName('transformStyle'); support.perspective = getSupportPropertyName('perspective'); support.perspectiveOrigin = getSupportPropertyName('perspectiveOrigin'); support.backfaceVisibility = getSupportPropertyName('backfaceVisibility'); support.filter = getSupportPropertyName('filter'); support.transition = getSupportPropertyName('transition'); support.transform3d = checkTransform3dSupport(); // 将检测到的支持结果写入 $.support for (var key in support) { if (typeof $.support[key] === 'undefined') { $.support[key] = support[key]; } } // 缓动列表 $.cssEase = { '_default' : 'swing', 'swing' : 'easeOutQuad', // 和 jQuery Easing 相同,查看详情 https://github.com/gdsmith/jquery.easing 'linear' : 'cubic-bezier(0,0,1,1)', 'ease' : 'cubic-bezier(.25,.1,.25,1)', 'easeIn' : 'cubic-bezier(.42,0,1,1)', 'easeOut' : 'cubic-bezier(0,0,.58,1)', 'easeInOut' : 'cubic-bezier(.42,0,.58,1)', 'easeInCubic' : 'cubic-bezier(.550,.055,.675,.190)', 'easeOutCubic' : 'cubic-bezier(.215,.61,.355,1)', 'easeInOutCubic' : 'cubic-bezier(.645,.045,.355,1)', 'easeInCirc' : 'cubic-bezier(.6,.04,.98,.335)', 'easeOutCirc' : 'cubic-bezier(.075,.82,.165,1)', 'easeInOutCirc' : 'cubic-bezier(.785,.135,.15,.86)', 'easeInExpo' : 'cubic-bezier(.95,.05,.795,.035)', 'easeOutExpo' : 'cubic-bezier(.19,1,.22,1)', 'easeInOutExpo' : 'cubic-bezier(1,0,0,1)', 'easeInQuad' : 'cubic-bezier(.55,.085,.68,.53)', 'easeOutQuad' : 'cubic-bezier(.25,.46,.45,.94)', 'easeInOutQuad' : 'cubic-bezier(.455,.03,.515,.955)', 'easeInQuart' : 'cubic-bezier(.895,.03,.685,.22)', 'easeOutQuart' : 'cubic-bezier(.165,.84,.44,1)', 'easeInOutQuart' : 'cubic-bezier(.77,0,.175,1)', 'easeInQuint' : 'cubic-bezier(.755,.05,.855,.06)', 'easeOutQuint' : 'cubic-bezier(.23,1,.32,1)', 'easeInOutQuint' : 'cubic-bezier(.86,0,.07,1)', 'easeInSine' : 'cubic-bezier(.47,0,.745,.715)', 'easeOutSine' : 'cubic-bezier(.39,.575,.565,1)', 'easeInOutSine' : 'cubic-bezier(.445,.05,.55,.95)', 'easeInBack' : 'cubic-bezier(.6,-.28,.735,.045)', 'easeOutBack' : 'cubic-bezier(.175, .885,.32,1.275)', 'easeInOutBack' : 'cubic-bezier(.68,-.55,.265,1.55)' }; // 转换easing为贝塞尔函数 // // 'swing' => 'cubic-bezier(.25,.46,.45,.94)' // function convertEase(easing) { if (typeof easing !== 'string') return; if (easing.indexOf('cubic-bezier') !== 0) { easing = $.cssEase[easing]; return convertEase(easing); } return easing; } // ## 'transform' CSS hook // // $('div').css({ transform: 'rotate(90deg)' }); // $('div').css('transform'); => { rotate: '90deg' } // $.cssHooks.transform = { get: function(elem) { return $.data(elem, 'bjj-transform') || new Transform(); }, set: function(elem, v) { var value = v; if (!(value instanceof Transform)) { value = new Transform(value); } elem.style[support.transform] = value.toString(); $.data(elem, 'bjj-transform', value); } }; // jQuery 1.8- 不支持这些属性的前缀转换 if (compareVersion('1.8', $.fn.jquery) > 0) { // ## 'transformOrigin' CSS hook // // $('div').css({ transformOrigin: '0 0' }); // $.cssHooks.transformOrigin = { get: function(elem) { return elem.style[support.transformOrigin]; }, set: function(elem, value) { elem.style[support.transformOrigin] = value; } }; // ## 'transformStyle' CSS hook // // $('div').css({ transformStyle: 'preserve-3d' }); // $.cssHooks.transformStyle = { get: function(elem) { return elem.style[support.transformStyle]; }, set: function(elem, value) { elem.style[support.transformStyle] = value; } }; // ## 'perspective' CSS hook // // $('div').css({ perspective: '1000px' }); // $.cssHooks.perspective = { get: function(elem) { return elem.style[support.perspective]; }, set: function(elem, value) { elem.style[support.perspective] = value; } }; // ## 'perspectiveOrigin' CSS hook // // $('div').css({ perspectiveOrigin: '100px 100px' }); // $.cssHooks.perspectiveOrigin = { get: function(elem) { return elem.style[support.perspectiveOrigin]; }, set: function(elem, value) { elem.style[support.perspectiveOrigin] = value; } }; // ## 'backfaceVisibility' CSS hook // // $('div').css({ backfaceVisibility: 'hidden' }); // $.cssHooks.backfaceVisibility = { get: function(elem) { return elem.style[support.backfaceVisibility]; }, set: function(elem, value) { elem.style[support.backfaceVisibility] = value; } }; // ## 'transition' CSS hook // // $('div').css({ transition: 'all 0 ease 0' }); // $.cssHooks.transition = { get: function(elem) { return elem.style[support.transition]; }, set: function(elem, value) { elem.style[support.transition] = value; } }; // ## 'filter' CSS hook // // $('div').css({ filter: 'blur(10px)' }); // $.cssHooks.filter = { get: function(elem) { return elem.style[support.filter]; }, set: function(elem, value) { elem.style[support.filter] = value; } }; } // ## compare version // // a = '1.11.1', b = '1.8.2' // a > b return 1 // a < b return -1 // a = b return 0 // function compareVersion(a, b) { var aa = a.split('.'), bb = b.split('.'), al = aa.length, bl = bb.length, len = Math.max(al, bl), aInt, bInt; for (; len > 0; len--) { aInt = parseInt(aa.shift()) || 0; bInt = parseInt(bb.shift()) || 0; if (aInt > bInt) return 1; else if (aInt < bInt) return -1; } return 0; } // 定义所有变换属性的 transition-property var propertyMap = {}; // Register other CSS hooks registerCssHook('x'); registerCssHook('y'); registerCssHook('z'); registerCssHook('translateX'); registerCssHook('translateY'); registerCssHook('translateZ'); registerCssHook('translate'); registerCssHook('translate3d'); registerCssHook('scale'); registerCssHook('scaleX'); registerCssHook('scaleY'); registerCssHook('scaleZ'); registerCssHook('scale3d'); registerCssHook('rotate'); registerCssHook('rotateX'); registerCssHook('rotateY'); registerCssHook('rotateZ'); registerCssHook('rotate3d'); registerCssHook('skew'); registerCssHook('skewX'); registerCssHook('skewY'); registerCssHook('pers'); function registerCssHook(prop, isPixels) { // 所有属性都不应该被强制添加px单位,即使是 translate,因为它也可能是百分比 if (!isPixels) { $.cssNumber[prop] = true; } propertyMap[prop] = support.transform; $.cssHooks[prop] = { get: function(elem) { var t = $.css(elem, 'transform'); return t.get(prop); }, set: function(elem, value) { var t = $.css(elem, 'transform'); t.setFromString(prop, value); $.style(elem, 'transform', t); } }; } // ## Transform class // // var t = new Transform('rotate(90) scale(4)'); // // Set properties // // t.set('rotate', 40) // // Get properties // // t.rotate => '40deg' // t.scale => '4' // // The output string // // t.toString() => 'rotate(40deg) scale(4)' // function Transform(str) { if (typeof str === 'string') { this.parse(str); } } Transform.prototype = { // ### setFromString() // // t.setFromString('scale', '2,4'); => ['scale', '2', '4'] // t.setFromString('scale', [,4]); => ['scale', null, '4'] // setFromString: function(prop, val) { var args; if ($.isArray(val)) { for (var i = 0; i < 3; i++) { if (val[i] === undefined) val[i] = null; } args = val; } else { args = (typeof val === 'string') ? val.split(',') : [val]; } args.unshift(prop); Transform.prototype.set.apply(this, args); }, set: function(prop) { var args = Array.prototype.slice.call(arguments, 1); if (this.setter[prop]) { this.setter[prop].apply(this, args); } else { this[prop] = args.join(','); } }, get: function(prop) { if (this.getter[prop]) { return this.getter[prop].call(this); } else { return this[prop]; } }, setter: { // ### x / y / z // // .css({ x: 4 }) => 'translate(4px, 0)' // .css({ y: 10 }) => 'translate(4px, 10px)' // .css({ z: 5 }) => 'translate(4px, 10px) translateZ(5px)' // x: function(x) { this.set('translate', x, null); }, y: function(y) { this.set('translate', null, y); }, z: function(z) { this.setProp('translateZ', z, 'px'); }, translateX: function(x) { this.set('x', x); }, translateY: function(y) { this.set('y', y); }, translateZ: function(z) { this.set('z', z); }, // ### translate // // .css({ translate: '2, 5' }) => 'translate(2px, 5px)' // .css({ translate: '' }) => remove 'translate(2px, 5px)' // translate: function(x, y) { if (y === undefined) { y = x; } this.setDoubleProp('translate', x, y, 'px'); }, // ### translate3d // // .css('translate3d', [100,200,300]); => 'translate(100px, 200px) translateZ(300px)' // translate3d: function(x, y, z) { if (y === undefined && z === undefined) { z = y = x; } this.set('translate', x, y); this.set('z', z); }, // ### scale // // .css({ scale: 3 }) => 'scale(3)' // .css({ scale: '3,2' }) => 'scale(3,2)' // scale: function(x, y) { if (y === undefined) { y = x; } this.setDoubleProp('scale', x, y, ''); }, // ### scale3d // // .css('scale3d', [1,2,3]); => 'scale(1, 2) scaleZ(3)' // scale3d: function(x, y, z) { if (y === undefined && z === undefined) { z = y = x; } this.set('scale', x, y); this.set('scaleZ', z); }, scaleX: function(x) { this.set('scale', x, null); }, scaleY: function(y) { this.set('scale', null, y); }, scaleZ: function(z) { this.setProp('scaleZ', z, ''); }, // ### rotate // // .css({ rotate: 30 }) // .css({ rotate: '30' }) // .css({ rotate: '30deg' }) // rotate: function(angle) { this.setProp('rotate', angle, 'deg'); }, rotateX: function(angle) { this.setProp('rotateX', angle, 'deg'); }, rotateY: function(angle) { this.setProp('rotateY', angle, 'deg'); }, rotateZ: function(angle) { this.set('rotate', angle); }, rotate3d: function(x, y, z) { if (y === undefined && z === undefined) { z = y = x; } this.set('rotateX', x); this.set('rotateY', y); this.set('rotate', z); }, skew: function(x, y) { if (y === undefined) { y = x; } this.set('skewX', x); this.set('skewY', y); }, skewX: function(x) { this.setProp('skewX', x, 'deg'); }, skewY: function(y) { this.setProp('skewY', y, 'deg'); }, // {pers: 100} => transform: perspective(100px); pers: function(pers) { this.setProp('perspective', pers, 'px'); } }, getter: { x: function() { return this._translateX || '0'; }, y: function() { return this._translateY || '0'; }, z: function() { return this.translateZ || '0'; }, translateX: function() { return this.get('x'); }, translateY: function() { return this.get('y'); }, translateZ: function() { return this.get('z'); }, translate: function() { return [this.get('x'), this.get('y')]; }, translate3d: function() { return [this.get('x'), this.get('y'), this.get('z')]; }, scale: function() { var x = this.get('scaleX'), y = this.get('scaleY'), s = [x, y]; // '2,2' => '2' // '2,1' => ['2','1'] return (s[0] === s[1]) ? s[0] : s; }, scale3d: function() { var x = this.get('scaleX'), y = this.get('scaleY'), z = this.get('scaleZ'), s = [x, y, z]; // '2,1,2' => ['2','1','2'] return s; }, scaleX: function() { return this._scaleX || '1'; }, scaleY: function() { return this._scaleY || '1'; }, scaleZ: function() { return this.scaleZ || '1'; }, rotate: function(theta) { return this.rotate || '0'; }, rotateX: function(theta) { return this.rotateX || '0'; }, rotateY: function(theta) { return this.rotateY || '0'; }, rotateZ: function(theta) { return this.get('rotate'); }, rotate3d: function() { return [this.get('rotateX'), this.get('rotateY'), this.get('rotate')]; }, skew: function() { return [this.get('skewX'), this.get('skewY')]; }, skewX: function() { return this.skewX || 0; }, skewY: function() { return this.skewY || 0; }, // .css('pers', 100).css('pers') => 100px pers: function() { return this.perspective || 0; } }, // ### setProp() // If the property value is an empty string, the attributes are removed // If the attribute values are not legal, ignore Settings // // .css({'rotate': 30}).css({'rotate': ''}) => remove 'rotate(30deg)' // .css({'rotate': 30}).css({'rotate': null}) => 'rotate(30deg)' // setProp: function(prop, value, u) { if (value !== undefined && value !== '') { if (isNaN(parseFloat(value))) { value = undefined; } } if (value === '') { delete this[prop]; } else if (value !== undefined) { this[prop] = unit(value, u); } }, // ### setDoubleProp() // If both the attribute value is empty string, the attributes are removed // If one of the attribute value is empty string, is set to the default value // If the attribute values are not legal, ignore Settings // // .css({'scaleX': 3}).css({'scale': ''}) => remove 'scale(3, 1)' // .css({'scaleX': 3}).css({'scale': ['',4]}) => 'scale(1, 4)' // .css({'scaleX': 3}).css({'scale': [null,4]}) => 'scale(3, 4)' // // Note // .css({'translate3d': '2,,'}) === .css({'translate3d': [2, '', '']}) // .css({'translate3d': [2,,,]}) === .css({'translate3d': '2, null, null'}) // setDoubleProp: function(prop, value1, value2, u) { if (this['_' + prop + 'X'] === undefined) { this['_' + prop + 'X'] = this.get(prop + 'X'); } if (this['_' + prop + 'Y'] === undefined) { this['_' + prop + 'Y'] = this.get(prop + 'Y'); } if (value1 !== undefined && value1 !== '') { if (isNaN(parseFloat(value1))) { value1 = undefined; } } if (value2 !== undefined && value2 !== '') { if (isNaN(parseFloat(value2))) { value2 = undefined; } } if (value1 === '' && value2 === '') { delete this['_' + prop + 'X']; delete this['_' + prop + 'Y']; delete this[prop]; } else { if (value1 === '') { delete this['_' + prop + 'X']; value1 = this.get(prop + 'X'); } else if (value2 === '') { delete this['_' + prop + 'Y']; value2 = this.get(prop + 'Y'); } if (value1 !== undefined) { this['_' + prop + 'X'] = unit(value1, u); } if (value2 !== undefined) { this['_' + prop + 'Y'] = unit(value2, u); } if (prop === 'scale') { this[prop] = this['_' + prop + 'X'] === this['_' + prop + 'Y'] ? this['_' + prop + 'X'] : this['_' + prop + 'X'] + ',' + this['_' + prop + 'Y']; } else { this[prop] = this['_' + prop + 'X'] + ',' + this['_' + prop + 'Y']; } } }, // ### parse() // // 'rotate(90) scale(4)' => self.setFromString('rotate', 90); self.setFromString('scale', 4); // parse: function(str) { var self = this; str.replace(/([a-zA-Z0-9]+)\((.*?)\)/g, function(x, prop, val) { self.setFromString(prop, val); }); }, toString: function() { var re = []; for (var i in this) { if (this.hasOwnProperty(i)) { if ((!support.transform3d) && ( (i === 'rotateX') || (i === 'rotateY') || (i === 'translateZ') || (i === 'scaleZ') || (i === 'perspective'))) { continue; } if (i[0] !== '_') { re.push(i + '(' + this[i] + ')'); } } } return re.join(' '); } }; // ### getTransition() // Returns the transition string to be used for the `transition` CSS property. // // getTransition({ opacity: 1, rotate: 30 }, 500, 'ease'); // => 'opacity 500ms ease, -webkit-transform 500ms ease' // function getTransition(properties, duration, easing, specialEasing) { // 获取属性对应的 transition-property 和 transition-timing-function // {marginTop: 100, paddingLeft: 200} => {'margin-top': 'swing', 'padding-left': 'swing'} var props = {}; for (var p in properties) { var key = $.camelCase(p); // Convert 'text-align' => 'textAlign' key = propertyMap[key] || $.cssProps[key] || key; // Get vendor specify propertie // For example 'transform-origin' 'perspective' if (support[key]) { key = support[key]; } if (!(key in props)) { props[key] = specialEasing[p]; } } var MS = toMS(duration); // For more properties, add them this way: // 'margin 200ms ease, padding 200ms ease, ...' var transitions = []; for (var p in props) { transitions.push(uncamel(p) + ' ' + MS + ' ' + (props[p])); } return transitions.join(','); } // ### disposeSpecialValue() // // .css({left: auto}).animate({left: '100'}) => .css({left: 0}).animate({left: 100}); // .animate({opacity: 'show'}) => .css({opacity: 0}).show().animate({opacity: 1}); // .animate({opacity: 'hide'}) => .css({opacity: 1}).animate({opacity: 0}, function() { $(this).hide() }); // function disposeSpecialValue($self, endProps, startProps, callback) { var clearStyles = {}, hidden = $self.css('display') === 'none', toggle = $self.data('bjj-toggle'), show; // Height/width overflow pass if ('height' in endProps || 'width' in endProps) { if ($self.css('display') === 'inline' && $self.css('float') === 'none') { $self.css('display', 'inline-block'); } } for (var p in endProps) { var startValue = $self.css(p), endValue = endProps[p], isToggle = endValue === 'toggle'; if (endValue === 'show' || isToggle && (show === true || show === undefined && (hidden || toggle === false))) { clearStyles[p] = ''; if (isToggle) $self.data('bjj-toggle', true); // jQuery 在获取 display: none 元素的宽高等属性时,会先让元素显示出来,但是又会为元素添加 position: absolute。 // 因此,当元素的宽高为百分比时,其基于的父元素会改变为最近的定位父元素,这就导致获取的宽高不正确 // 这里只好手动让元素显示出来后,再获取它的宽高。 if (hidden) $self.show(); var inlineStyleValue = $self[0].style[p], originalValue = $self.css(p, '').css(p); $self.css(p, inlineStyleValue); if (hidden) $self.hide(); if (hidden || toggle === false || startValue != originalValue) { if (show === undefined) show = true; endValue = originalValue; if (hidden) startValue = 0; } else { endValue = undefined; } } else if (endValue === 'hide' || isToggle && (show === false || show === undefined && (!hidden || toggle === true))) { clearStyles[p] = ''; if (isToggle) $self.data('bjj-toggle', false); if (!hidden || toggle === true) { if (show === undefined) show = false; endValue = 0; } else { endValue = undefined; } } var startUnit = getUnit(startValue), endUnit = getUnit(endValue), startColor, endColor; if (isComplex(startValue)) { // 检查复合值是否为阴影 startValue = checkShadow(p, startValue); } else if (startColor = isColor(startValue)) { // 是否是颜色 } else if (isKeyword(startValue)) { // 处理关键字 // 如果是阴影属性,则返回一个复合属性 startValue = checkShadow(p, startValue, (endValue + '').indexOf('inset') >= 0); if (!isComplex(startValue)) { startValue = 0; // 主要针对定位属性,如:left默认为auto } } else if (!startUnit && endUnit) { startValue += endUnit; } if (isComplex(endValue)) { endValue = checkShadow(p, endValue); } else if (endColor = isColor(endValue)) { } else if (isKeyword(endValue)) { endValue = checkShadow(p, endValue, startValue.indexOf('inset') >= 0); // 如果是阴影属性,则返回一个复合属性 if (!isComplex(endValue) && endValue.match(/^[+-]=/)) { // 主要针对递增或递减值,例如:left: '+=30%' endValue = calculateValue($self, p, startValue, endValue, 1); } } else if (endValue !== undefined && !endUnit && startUnit) { endValue += startUnit; } if ((endValue === undefined) //|| (startValue == endValue) || (!!endColor ^ !!startColor)) { delete endProps[p]; continue; } endProps[p] = endValue; startProps[p] = startValue; } var fn = callback; if (show === true) { if ('width' in clearStyles || 'height' in clearStyles) { if($self.data('bjj-overflow') === undefined) { $self.data('bjj-overflow', $self[0].style.overflow); } clearStyles['overflow'] = $self.data('bjj-overflow'); $self.css('overflow', 'hidden'); } $self.show(); } else if (show === false) { if ('width' in clearStyles || 'height' in clearStyles) { if($self.data('bjj-overflow') === undefined) { $self.data('bjj-overflow', $self[0].style.overflow); } clearStyles['overflow'] = ''; $self.css('overflow', 'hidden'); } clearStyles['display'] = 'none'; } if (show !== undefined) { fn = function() { if(show) $self.removeData('bjj-overflow'); $self.css(clearStyles); if (typeof callback === 'function') callback.call(this); }; } return fn; } // 调用队列 function callOrQueue(self, queue, callback) { if (queue === true) { self.queue(callback); } else if (queue) { self.queue(queue, callback); } else { self.each(function() { callback.call(this); }); } } function finishCall(self, callback, next) { if (typeof callback === 'function') { callback.call(self); } if (typeof next === 'function') { next(); } } function delayRun(next) { var self = this, $self = $(self), transitionDelayRunParams = $self.data('bjj-transitionDelayRunParams'); if (!transitionDelayRunParams) return; var transitionValueList = transitionDelayRunParams.transitionValueList, params = transitionDelayRunParams.queueParams.shift(), startProps = {}, endProps = params.endProps, duration = params.duration, easing = params.easing, queue = params.queue, specialEasing = params.specialEasing, callback = params.callback = disposeSpecialValue($self, endProps, startProps, params.callback), empty = $.isEmptyObject(endProps); // If there's nothing to do... if (duration === 0 || empty) { if (!empty) $self.css(endProps); finishCall(self, callback, next); return; } $self.css(startProps); self.offsetWidth; // 强制刷新 // Prepare the callback. var cb = function(e) { var i = $.inArray(timer, $.timers); if (i >= 0) $.timers.splice(i, 1); i = $.inArray(transitionValue, transitionValueList); if (i >= 0) transitionValueList.splice(i, 1); self.style[support.transition] = transitionValueList.join(','); finishCall(self, callback, next); }; var stop = function(gotoEnd) { window.clearTimeout(timerID); var i = $.inArray(transitionValue, transitionValueList); if (i >= 0) transitionValueList.splice(i, 1); self.style[support.transition] = transitionValueList.join(','); if (gotoEnd) { finishCall(self, callback, next); } else { var curProp = {}; for (var p in endProps) { var startValue = startProps[p], endValue = endProps[p], endColor = isColor(endValue), dv; setCubicBezier(specialEasing[p]); var bezierY = cubicBezier.getY(($.now() - startTime) / duration); if (endColor) { // 如果是颜色 dv = calculateColor(startValue, endColor, bezierY); } else { dv = calculateValue($self, p, startValue, endValue, bezierY); } curProp[p] = dv; } $self.css(curProp); } }; // 模拟 .stop() 所需要的对象 var timer = function() { // 在 animate 动画完成后,且下一个队列函数已经执行,timer 被加入到 $.timers 队列中后,此时 jQuery 又会执行一次 jQuery.fx.tick // 在 jQuery.fx.tick 中 $.timers 会将返回值为 false 的 timer 全部清空,这样会导致当前的动画无法执行 stop() // 注释:animate 的动画中 timer() 的返回值为当前动画的剩余时间 return true; }; timer.elem = self; timer.queue = queue; timer.anim = { stop: stop }; $.timers.push(timer); // Build the `transition` property. var transitionValue = getTransition(endProps, duration, easing, specialEasing); transitionValueList.push(transitionValue); var startTime = $.now(); self.style[support.transition] = transitionValueList.join(','); $self.css(endProps); // transitionend 事件还是存在多个 bug // 例如,多个 transition-property 并行时,先结束的动画会将触发其它属性动画的 transitionend 事件 // 再例如,元素动画途中 display: none 后,将不会再触发 transitionend 事件 // 因此只好弃用它,使用 setTimeout 代替它 var timerID = window.setTimeout(cb, duration); } // 模拟 .finish() 所需要的方法 // 当执行 finish() 时 // 如果动画队列中只有一个动画,那么这里的 finish 不会执行,只会执行 stop() // 如果动画队列中有多个动画,那么除第一个动画以外,剩余多少动画,这里的 finish 就会被执行多少次。 delayRun.finish = function() { var $self = $(this), transitionDelayRunParams = $self.data('bjj-transitionDelayRunParams'); if (!transitionDelayRunParams) return; var params = transitionDelayRunParams.queueParams.shift(); // 能够执行 finish 的动画,将不会执行 delayRun params.callback = disposeSpecialValue($self, params.endProps, {}, params.callback); $self.css(params.endProps); finishCall(this, params.callback); }; // ## transition() function transition(properties, duration, easing, callback, queue, specialEasing) { duration = parseInt(toMS(duration), 10); this.each(function() { var $self = $(this), transitionDelayRunParams = $self.data('bjj-transitionDelayRunParams'); if (!transitionDelayRunParams) { transitionDelayRunParams = { transitionValueList: [], // 用于分别保存每一个对象的 transition 属性值列表 queueParams: [] // 用于保存动画队列中各个动画的参数 } } transitionDelayRunParams.queueParams.push({ endProps: $.extend(true, {}, properties), duration: duration, easing: easing, callback: callback, queue: queue, specialEasing: specialEasing }); $self.data('bjj-transitionDelayRunParams', transitionDelayRunParams); }); // Use jQuery's fx queue. callOrQueue(this, queue, delayRun); return this; } // ### propFilter() // jQuery 源码使用该方法来过滤出属性和缓动 // // props = {left: [200, 'easeInBack'], width: [100, 'linear'], height: 200} // specialEasing = {height: 'swing'}; // // propFilter(props, specialEasing); // // => props == {left: 200, width: 100, height: 200} // specialEasing == {left: 'easeInBack', width: 'linear', height: 'swing'} // function propFilter(props, specialEasing) { var index, name, easing, value, hooks; // camelCase, specialEasing and expand cssHook pass for (index in props) { name = $.camelCase(index); easing = specialEasing[name]; value = props[index]; if ($.isArray(value)) { easing = value[1]; value = props[index] = value[0]; } if (index !== name) { props[name] = value; delete props[index]; } hooks = $.cssHooks[name]; if (hooks && 'expand' in hooks) { value = hooks.expand(value); delete props[name]; // not quite $.extend, this wont overwrite keys already present. // also - reusing 'index' from above because we have the correct 'name' for (index in value) { if (!(index in props)) { props[index] = value[index]; specialEasing[index] = easing; } } } else { specialEasing[name] = easing; } } } // ### Compatible with the following written // Example: // .animate.({'translate3d': [100, 200, 300]}); // .animate.({'scale': [1, 2]}); // .animate.({'scale': '1,2'}); var _animate = $.fn._animate = $.fn.animate; $.fn.animate = function(properties, duration, easing, callback) { var queue = true, specialEasing = {}, originalProperties = $.extend({}, properties), originalDuration = duration, originalEasing = easing, originalCallback = callback; if (typeof duration === 'function') { // Account for `.transition(properties, callback)`. callback = duration; duration = undefined; } else if ($.isPlainObject(duration)) { // Account for `.transition(properties, options)`. originalDuration = $.extend({}, duration); easing = duration.easing; queue = typeof duration.queue === 'undefined' ? true : duration.queue; specialEasing = duration.specialEasing || specialEasing; callback = duration.complete; duration = duration.duration; } else if (typeof easing === 'function') { // Account for `.transition(properties, duration, callback)`. callback = easing; easing = undefined; } // Set defaults. (`400` duration, `ease` easing) if (!duration && duration !== 0) { duration = $.fx.speeds._default; } if (!easing) { easing = $.cssEase._default; } // 拆分属性 resolveProp(properties); var useTransition = support.transition; if (useTransition && ('scrollLeft' in properties || 'scrollTop' in properties)) { useTransition = false; } if (useTransition) { // 将属性名与对应的缓动过滤出来 propFilter(properties, specialEasing); var transformEasing; for (var p in specialEasing) { // 将缓动转换为贝塞尔函数 var e = specialEasing[p] = convertEase(specialEasing[p] || easing); if (typeof e === 'undefined') { useTransition = false; break; } else if (propertyMap[p] === support.transform) { // 判断transform是否设置了不同的easing if (transformEasing && transformEasing !== e) { useTransition = false; break; } else { transformEasing = e; } } } } // 如果不支持 transition 动画,或者不支持该缓动类型,再或者动画属性中包含不支持的属性,则使用 animate 实现 if (!useTransition) { // 修复一个 动画的 bug,该 bug 仅在 animate() 动画中才会出现 // // jQuery 源码 6987 行 // if ( tween.elem[ tween.prop ] != null && // (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) { // return tween.elem[ tween.prop ]; // } // // 这段代码检查元素是否定义了该属性,如果定义了,并且元素的style属性中未定义该属性,则直接返回该属性值 // 由于 自身拥有 x,y 这两个属性,因此最终导致了 元素的 x,y 动画起始位置不正确 this.each(function() { try { // 在window平台下的safari浏览器下,删除属性会导致程序报错 // 不过在该环境下即使不删除这两个属性,动画也不会出现问题 if (this.x != null) delete this.x; if (this.y != null) delete this.y; } catch(e) {} }); return _animate.call(this, originalProperties, originalDuration, originalEasing, originalCallback); } // normalize opt.queue - true/undefined/null -> 'fx' if (queue == null || queue === true) { queue = 'fx'; } return transition.call(this, properties, duration, easing, callback, queue, specialEasing); } // ### resolveProp(props) // // {scale: '3,2'} => {scaleX: 3, scaleY: 2} // var xyz = ['X', 'Y', 'Z'], checkProp = [ 'translate', 'translate3d', 'scale', 'scale3d', 'rotate3d', 'skew' ]; function resolveProp(props) { for (var i = 0, l = checkProp.length; i < l; i++) { var p = checkProp[i]; if (p in props) { var val = props[p]; delete props[p]; // 区分 [[1,2], 'easeOutBack'] 和 [1,2] var easeing; if ($.isArray(val)) { var val0 = val[0], val1 = val[1]; if ($.isArray(val0) || (typeof val0 === 'string' && val0.split(',').length > 1) || (val1 && isNaN(parseFloat(val1)))) { val = val0; easeing = val1; } } var is3d = p.indexOf('3d') !== -1, arr; if ($.isArray(val)) { arr = val; } else { arr = (typeof val === 'string') ? val.split(',') : [val]; // {scale: [3]} => {scale: [3, 3]} if (arr.length < 2) { if (is3d) { arr[2] = arr[1] = arr[0]; } else { arr[1] = arr[0]; } } } if (is3d) p = p.slice(0, -2); var def = p === 'scale' ? 1 : 0; // {scale: [2, 3]} => {scaleX: 2, scaleY: 3} for (var j = 0; j < 3; j++) { val = arr[j]; if (val || val === 0) { props[p + xyz[j]] = easeing ? [val, easeing] : val; } else if (val === '') { props[p + xyz[j]] = easeing ? [def, easeing] : def; } } } } } // ### uncamel(str) // Converts a camelcase string to a dasherized string. // // 'marginLeft' => 'margin-left' // 'webkitTransformOrigin' => '-webkit-transform-origin' // function uncamel(str) { if (!str.indexOf('webkit')) str = 'W' + str.substr(1); if (!str.indexOf('moz') || !str.indexOf('ms')) str = 'M' + str.substr(1); return str.replace(/([A-Z])/g, '-$1').toLowerCase(); } // ### unit(number, unit) // // unit(30, 'px') => '30px' // unit('30%', 'px') => '30%' // function unit(i, units) { if ((typeof i === 'string') && (!i.match(/^[\-0-9\.]+$/))) { return i; } else { return '' + i + units; } } // ### getUnit(str) // // getUnit('30px') => 'px' // getUnit('30%') => '%' // getUnit('30') => '' // function getUnit(value) { if (typeof value !== 'string') return ''; var s = value.match(/^(?:\-=|\+=)?[\-0-9\.]+/); if (!s) return ''; return value.substr(s[0].length) || ''; } // ### getBaseValue($self, prop, index) // 根据属性获取百分比时基于的值 // @param index 参数表示需要获取的子属性在该符合属性值组中的索引位置,例如 ('margin', 0) == 'margin-top' // // getBaseValue($self, 'width') => $self.parent().width() // getBaseValue($self, 'x') => $self.outerWidth() // // getBaseValue($self, 'margin', 2) => $self.parent().height() // getBaseValue($self, 'background-position', 0) => $self.outerWidth() // function getBaseValue($self, prop, index) { var baseSelfKeyword = [ 'x', 'y', 'position', 'origin', 'radius' ], horDirKeyword = [ 'x', 'width', 'padding', 'margin', 'left', 'right' ], baseSelf = false, dir = 0, // 表示方向,1表示水平,-1表示垂直 baseValue = 0, p = prop.toLowerCase(), i = 0, l = baseSelfKeyword.length; for (; i < l; i++) { var k = baseSelfKeyword[i]; if (p.indexOf(k) >= 0) { baseSelf = true; } } i = 0; l = horDirKeyword.length; for (; i < l; i++) { if (p.indexOf(horDirKeyword[i]) >= 0) { dir = 1; } } if (!dir) { if (p.indexOf('position') >= 0 || p.indexOf('size') >= 0 || p.indexOf('origin') >= 0) { if (index === 1) { dir = -1; } else { dir = 1; } } else { if (index === 1 || index === 3) { dir = 1; } else { dir = -1; } } } if (dir === 1) { baseValue = baseSelf ? $self.outerWidth() : $self.parent().width(); } else if (dir === -1) { baseValue = baseSelf ? $self.outerHeight() : $self.parent().height(); } return baseValue; } // ### convertUnit() // 将属性值转换为指定单位的新值 // @param index 参数表示需要获取的子属性在该符合属性值组中的索引位置,例如 ('margin', 0) == 'margin-top' // // convertUnit($self, 'margin', 1, 100px, %) => (100 / $self.parent().width()) * 100 + '%' // function convertUnit($self, prop, index, value, newUnit) { var px = 'px', em = 'em', pe = '%', oldUnit = getUnit(value), newValue = value; if (oldUnit != newUnit) { var oldValue = parseFloat(value); if (newUnit === px) { if (oldUnit === pe) { newValue = oldValue / 100 * getBaseValue($self, prop, index) + px; } else if (oldUnit === em) { newValue = oldValue * parseFloat($self.css('font-size')) + px; } } else if (newUnit === pe) { var baseValue = getBaseValue($self, prop, index); if (!baseValue) { newValue = 0; } else if (oldUnit === px) { newValue = (oldValue / baseValue) * 100 + pe; } else if (oldUnit === em) { newValue = (oldValue * parseFloat($self.css('font-size')) / baseValue) * 100 + pe; } } else if (newUnit === em) { if (oldUnit === px) { newValue = oldValue / parseFloat($self.css('font-size')) + em; } else if (oldUnit === pe) { newValue = oldValue / 100 * getBaseValue($self, prop, index) / parseFloat($self.css('font-size')) + em; } } } return newValue; } // ### calculateValue($self, begin, end, pos) // 根据 0-1 之间的位置比,计算两个属性值在该位置上的中间值 // // calculateValue($self, 'left', 0, 10, .5); => 5 // calculateValue($self, 'left', '50px', '100%', .5); => (100 - 50 / $self.parent().width() * 100) * .5 + 50 / $self.parent().width() * 100 + '%' // calculateValue($self, 'left', '50px', '+=100%', .5); => $self.parent().width() * 100% * .5 + 50 + 'px' // var rfxnum = new RegExp('^(?:([+-])=|)([+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|))([a-z%]*)$', 'i'); function calculateValue($self, prop, begin, end, pos) { begin = (begin + '').split(' '); end = (end + '').split(' '); var value = [], i = 0, l = end.length; for (; i < l; i++) { var startValue = begin[i] || 0, endValue = end[i], startUnit = getUnit(startValue), endUnit = getUnit(endValue) || startUnit, endColor = isColor(endValue); if (endColor) { value[i] = calculateColor(startValue, endColor, pos); } else if (isNaN(parseFloat(endValue))) { // 可能是一些关键字或者特殊值 var ret = rfxnum.exec(end[i]); if (ret) { // 如果结束值为 '+=' 或者 '-=' var v = (ret[1] + 1) * ret[2] + ret[3]; endUnit = startUnit || endUnit; // 单位改用起始值的单位 v = convertUnit($self, prop, i, v, endUnit); startValue = parseFloat(startValue); endValue = startValue + parseFloat(v); value[i] = (endValue - startValue) * pos + startValue + endUnit; } else { // 如果是关键字,就保留原值 value[i] = endValue; } } else { endValue = parseFloat(endValue); startValue = convertUnit($self, prop, i, startValue, endUnit); startValue = parseFloat(startValue); value[i] = (endValue - startValue) * pos + startValue + endUnit; } } l = begin.length; for (; i < l; i++) { value.push(begin[i]); } value = value.join(' '); var numberValue = parseFloat(value); return value == numberValue ? numberValue : value; } // ### toMS(duration) // Converts given `duration` to a millisecond string. // // toMS('fast') => $.fx.speeds[i] => '200ms' // toMS('normal') => $.fx.speeds._default => '400ms' // toMS(10) => '10ms' // toMS('100ms') => '100ms' // function toMS(duration) { var i = duration; // Allow string durations like 'fast' and 'slow', without overriding numeric values. if (typeof i === 'string' && (!i.match(/^[\-0-9\.]+/))) { i = $.fx.speeds[i] || $.fx.speeds._default; } return unit(i, 'ms'); } // ### isKeyword(value) // 判断一个值是否为关键字 // // isKeyword('auto'); => true // isKeyword('50px 50px 1px #000'); => false // function isKeyword(value) { return typeof value === 'string' && isNaN(parseFloat(value)) && value.split(' ').length === 1; } // ### isComplex(value) // 判断一个值是否为复合值 // // isComplex('50px'); => false // isComplex('rgb(0, 0, 0)'); => false // isComplex('50px 50px 1px #000'); => true // function isComplex(value) { var str = value + ''; return str.split(',').length === 1 && str.split(' ').length > 1; } // ### isColor(value) // 判断一个值是否为颜色,如果是颜色则返回该颜色的 rgb 形式 // // isColor('white'); => 'rgb(255,255,255)' // isColor('auto'); => false // function isColor(value) { if (typeof value === 'string') { if (!value.indexOf('rgb') && value.split(' ').length === 1) return value; return getCorrectValue('color', value); } return false; } // ### checkShadow(prop, value, inset) // 检查一个属性是否为阴影 // 如果不是阴影,则返回原值 // 如果阴影值不合法,则返回默认阴影。 // 如果是阴影则返回该阴影的正确形式,同时去掉参数中的多余空格。 // 第三个参数表示 boxShadow 的默认阴影是否为内阴影 // // checkShadow('boxShadow', '50px 50px 1px #000'); => rgb(0,0,0) 50px 50px 1px 0px // checkShadow('boxShadow', 'none'); => rgba(0,0,0,0) 0px 0px 0px 0px // checkShadow('left', '50px'); => '50px' // function checkShadow(prop, value, inset) { if (prop.indexOf('hadow') < 0) return value; value = getCorrectValue(prop, value); if (!value) { var def = inset && !prop.indexOf('box') ? 'inset 0 0 0 transparent' : '0 0 0 transparent'; value = getCorrectValue(prop, def); } value = value.replace(/,\s/g, ','); return value; } // ### getCorrectValue(prop, value) // 返回该值的正确形式 // // getCorrectValue('color', 'white'); => 'rgb(255,255,255)' // getCorrectValue('boxShadow', '50px 50px 1px #000'); => rgb(0, 0, 0) 50px 50px 1px 0px // function getCorrectValue(prop, value) { testElem.style[prop] = ''; testElem.style[prop] = value; $testElem.appendTo('body'); value = testElem.style[prop] !== '' && testElem.style[prop] !== 'none' && $testElem.css(prop); $testElem.detach(); return value; } // ### calculateColor(begin, end, pos) // 根据 0-1 之间的位置比,计算两个颜色在该位置上的过渡色 // // calculateColor([255,255,255,1], [0,0,0,1], .5); => 'rgba(127,127,127,1)' // calculateColor([255,255,255], [0,0,0], .5); => 'rgb(127,127,127)' // function calculateColor(begin, end, pos) { // 如果传入的是颜色值,将其先解析为数组 if (!$.isArray(begin)) begin = parseColor(begin); if (!$.isArray(end)) end = parseColor(end); var len = Math.min(begin.length, end.length), color = 'rgb', dr = pos * (end[0] - begin[0]), dg = pos * (end[1] - begin[1]), db = pos * (end[2] - begin[2]), r = begin[0] + dr, g = begin[1] + dg, b = begin[2] + db, a = 1; if (len > 3) { color += 'a'; a = parseFloat(begin[3] + pos * (end[3] - begin[3]), 10); r = begin[0] + dr / a * end[3]; g = begin[1] + dg / a * end[3]; b = begin[2] + db / a * end[3]; } r = parseInt(r, 10); g = parseInt(g, 10); b = parseInt(b, 10); color += '(' + r + ',' + g + ',' + b ; if (len > 3) { color += ',' + a; } color += ')'; return color; } // ### parseColor(color) // 将一个颜色值转化为数组形式 // // 'rgba(127,127,127,1)' => [127, 127, 127, 1] // 'rgb(127,127,127)' => [127, 127, 127, 1] // '#000000' => [ 0, 0, 0, 1] // '#fff' => [255, 255, 255, 1] // function parseColor(color) { var match, triplet; // Match #aabbcc if (match = /#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/.exec(color)) { triplet = [parseInt(match[1], 16), parseInt(match[2], 16), parseInt(match[3], 16), 1]; // Match #abc } else if (match = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/.exec(color)) { triplet = [parseInt(match[1], 16) * 17, parseInt(match[2], 16) * 17, parseInt(match[3], 16) * 17, 1]; // Match rgb(n, n, n) } else if (match = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) { triplet = [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), 1]; } else if (match = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9\.]*)\s*\)/.exec(color)) { triplet = [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10),parseFloat(match[4])]; // No browser returns rgb(n%, n%, n%), so little reason to support this format. } return triplet; } // ### getArg(str) // 获取字符串表达式中括号内的参数数组 // // 'cubic-bezier(0,0,1,1)' => [0,0,1,1] // function getArg(str) { var s = str.match(/\(.*\)$/); if (s) { var args = s[0].slice(1, -1).split(','); for (var i = 0, l = args.length; i < l; i++) { var arg = parseFloat(args[i]); args[i] = isNaN(arg) ? undefined : arg; } return args; } return null; } /** * 创建一个三次贝塞尔对象 * @exception 每一个参数为一个表示点的数组[x,y] * @param {array} c1 表示起始点的控制点 * @param {array} c2 表示结束点的控制点 * @param {array} begin 表示起始点,默认为[0,0] * @param {array} end 表示结束点,默认为[1,1] */ function CubicBezier() { this.set.apply(this, arguments); } CubicBezier.prototype = { _bezierFunc: function(p, t, targ) { return this.begin[p] * Math.pow(1 - t, 3) + this.c1[p] * 3 * t * Math.pow(1 - t, 2) + this.c2[p] * 3 * (1 - t) * Math.pow(t, 2) + this.end[p] * Math.pow(t, 3) - targ; }, _deltaBezierFunc: function(p, t, targ) { var dt = 1e-8; return (this._bezierFunc(p, t, targ) - this._bezierFunc(p, t - dt, targ)) / dt; }, set: function(c1, c2, begin, end) { this.c1 = c1 ? new Point(c1[0], c1[1]) : new Point(0, 0); this.c2 = c2 ? new Point(c2[0], c2[1]) : new Point(1, 1); this.begin = begin ? new Point(begin[0], begin[1]) : new Point(0, 0); this.end = end ? new Point(end[0], end[1]) : new Point(1, 1); }, /** * 已知x,求y * @param {number} x 参数表示一个在贝塞尔曲线上X轴方向的向量,取值在 0.0 - 1.0 之间 * @return 返回x在贝塞尔曲线上对应的y */ getY: function(x) { var t = .5; //设置t的初值 for (var i = 0; i < 1000; i++) { t = t - this._bezierFunc('x', t, x) / this._deltaBezierFunc('x', t, x); if (this._bezierFunc('x', t, x) === 0) break; } return this._bezierFunc('y', t, 0); } } // ## Point class function Point(x, y) { this.x = x || 0; this.y = y || 0; } // 根据缓动函数,创建三次贝塞尔对象 var cubicBezier = new CubicBezier(); function setCubicBezier(easing) { var args = getArg(easing); cubicBezier.set([args[0], args[1]], [args[2], args[3]]); } return $; }));