早在 v2时,它就加入了对旋转与放大的支持,但由于数学知识的短板一直搞出个矩阵类。而矩阵类是解决IE transform 2D的关键。从上星期决定升级CSS模块开始,就想方设法引进一个矩进类。 个人一开始很看重两个框架 Sylvester.js ,与matrix.js。但它们都太大了,最终还是决定自己搞:
function toFixed(d){
return d > -0.0000001 && d < 0.0000001 ? 0 : /e/.test(d+"") ? d.toFixed(7) : d
}
function rad(value) {
if(isFinite(value)) {
return parseFloat(value);
}
if(~value.indexOf("deg")) {//圆角制。
return parseInt(value,10) * (Math.PI / 180);
} else if (~value.indexOf("grad")) {//梯度制。一个直角的100等分之一。一个圆圈相当于400grad。
return parseInt(value,10) * (Math.PI/200);
}//弧度制,360=2π
return parseFloat(value,10)
}
var Matrix = $.factory({
init: function(rows,cols){
this.rows = rows || 3;
this.cols = cols || 3;
this.set.apply(this, [].slice.call(arguments,2))
},
set: function(){//用于设置元素
for(var i = 0, n = this.rows * this.cols; i < n; i++){
this[ Math.floor(i / this.rows) +","+(i % this.rows) ] = parseFloat(arguments[i]) || 0;
}
return this;
},
get: function(){//转变成数组
var array = [], ret = []
for(var key in this){
if(~key.indexOf(",")){
array.push( key )
}
}
array.sort() ;
for(var i = 0; i < array.length; i++){
ret[i] = this[array[i]]
}
return ret ;
},
set2D: function(a,b,c,d,tx,ty){
this.a = this["0,0"] = a * 1
this.b = this["1,0"] = b * 1
this.c = this["0,1"] = c * 1
this.d = this["1,1"] = d * 1
this.tx = this["2,0"] = tx * 1
this.ty = this["2,1"] = ty * 1
this["0,2"] = this["1,2"] = 0
this["2,2"] = 1;
return this;
},
get2D: function(){
return "matrix("+[ this["0,0"],this["1,0"],this["0,1"],this["1,1"],this["2,0"],this["2,1"] ]+")";
},
cross: function(matrix){
if(this.cols === matrix.rows){
var ret = new Matrix(this.rows, matrix.cols);
var n = Math.max(this.rows, matrix.cols)
for(var key in ret){
if(key.match(/(\d+),(\d+)/)){
var r = RegExp.$1, c = RegExp.$2
for(var i = 0; i < n; i++ ){
ret[key] += ( (this[r+","+i] || 0) * (matrix[i+","+c]||0 ));//X轴*Y轴
}
}
}
for(key in this){
if(typeof this[key] == "number"){
delete this[key]
}
}
for(key in ret){
if(typeof ret[key] == "number"){
this[key] = toFixed(ret[key])
}
}
return this
}else{
throw "cross error: this.cols !== matrix.rows"
}
},
//http://www.zweigmedia.com/RealWorld/tutorialsf1/frames3_2.html
//http://www.w3.org/TR/SVG/coords.html#RotationDefined
//http://www.mathamazement.com/Lessons/Pre-Calculus/08_Matrices-and-Determinants/coordinate-transformation-matrices.html
translate: function(tx, ty) {
tx = parseFloat(tx) || 0;//沿 x 轴平移每个点的距离。
ty = parseFloat(ty) || 0;//沿 y 轴平移每个点的距离。
var m = (new Matrix()).set2D(1 ,0, 0, 1, tx, ty);
this.cross(m)
},
translateX: function(tx) {
this.translate(tx, 0)
},
translateY: function(ty) {
this.translate(0, ty)
},
scale: function(sx, sy){
sx = isFinite(sx) ? parseFloat(sx) : 1 ;
sy = isFinite(sy) ? parseFloat(sy) : 1 ;
var m = (new Matrix()).set2D( sx, 0, 0, sy, 0, 0);
this.cross(m)
},
scaleX: function(sx) {
this.scale(sx, 1)
},
scaleY: function(sy) {
this.scale(1, sy)
},
rotate: function(angle, fix){//matrix.rotate(60)==>顺时针转60度
fix = fix === -1 ? fix : 1;
angle = rad(angle);
var cos = Math.cos(angle);
var sin = Math.sin(angle);// a, b, c, d
var m = (new Matrix()).set2D( cos,fix * sin , fix * -sin, cos, 0, 0);
return this.cross(m)
},
skew: function(ax, ay){
var xRad = rad(ax);
var yRad;
if (ay != null) {
yRad = rad(ay)
} else {
yRad = xRad
}
var m = (new Matrix()).set2D( 1, Math.tan( xRad ), Math.tan( yRad ), 1, 0, 0);
return this.cross(m)
},
skewX: function(ax){
return this.skew(ax, 0);
},
skewY: function(ay){
this.skew(0, ay);
},
// ┌ ┐┌ ┐
// │ a c tx││ M11 -M12 tx│
// │ b d ty││ -M21 M22 tx│
// └ ┘└ ┘
//http://help.adobe.com/zh_CN/FlashPlatform/reference/actionscript/3/flash/geom/Matrix.html
//分解原始数值,得到a,b,c,e,tx,ty属性,以及返回一个包含x,y,scaleX,scaleY,skewX,skewY,rotation的对象
decompose2D: function(){
var ret = {}
this.a = this["0,0"]
this.b = this["1,0"]
this.c = this["0,1"]
this.d = this["1,1"]
ret.x = this.tx = this["2,0"]
ret.y = this.ty = this["2,1"]
ret.scaleX = Math.sqrt(this.a * this.a + this.b * this.b);
ret.scaleY = Math.sqrt(this.c * this.c + this.d * this.d);
var skewX = Math.atan2(-this.c, this.d);
var skewY = Math.atan2(this.b, this.a);
if (skewX == skewY) {
ret.rotation = skewY/Matrix.DEG_TO_RAD;
if (this.a < 0 && this.d >= 0) {
ret.rotation += (ret.rotation <= 0) ? 180 : -180;
}
ret.skewX = ret.skewY = 0;
} else {
ret.skewX = skewX/Matrix.DEG_TO_RAD;
ret.skewY = skewY/Matrix.DEG_TO_RAD;
}
return ret;
}
});
"translateX,translateY,scaleX,scaleY,skewX,skewY".replace($.rword, function(n){
Matrix.prototype[n.toLowerCase()] = Matrix.prototype[n]
});
Matrix.DEG_TO_RAD = Math.PI/180;
从这个矩阵类也可以看到,乘法是最重要的,什么translate, scale, skew, rotate都是基于它。唯一不爽的是,它的元素命名有点复杂。当然这是基于乘法运算的需要。由于野心太大,既可以实现2D矩阵,也可以实现3D矩阵,4*3矩阵……在实现过程中,得知矩阵相乘还是有条件的,于是理想主义死亡了。
第二版的矩阵类很简单,就是专攻2D,名字也从$.Matrix收窄为$.Matrix2D。放弃"x,y"这样复杂的元素命名法,改用a, b, c, d, tx, ty命名。基于cross的各种API也自行代码防御与数字转换,容错性大大提高!
function toFixed(d){//矩阵类第二版
return d > -0.0000001 && d < 0.0000001 ? 0 : /e/.test(d+"") ? d.toFixed(7) : d
}
function toFloat(d, x){
return isFinite(d) ? d: parseFloat(d) || x || 0
}
//http://zh.wikipedia.org/wiki/%E7%9F%A9%E9%98%B5
//http://help.dottoro.com/lcebdggm.php
var Matrix2D = $.factory({
init: function(){
this.set.apply(this, arguments);
},
cross: function(a, b, c, d, tx, ty) {
var a1 = this.a;
var b1 = this.b;
var c1 = this.c;
var d1 = this.d;
this.a = toFixed(a*a1+b*c1);
this.b = toFixed(a*b1+b*d1);
this.c = toFixed(c*a1+d*c1);
this.d = toFixed(c*b1+d*d1);
this.tx = toFixed(tx*a1+ty*c1+this.tx);
this.ty = toFixed(tx*b1+ty*d1+this.ty);
return this;
},
rotate: function( radian ) {
var cos = Math.cos(radian);
var sin = Math.sin(radian);
return this.cross(cos, sin, -sin, cos, 0, 0)
},
skew: function(sx, sy) {
return this.cross(1, Math.tan( sy ), Math.tan( sx ), 1, 0, 0);
},
skewX: function(radian){
return this.skew(radian, 0);
},
skewY: function(radian){
return this.skew(0, radian);
},
scale: function(x, y) {
return this.cross( toFloat(x, 1) ,0, 0, toFloat(y, 1), 0, 0)
},
scaleX: function(x){
return this.scale(x ,1);
},
scaleY: function(y){
return this.scale(1 ,y);
},
translate : function(x, y) {
return this.cross(1, 0, 0, 1, toFloat(x, 0), toFloat(x, 0) );
},
translateX: function(x) {
return this.translate(x, 0);
},
translateY: function(y) {
return this.translate(0, y);
},
toString: function(){
return "matrix("+this.get()+")";
},
get: function(){
return [this.a,this.b,this.c,this.d,this.tx,this.ty];
},
set: function(a, b, c, d, tx, ty){
this.a = a * 1;
this.b = b * 1 || 0;
this.c = c * 1 || 0;
this.d = d * 1;
this.tx = tx * 1 || 0;
this.ty = ty * 1 || 0;
return this;
},
matrix:function(a, b, c, d, tx, ty){
return this.cross(a, b, c, d, toFloat(tx), toFloat(ty))
},
decompose : function() {
//分解原始数值,返回一个包含x,y,scaleX,scaleY,skewX,skewY,rotation的对象
var ret = {};
ret.x = this.tx;
ret.y = this.ty;
ret.scaleX = Math.sqrt(this.a * this.a + this.b * this.b);
ret.scaleY = Math.sqrt(this.c * this.c + this.d * this.d);
var skewX = Math.atan2(-this.c, this.d);
var skewY = Math.atan2(this.b, this.a);
if (skewX == skewY) {
ret.rotation = skewY/ Math.PI * 180;
if (this.a < 0 && this.d >= 0) {
ret.rotation += (ret.rotation <= 0) ? 180 : -180;
}
ret.skewX = ret.skewY = 0;
} else {
ret.skewX = skewX/ Math.PI * 180;
ret.skewY = skewY/ Math.PI * 180;
}
return ret;
}
});
$.Matrix2D = Matrix2D
第二版与初版唯一没有动的地方是decompose 方法,这是从EaselJS抄过来的。而它的作用与louisremi的jquery.transform2的dunmatrix作用相仿,相后者据说是从FireFox源码从扒出来的,但EaselJS的实现明显顺眼多了。至于矩阵类的其他部分,则是从jQuery作者 John Resig的另一个项目Processing.js,不过它的位移与放缩部分有点偷懒,导致错误,于是外围API统统调用cross方法。
但光是有矩阵类是不行的,因此DOM的实现开始时是借鉴useragentman的这篇文章,追根索底,他也是参考另一位大牛的实现。 heygrady 在写了一篇叫《Correcting Transform Origin and Translate in IE》,阐述解题步骤。这些思路后来就被useragentman与louisremi 借鉴去了。但他们俩都在取得变形前元素的尺寸上遇到麻烦,为此使用了矩阵乘向量,然后取四个最上最下最左最右的坐标来求宽高,如此复杂的计算导致误差。因此我框架的CSS模块 v3唯一可做,也唯一能骄傲之处,就是给出更便捷更优雅的求变形前元素的尺寸的解。
下面就是css_fix有关矩阵变换的所有代码,可以看出,数据缓存系统非常重要!
var ident = "DXImageTransform.Microsoft.Matrix"
adapter[ "transform:get" ] = function(node, name){
var m = $._data(node,"matrix")
if(!m){
if(!node.currentStyle.hasLayout){
node.style.zoom = 1;
}
//IE9下请千万别设置 <meta content="IE=8" http-equiv="X-UA-Compatible"/>
//http://www.cnblogs.com/Libra/archive/2009/03/24/1420731.html
if(!node.filters[ident]){
var old = node.currentStyle.filter;//防止覆盖已有的滤镜
node.style.filter = (old ? old +"," : "") + " progid:" + ident + "(sizingMethod='auto expand')";
}
var f = node.filters[ident];
m = new $.Matrix2D( f.M11, f.M12, f.M21, f.M22, f.Dx, f.Dy);
$._data(node,"matrix",m ) //保存到缓存系统,省得每次都计算
}
return name === true ? m : m.toString();
}
//deg degrees, 角度
//grad grads, 百分度
//rad radians, 弧度
function toRadian(value) {
return ~value.indexOf("deg") ?
parseInt(value,10) * Math.PI/180:
~value.indexOf("grad") ?
parseInt(value,10) * Math.PI/200:
parseFloat(value);
}
adapter[ "transform:set" ] = function(node, name, value){
var m = adapter[ "transform:get" ](node, true)
//注意:IE滤镜和其他浏览器定义的角度方向相反
value.toLowerCase().replace(rtransform,function(_,method,array){
array = array.replace(/px/g,"").match($.rword) || [];
if(/skew|rotate/.test(method)){//角度必须带单位
array[0] = toRadian(array[0] );//IE矩阵滤镜的方向是相反的
array[1] = toRadian(array[1] || "0");
}
if(method == "scale" && array[1] == void 0){
array[1] = array[0] //sy如果没有定义等于sx
}
if(method !== "matrix"){
method = method.replace(/(x|y)$/i,function(_,b){
return b.toUpperCase();//处理translateX translateY scaleX scaleY skewX skewY等大小写问题
})
}
m[method].apply(m, array);
var filter = node.filters[ident];
filter.M11 = filter.M22 = 1;//取得未变形前的宽高
filter.M12 = filter.M21 = 0;
var width = node.offsetWidth;
var height = node.offsetHeight;
filter.M11 = m.a;
filter.M12 = m.c;//★★★注意这里的顺序
filter.M21 = m.b;
filter.M22 = m.d;
filter.Dx = m.tx;
filter.Dy = m.ty;
$._data(node,"matrix",m);
var tw = node.offsetWidth, th = node.offsetHeight;//取得变形后高宽
node.style.position = "relative";
node.style.left = (width - tw)/2 + m.tx + "px";
node.style.top = (height - th)/2 + m.ty + "px";
//http://extremelysatisfactorytotalitarianism.com/blog/?p=922
//http://someguynameddylan.com/lab/transform-origin-in-internet-explorer.php
//http://extremelysatisfactorytotalitarianism.com/blog/?p=1002
});
注释里有许多链接,是向先行者致敬的!
在这过程中,还发现许多好东西,一并放出来,以供未来的偷师与转化!
早在 v2时,它就加入了对旋转与放大的支持,但由于数学知识的短板一直搞出个矩阵类。而矩阵类是解决IE transform 2D的关键。从上星期决定升级CSS模块开始,就想方设法引进一个矩进类。 个人一开始很看重两个框架 Sylvester.js ,与matrix.js。但它们都太大了,最终还是决定自己搞:
function toFixed(d){ return d > -0.0000001 && d < 0.0000001 ? 0 : /e/.test(d+"") ? d.toFixed(7) : d } function rad(value) { if(isFinite(value)) { return parseFloat(value); } if(~value.indexOf("deg")) {//圆角制。 return parseInt(value,10) * (Math.PI / 180); } else if (~value.indexOf("grad")) {//梯度制。一个直角的100等分之一。一个圆圈相当于400grad。 return parseInt(value,10) * (Math.PI/200); }//弧度制,360=2π return parseFloat(value,10) } var Matrix = $.factory({ init: function(rows,cols){ this.rows = rows || 3; this.cols = cols || 3; this.set.apply(this, [].slice.call(arguments,2)) }, set: function(){//用于设置元素 for(var i = 0, n = this.rows * this.cols; i < n; i++){ this[ Math.floor(i / this.rows) +","+(i % this.rows) ] = parseFloat(arguments[i]) || 0; } return this; }, get: function(){//转变成数组 var array = [], ret = [] for(var key in this){ if(~key.indexOf(",")){ array.push( key ) } } array.sort() ; for(var i = 0; i < array.length; i++){ ret[i] = this[array[i]] } return ret ; }, set2D: function(a,b,c,d,tx,ty){ this.a = this["0,0"] = a * 1 this.b = this["1,0"] = b * 1 this.c = this["0,1"] = c * 1 this.d = this["1,1"] = d * 1 this.tx = this["2,0"] = tx * 1 this.ty = this["2,1"] = ty * 1 this["0,2"] = this["1,2"] = 0 this["2,2"] = 1; return this; }, get2D: function(){ return "matrix("+[ this["0,0"],this["1,0"],this["0,1"],this["1,1"],this["2,0"],this["2,1"] ]+")"; }, cross: function(matrix){ if(this.cols === matrix.rows){ var ret = new Matrix(this.rows, matrix.cols); var n = Math.max(this.rows, matrix.cols) for(var key in ret){ if(key.match(/(\d+),(\d+)/)){ var r = RegExp.$1, c = RegExp.$2 for(var i = 0; i < n; i++ ){ ret[key] += ( (this[r+","+i] || 0) * (matrix[i+","+c]||0 ));//X轴*Y轴 } } } for(key in this){ if(typeof this[key] == "number"){ delete this[key] } } for(key in ret){ if(typeof ret[key] == "number"){ this[key] = toFixed(ret[key]) } } return this }else{ throw "cross error: this.cols !== matrix.rows" } }, //http://www.zweigmedia.com/RealWorld/tutorialsf1/frames3_2.html //http://www.w3.org/TR/SVG/coords.html#RotationDefined //http://www.mathamazement.com/Lessons/Pre-Calculus/08_Matrices-and-Determinants/coordinate-transformation-matrices.html translate: function(tx, ty) { tx = parseFloat(tx) || 0;//沿 x 轴平移每个点的距离。 ty = parseFloat(ty) || 0;//沿 y 轴平移每个点的距离。 var m = (new Matrix()).set2D(1 ,0, 0, 1, tx, ty); this.cross(m) }, translateX: function(tx) { this.translate(tx, 0) }, translateY: function(ty) { this.translate(0, ty) }, scale: function(sx, sy){ sx = isFinite(sx) ? parseFloat(sx) : 1 ; sy = isFinite(sy) ? parseFloat(sy) : 1 ; var m = (new Matrix()).set2D( sx, 0, 0, sy, 0, 0); this.cross(m) }, scaleX: function(sx) { this.scale(sx, 1) }, scaleY: function(sy) { this.scale(1, sy) }, rotate: function(angle, fix){//matrix.rotate(60)==>顺时针转60度 fix = fix === -1 ? fix : 1; angle = rad(angle); var cos = Math.cos(angle); var sin = Math.sin(angle);// a, b, c, d var m = (new Matrix()).set2D( cos,fix * sin , fix * -sin, cos, 0, 0); return this.cross(m) }, skew: function(ax, ay){ var xRad = rad(ax); var yRad; if (ay != null) { yRad = rad(ay) } else { yRad = xRad } var m = (new Matrix()).set2D( 1, Math.tan( xRad ), Math.tan( yRad ), 1, 0, 0); return this.cross(m) }, skewX: function(ax){ return this.skew(ax, 0); }, skewY: function(ay){ this.skew(0, ay); }, // ┌ ┐┌ ┐ // │ a c tx││ M11 -M12 tx│ // │ b d ty││ -M21 M22 tx│ // └ ┘└ ┘ //http://help.adobe.com/zh_CN/FlashPlatform/reference/actionscript/3/flash/geom/Matrix.html //分解原始数值,得到a,b,c,e,tx,ty属性,以及返回一个包含x,y,scaleX,scaleY,skewX,skewY,rotation的对象 decompose2D: function(){ var ret = {} this.a = this["0,0"] this.b = this["1,0"] this.c = this["0,1"] this.d = this["1,1"] ret.x = this.tx = this["2,0"] ret.y = this.ty = this["2,1"] ret.scaleX = Math.sqrt(this.a * this.a + this.b * this.b); ret.scaleY = Math.sqrt(this.c * this.c + this.d * this.d); var skewX = Math.atan2(-this.c, this.d); var skewY = Math.atan2(this.b, this.a); if (skewX == skewY) { ret.rotation = skewY/Matrix.DEG_TO_RAD; if (this.a < 0 && this.d >= 0) { ret.rotation += (ret.rotation <= 0) ? 180 : -180; } ret.skewX = ret.skewY = 0; } else { ret.skewX = skewX/Matrix.DEG_TO_RAD; ret.skewY = skewY/Matrix.DEG_TO_RAD; } return ret; } }); "translateX,translateY,scaleX,scaleY,skewX,skewY".replace($.rword, function(n){ Matrix.prototype[n.toLowerCase()] = Matrix.prototype[n] }); Matrix.DEG_TO_RAD = Math.PI/180;从这个矩阵类也可以看到,乘法是最重要的,什么translate, scale, skew, rotate都是基于它。唯一不爽的是,它的元素命名有点复杂。当然这是基于乘法运算的需要。由于野心太大,既可以实现2D矩阵,也可以实现3D矩阵,4*3矩阵……在实现过程中,得知矩阵相乘还是有条件的,于是理想主义死亡了。
第二版的矩阵类很简单,就是专攻2D,名字也从$.Matrix收窄为$.Matrix2D。放弃"x,y"这样复杂的元素命名法,改用a, b, c, d, tx, ty命名。基于cross的各种API也自行代码防御与数字转换,容错性大大提高!
function toFixed(d){//矩阵类第二版 return d > -0.0000001 && d < 0.0000001 ? 0 : /e/.test(d+"") ? d.toFixed(7) : d } function toFloat(d, x){ return isFinite(d) ? d: parseFloat(d) || x || 0 } //http://zh.wikipedia.org/wiki/%E7%9F%A9%E9%98%B5 //http://help.dottoro.com/lcebdggm.php var Matrix2D = $.factory({ init: function(){ this.set.apply(this, arguments); }, cross: function(a, b, c, d, tx, ty) { var a1 = this.a; var b1 = this.b; var c1 = this.c; var d1 = this.d; this.a = toFixed(a*a1+b*c1); this.b = toFixed(a*b1+b*d1); this.c = toFixed(c*a1+d*c1); this.d = toFixed(c*b1+d*d1); this.tx = toFixed(tx*a1+ty*c1+this.tx); this.ty = toFixed(tx*b1+ty*d1+this.ty); return this; }, rotate: function( radian ) { var cos = Math.cos(radian); var sin = Math.sin(radian); return this.cross(cos, sin, -sin, cos, 0, 0) }, skew: function(sx, sy) { return this.cross(1, Math.tan( sy ), Math.tan( sx ), 1, 0, 0); }, skewX: function(radian){ return this.skew(radian, 0); }, skewY: function(radian){ return this.skew(0, radian); }, scale: function(x, y) { return this.cross( toFloat(x, 1) ,0, 0, toFloat(y, 1), 0, 0) }, scaleX: function(x){ return this.scale(x ,1); }, scaleY: function(y){ return this.scale(1 ,y); }, translate : function(x, y) { return this.cross(1, 0, 0, 1, toFloat(x, 0), toFloat(x, 0) ); }, translateX: function(x) { return this.translate(x, 0); }, translateY: function(y) { return this.translate(0, y); }, toString: function(){ return "matrix("+this.get()+")"; }, get: function(){ return [this.a,this.b,this.c,this.d,this.tx,this.ty]; }, set: function(a, b, c, d, tx, ty){ this.a = a * 1; this.b = b * 1 || 0; this.c = c * 1 || 0; this.d = d * 1; this.tx = tx * 1 || 0; this.ty = ty * 1 || 0; return this; }, matrix:function(a, b, c, d, tx, ty){ return this.cross(a, b, c, d, toFloat(tx), toFloat(ty)) }, decompose : function() { //分解原始数值,返回一个包含x,y,scaleX,scaleY,skewX,skewY,rotation的对象 var ret = {}; ret.x = this.tx; ret.y = this.ty; ret.scaleX = Math.sqrt(this.a * this.a + this.b * this.b); ret.scaleY = Math.sqrt(this.c * this.c + this.d * this.d); var skewX = Math.atan2(-this.c, this.d); var skewY = Math.atan2(this.b, this.a); if (skewX == skewY) { ret.rotation = skewY/ Math.PI * 180; if (this.a < 0 && this.d >= 0) { ret.rotation += (ret.rotation <= 0) ? 180 : -180; } ret.skewX = ret.skewY = 0; } else { ret.skewX = skewX/ Math.PI * 180; ret.skewY = skewY/ Math.PI * 180; } return ret; } }); $.Matrix2D = Matrix2D第二版与初版唯一没有动的地方是decompose 方法,这是从EaselJS抄过来的。而它的作用与louisremi的jquery.transform2的dunmatrix作用相仿,相后者据说是从FireFox源码从扒出来的,但EaselJS的实现明显顺眼多了。至于矩阵类的其他部分,则是从jQuery作者 John Resig的另一个项目Processing.js,不过它的位移与放缩部分有点偷懒,导致错误,于是外围API统统调用cross方法。
但光是有矩阵类是不行的,因此DOM的实现开始时是借鉴useragentman的这篇文章,追根索底,他也是参考另一位大牛的实现。 heygrady 在写了一篇叫《Correcting Transform Origin and Translate in IE》,阐述解题步骤。这些思路后来就被useragentman与louisremi 借鉴去了。但他们俩都在取得变形前元素的尺寸上遇到麻烦,为此使用了矩阵乘向量,然后取四个最上最下最左最右的坐标来求宽高,如此复杂的计算导致误差。因此我框架的CSS模块 v3唯一可做,也唯一能骄傲之处,就是给出更便捷更优雅的求变形前元素的尺寸的解。
下面就是css_fix有关矩阵变换的所有代码,可以看出,数据缓存系统非常重要!
var ident = "DXImageTransform.Microsoft.Matrix" adapter[ "transform:get" ] = function(node, name){ var m = $._data(node,"matrix") if(!m){ if(!node.currentStyle.hasLayout){ node.style.zoom = 1; } //IE9下请千万别设置 <meta content="IE=8" http-equiv="X-UA-Compatible"/> //http://www.cnblogs.com/Libra/archive/2009/03/24/1420731.html if(!node.filters[ident]){ var old = node.currentStyle.filter;//防止覆盖已有的滤镜 node.style.filter = (old ? old +"," : "") + " progid:" + ident + "(sizingMethod='auto expand')"; } var f = node.filters[ident]; m = new $.Matrix2D( f.M11, f.M12, f.M21, f.M22, f.Dx, f.Dy); $._data(node,"matrix",m ) //保存到缓存系统,省得每次都计算 } return name === true ? m : m.toString(); } //deg degrees, 角度 //grad grads, 百分度 //rad radians, 弧度 function toRadian(value) { return ~value.indexOf("deg") ? parseInt(value,10) * Math.PI/180: ~value.indexOf("grad") ? parseInt(value,10) * Math.PI/200: parseFloat(value); } adapter[ "transform:set" ] = function(node, name, value){ var m = adapter[ "transform:get" ](node, true) //注意:IE滤镜和其他浏览器定义的角度方向相反 value.toLowerCase().replace(rtransform,function(_,method,array){ array = array.replace(/px/g,"").match($.rword) || []; if(/skew|rotate/.test(method)){//角度必须带单位 array[0] = toRadian(array[0] );//IE矩阵滤镜的方向是相反的 array[1] = toRadian(array[1] || "0"); } if(method == "scale" && array[1] == void 0){ array[1] = array[0] //sy如果没有定义等于sx } if(method !== "matrix"){ method = method.replace(/(x|y)$/i,function(_,b){ return b.toUpperCase();//处理translateX translateY scaleX scaleY skewX skewY等大小写问题 }) } m[method].apply(m, array); var filter = node.filters[ident]; filter.M11 = filter.M22 = 1;//取得未变形前的宽高 filter.M12 = filter.M21 = 0; var width = node.offsetWidth; var height = node.offsetHeight; filter.M11 = m.a; filter.M12 = m.c;//★★★注意这里的顺序 filter.M21 = m.b; filter.M22 = m.d; filter.Dx = m.tx; filter.Dy = m.ty; $._data(node,"matrix",m); var tw = node.offsetWidth, th = node.offsetHeight;//取得变形后高宽 node.style.position = "relative"; node.style.left = (width - tw)/2 + m.tx + "px"; node.style.top = (height - th)/2 + m.ty + "px"; //http://extremelysatisfactorytotalitarianism.com/blog/?p=922 //http://someguynameddylan.com/lab/transform-origin-in-internet-explorer.php //http://extremelysatisfactorytotalitarianism.com/blog/?p=1002 });注释里有许多链接,是向先行者致敬的!
在这过程中,还发现许多好东西,一并放出来,以供未来的偷师与转化!