-
Notifications
You must be signed in to change notification settings - Fork 534
Description
早在 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
});
注释里有许多链接,是向先行者致敬的!
在这过程中,还发现许多好东西,一并放出来,以供未来的偷师与转化!
- http://processing.org/learning/transform2d/
- http://www.the-art-of-web.com/css/css-animation/
- http://help.dottoro.com/lcebdggm.php 矩阵类的API设计就是参考它的,比MDC与W3C靠谱多了。
- https://github.com/kangax/fabric.js这库作者名气够响吧!