Skip to content

Commit

Permalink
fix: mask的transform跟随问题 #91
Browse files Browse the repository at this point in the history
  • Loading branch information
army8735 committed Sep 16, 2020
1 parent 74d0ebe commit 63390ed
Show file tree
Hide file tree
Showing 20 changed files with 463 additions and 179 deletions.
307 changes: 197 additions & 110 deletions index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion index.js.map

Large diffs are not rendered by default.

59 changes: 3 additions & 56 deletions src/geom/Geom.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import css from '../style/css';
import unit from '../style/unit';
import mode from '../util/mode';
import util from '../util/util';
import matrix from '../math/matrix';
// import geom from '../math/geom';

const { AUTO, PX, PERCENT } = unit;
const { clone, int2rgba, isNil, joinArr } = util;
const { int2rgba, isNil } = util;

const REGISTER = {};

Expand Down Expand Up @@ -267,6 +265,8 @@ class Geom extends Xom {
__renderAsMask(renderMode, ctx, defs, isClip) {
// mask渲染在canvas等被遮罩层调用,svg生成maskId
if(renderMode === mode.SVG) {
// 强制不缓存,防止引用mask的matrix变化不生效
this.__cancelCacheSvg();
this.render(renderMode, ctx, defs);
let vd = this.virtualDom;
if(isClip) {
Expand All @@ -275,51 +275,6 @@ class Geom extends Xom {
else {
vd.isMask = true;
}
// svg的mask没有transform,需手动计算变换后的坐标应用
let children = clone(vd.children);
let m = this.matrix;
// let { sx, sy, computedStyle: { marginLeft, borderLeftWidth, marginTop, borderTopWidth, transformOrigin: [ox, oy] } } = this;
// let offsetX = sx + marginLeft + borderLeftWidth + ox;
// let offsetY = sy + marginTop + borderTopWidth + oy;
children.forEach(child => {
let props = child.props;
if(child.tagName === 'path') {
for(let i = 0, len = props.length; i < len; i++) {
let [k, v] = props[i];
if(k === 'd') {
props[i][1] = v.replace(/([\d.]+),([\d.]+)/g, ($0, $1, $2) => {
return joinArr(matrix.calPoint([$1, $2], m), ',');
// let dx = parseFloat($1) - offsetX;
// let dy = parseFloat($2) - offsetY;
// let p = matrix.calPoint([dx, dy], m);
// p[0] += offsetX;
// p[1] += offsetY;
// return joinArr(p, ',');
});
break;
}
}
}
});
// 连续多个mask需要合并
let { prev } = this;
if(prev && (isClip ? prev.isClip : prev.isMask)) {
let last = defs.value;
last = last[last.length - 1];
last.children = last.children.concat(children);
return;
}
let id = defs.add({
tagName: isClip ? 'clipPath' : 'mask',
props: [],
children,
});
if(isClip) {
this.__clipId = 'url(#' + id + ')';
}
else {
this.__maskId = 'url(#' + id + ')';
}
}
}

Expand Down Expand Up @@ -371,14 +326,6 @@ class Geom extends Xom {
return this.__isClip;
}

get maskId() {
return this.__maskId;
}

get clipId() {
return this.__clipId;
}

get currentProps() {
return this.__currentProps;
}
Expand Down
4 changes: 2 additions & 2 deletions src/geom/Polyline.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class Polyline extends Geom {
// points/controls有变化就需要重建顶点
if(rebuild) {
if(isMulti) {
let list = pts.map((item, i) => {
let list = pts.filter(item => Array.isArray(item)).map((item, i) => {
let cl = cls[i];
if(Array.isArray(item)) {
return item.map((point, j) => {
Expand All @@ -135,7 +135,7 @@ class Polyline extends Geom {
}
}
else {
let list = pts.map((point, i) => {
let list = pts.filter(item => Array.isArray(item)).map((point, i) => {
if(i) {
return concatPointAndControl(point, cls[i - 1]);
}
Expand Down
77 changes: 77 additions & 0 deletions src/math/matrix.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,86 @@ function int2convolution(v) {
return d;
}

/**
* 初等行变换求3*3特定css的matrix方阵,一维6长度
* @param m
*/
function inverse(m) {
let [a, b, c, d, e, f] = m;
let ar = 1;
let br = 0;
let cr = 0;
let dr = 1;
let er = 0;
let fr = 0;
// 先检查a是否为0,强制a为1
if(a === 0) {
a = 1;
c += 1;
e += 1;
ar = 2;
cr = 1;
er = 1;
}
// b/a=x,R2-R1*x,b为0可优化
if(b !== 0) {
let x = b / a;
b = 0;
d -= c * x;
f -= e * x;
br = -x;
dr -= cr * x;
fr -= er * x;
}
// R1/a,a为1可优化
if(a !== 1) {
a = 1;
c /= a;
ar /= a;
cr /= a;
er /= a;
}
// c/e=y,R1-R2*y,c为0可优化
if(c !== 0) {
let y = c / e;
c = 0;
e -= f * y;
ar -= br * y;
cr -= dr * y;
er -= fr * y;
}
// 检查d是否为0,如果为0转成1,R2+1-R1
if(d === 0) {
d = 1;
f += 1 - e;
br += 1 - ar;
dr += 1 - cr;
fr += 1 - er;
}
// R2/d,d为1可优化
else if(d !== 1) {
f /= d;
br /= d;
dr /= d;
fr /= d;
d = 1;
}
// R1-R3*e,R2-R3*f,e/f为0可优化
if(e !== 0) {
er -= e;
e = 0;
}
if(f !== 0) {
fr -= f;
f = 0;
}
return [ar, br, cr, dr, er, fr];
}

export default {
identity,
multiply,
calPoint,
int2convolution,
inverse,
};
49 changes: 46 additions & 3 deletions src/node/Xom.js
Original file line number Diff line number Diff line change
Expand Up @@ -1224,7 +1224,7 @@ class Xom extends Node {
matrix = transform;
matrix = __cacheStyle.matrix = tf.calMatrixByOrigin(matrix, tfo);
}
let renderMatrix = matrix;
let renderMatrix = this.__svgMatrix = matrix;
// 变换对事件影响,canvas要设置渲染
if(p) {
matrix = mx.multiply(p.matrixEvent, matrix);
Expand Down Expand Up @@ -1719,12 +1719,55 @@ class Xom extends Node {
if(isEmpty) {
return;
}
// 应用mask本身的matrix,以及被遮罩对象的matrix逆
sibling = next;
let mChildren = [];
while(sibling) {
let { children } = sibling.virtualDom;
mChildren = mChildren.concat(children);
for(let i = 0, len = children.length; i < len; i++) {
let { tagName, props } = children[i];
if(tagName === 'path') {
let matrix = sibling.__svgMatrix;
let inverse = mx.inverse(this.matrix);
matrix = mx.multiply(matrix, inverse);
// transform属性放在最后一个省去循环
let len = props.length;
if(!len || props[len - 1][0] !== 'transform') {
props.push(['transform', `matrix(${matrix})`]);
}
else {
props[len - 1][1] = `matrix(${matrix})`;
}
}
}
sibling = sibling.next;
if(!sibling) {
break;
}
if(hasMask) {
if(!sibling.isMask) {
break;
}
}
else if(hasClip) {
if(!sibling.isClip) {
break;
}
}
}
let id = defs.add({
tagName: hasClip ? 'clipPath' : 'mask',
props: [],
children: mChildren,
});
id = 'url(#' + id + ')';
// 作为mask会在defs生成maskId供使用,多个连续mask共用一个id
if(hasMask) {
this.virtualDom.mask = next.maskId;
this.virtualDom.mask = id;
}
else if(hasClip) {
this.virtualDom.clip = next.clipId;
this.virtualDom.clip = id;
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/parser/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ function linkLibrary(item, hash) {
let { libraryId } = child;
// ide中库文件的child来自于库一定有libraryId,但是为了编程特殊需求,放开允许存入自定义数据
if(isNil(libraryId)) {
console.warn('Library item should have a libraryId: ' + JSON.stringify(child));
return;
}
let libraryItem = hash[libraryId];
Expand All @@ -105,14 +106,14 @@ function linkLibrary(item, hash) {
linkChild(child, libraryItem);
}
else {
throw new Error('Link library item miss id: ' + libraryId);
throw new Error('Link library item miss libraryId: ' + libraryId);
}
}
});
}
// library中一定有id,因为是一级,二级+特殊需求才会出现放开
if(isNil(id)) {
throw new Error('Library item miss id: ' + id);
throw new Error('Library item miss id: ' + JSON.stringify(item));
}
else {
hash[id] = item;
Expand Down Expand Up @@ -183,7 +184,7 @@ function parse(karas, json, animateRecords, vars, hash = {}) {
}
let { tagName, props = {}, children = [], animate = [], __animateRecords } = json;
if(!tagName) {
throw new Error('Dom must have a tagName: ' + json);
throw new Error('Dom must have a tagName: ' + JSON.stringify(json));
}
let style = props.style;
abbr2full(style, abbrCssProperty);
Expand Down
2 changes: 1 addition & 1 deletion test/domdiff-mask-svg/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
browser
.url('file://' + path.join(__dirname, 'index.html'))
.waitForElementVisible('body', 1000)
.assert.value('input', '{"bb":[],"children":[{"bb":[],"children":[{"type":"text","children":[{"type":"item","tagName":"text","props":[["x",0],["y",14.484375],["fill","rgba(0,0,0,1)"],["font-family","arial"],["font-weight",400],["font-style","normal"],["font-size","16px"]],"content":"looooooooooooooong"}]}],"opacity":1,"type":"dom"},{"bb":[{"type":"item","tagName":"rect","props":[["x",5],["y",10],["width",100],["height",100],["fill","rgba(255,0,0,1)"]]}],"children":[{"type":"text","children":[{"type":"item","tagName":"text","props":[["x",5],["y",24.484375],["fill","rgba(0,0,0,1)"],["font-family","arial"],["font-weight",400],["font-style","normal"],["font-size","16px"]],"content":"123"}]}],"opacity":1,"type":"dom","mask":"url(#karas-defs-0-0)"}],"opacity":1,"type":"dom","defs":[{"tagName":"mask","props":[],"children":[{"type":"item","tagName":"path","props":[["d","M0,0L100,100L0,100L0,0"],["fill","rgba(51,51,51,1)"],["stroke","rgba(0,0,0,1)"],["stroke-width",0]]}],"uuid":"karas-defs-0-0"}]}')
.assert.value('input', '{"bb":[],"children":[{"bb":[],"children":[{"type":"text","children":[{"type":"item","tagName":"text","props":[["x",0],["y",14.484375],["fill","rgba(0,0,0,1)"],["font-family","arial"],["font-weight",400],["font-style","normal"],["font-size","16px"]],"content":"looooooooooooooong"}]}],"opacity":1,"type":"dom"},{"bb":[{"type":"item","tagName":"rect","props":[["x",5],["y",10],["width",100],["height",100],["fill","rgba(255,0,0,1)"]]}],"children":[{"type":"text","children":[{"type":"item","tagName":"text","props":[["x",5],["y",24.484375],["fill","rgba(0,0,0,1)"],["font-family","arial"],["font-weight",400],["font-style","normal"],["font-size","16px"]],"content":"123"}]}],"opacity":1,"type":"dom","mask":"url(#karas-defs-0-0)"}],"opacity":1,"type":"dom","defs":[{"tagName":"mask","props":[],"children":[{"type":"item","tagName":"path","props":[["d","M0,0L100,100L0,100L0,0"],["fill","rgba(51,51,51,1)"],["stroke","rgba(0,0,0,1)"],["stroke-width",0],["transform","matrix(1,0,0,1,0,0)"]]}],"uuid":"karas-defs-0-0"}]}')
.end();
}
};
14 changes: 14 additions & 0 deletions test/mask-empty-svg/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=0"/>
<title>test</title>
</head>
<body>
<div id="test"></div>
<input id="base64" type="text" value=""/>
<script src="../../index.js"></script>
<script src="script.js"></script>
</body>
</html>
16 changes: 16 additions & 0 deletions test/mask-empty-svg/script.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
let o = karas.render(
<svg width="360" height="360">
<div>looooooooooooooong</div>
<div
ref="t"
style={{position:'absolute',left:5,top:10,width:100,height:100,background:'#F00'}}>123</div>
<$polygon
mask="true"
points={[
]}
style={{position:'absolute',left:0,top:0,width:100,height:100,strokeWidth:0,fill:'#EEE'}}/>
</svg>,
'#test'
);
var input = document.querySelector('#base64');
input.value = JSON.stringify(o.virtualDom);
12 changes: 12 additions & 0 deletions test/mask-empty-svg/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
var path = require('path');
var fs = require('fs');

module.exports = {
'init': function(browser) {
browser
.url('file://' + path.join(__dirname, 'index.html'))
.waitForElementVisible('body', 1000)
.assert.value('input', '{"bb":[],"children":[{"bb":[],"children":[{"type":"text","children":[{"type":"item","tagName":"text","props":[["x",0],["y",14.484375],["fill","rgba(0,0,0,1)"],["font-family","arial"],["font-weight",400],["font-style","normal"],["font-size","16px"]],"content":"looooooooooooooong"}]}],"opacity":1,"type":"dom"},{"bb":[{"type":"item","tagName":"rect","props":[["x",5],["y",10],["width",100],["height",100],["fill","rgba(255,0,0,1)"]]}],"children":[{"type":"text","children":[{"type":"item","tagName":"text","props":[["x",5],["y",24.484375],["fill","rgba(0,0,0,1)"],["font-family","arial"],["font-weight",400],["font-style","normal"],["font-size","16px"]],"content":"123"}]}],"opacity":1,"type":"dom"}],"opacity":1,"type":"dom","defs":[]}')
.end();
}
};
2 changes: 1 addition & 1 deletion test/mask-parent-transform-svg/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
browser
.url('file://' + path.join(__dirname, 'index.html'))
.waitForElementVisible('body', 1000)
.assert.value('input', '<svg width="360" height="360"><defs><mask id="karas-defs-0-0"><path d="M50,100L100,150L50,150L50,100" fill="rgba(255,255,255,1)" stroke="rgba(0,0,0,1)" stroke-width="0"></path></mask></defs><g></g><g><g transform="matrix(1,0,0,1,50,0)"><g></g><g><g mask="url(#karas-defs-0-0)"><g><rect x="0" y="0" width="360" height="360" fill="rgba(255,0,0,1)"></rect></g><g><g><text x="0" y="14.484375" fill="rgba(0,0,0,1)" font-family="arial" font-weight="400" font-style="normal" font-size="16px">123</text></g></g></g></g></g></g></svg>')
.assert.value('input', '<svg width="360" height="360"><defs><mask id="karas-defs-0-0"><path d="M50,50L100,100L50,100L50,50" fill="rgba(255,255,255,1)" stroke="rgba(0,0,0,1)" stroke-width="0" transform="matrix(1,0,0,1,0,50)"></path></mask></defs><g></g><g><g transform="matrix(1,0,0,1,50,0)"><g></g><g><g mask="url(#karas-defs-0-0)"><g><rect x="0" y="0" width="360" height="360" fill="rgba(255,0,0,1)"></rect></g><g><g><text x="0" y="14.484375" fill="rgba(0,0,0,1)" font-family="arial" font-weight="400" font-style="normal" font-size="16px">123</text></g></g></g></g></g></g></svg>')
.end();
}
};
2 changes: 1 addition & 1 deletion test/mask-svg/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
browser
.url('file://' + path.join(__dirname, 'index.html'))
.waitForElementVisible('body', 1000)
.assert.value('input', '{"bb":[],"children":[{"bb":[],"children":[{"type":"text","children":[{"type":"item","tagName":"text","props":[["x",0],["y",14.484375],["fill","rgba(0,0,0,1)"],["font-family","arial"],["font-weight",400],["font-style","normal"],["font-size","16px"]],"content":"looooooooooooooong"}]}],"opacity":1,"type":"dom"},{"bb":[{"type":"item","tagName":"rect","props":[["x",5],["y",10],["width",100],["height",100],["fill","rgba(255,0,0,1)"]]}],"children":[{"type":"text","children":[{"type":"item","tagName":"text","props":[["x",5],["y",24.484375],["fill","rgba(0,0,0,1)"],["font-family","arial"],["font-weight",400],["font-style","normal"],["font-size","16px"]],"content":"123"}]}],"opacity":1,"type":"dom","mask":"url(#karas-defs-0-0)"}],"opacity":1,"type":"dom","defs":[{"tagName":"mask","props":[],"children":[{"type":"item","tagName":"path","props":[["d","M0,0L100,100L0,100L0,0"],["fill","rgba(238,238,238,1)"],["stroke","rgba(0,0,0,1)"],["stroke-width",0]]}],"uuid":"karas-defs-0-0"}]}')
.assert.value('input', '{"bb":[],"children":[{"bb":[],"children":[{"type":"text","children":[{"type":"item","tagName":"text","props":[["x",0],["y",14.484375],["fill","rgba(0,0,0,1)"],["font-family","arial"],["font-weight",400],["font-style","normal"],["font-size","16px"]],"content":"looooooooooooooong"}]}],"opacity":1,"type":"dom"},{"bb":[{"type":"item","tagName":"rect","props":[["x",5],["y",10],["width",100],["height",100],["fill","rgba(255,0,0,1)"]]}],"children":[{"type":"text","children":[{"type":"item","tagName":"text","props":[["x",5],["y",24.484375],["fill","rgba(0,0,0,1)"],["font-family","arial"],["font-weight",400],["font-style","normal"],["font-size","16px"]],"content":"123"}]}],"opacity":1,"type":"dom","mask":"url(#karas-defs-0-0)"}],"opacity":1,"type":"dom","defs":[{"tagName":"mask","props":[],"children":[{"type":"item","tagName":"path","props":[["d","M0,0L100,100L0,100L0,0"],["fill","rgba(238,238,238,1)"],["stroke","rgba(0,0,0,1)"],["stroke-width",0],["transform","matrix(1,0,0,1,0,0)"]]}],"uuid":"karas-defs-0-0"}]}')
.end();
}
};
14 changes: 14 additions & 0 deletions test/mask-target-transform-absolute-svg/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=0"/>
<title>test</title>
</head>
<body>
<div id="test"></div>
<input id="base64" type="text" value=""/>
<script src="../../index.js"></script>
<script src="script.js"></script>
</body>
</html>
16 changes: 16 additions & 0 deletions test/mask-target-transform-absolute-svg/script.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
let o = karas.render(
<svg width="360" height="360">
<div style={{position:'absolute',left:100,top:100,width:100,height:100,
background:'#F00',translateX:30}}/>
<$polygon style={{position:'absolute',left:100,top:100,width:100,height:100,
fill:'rgba(255, 255, 255, 0.5)',strokeWidth:0,rotateZ:20}}
points={[
[0, 0],
[1, 0],
[0.5, 1]
]}mask="1"/>
</svg>,
'#test'
);
var input = document.querySelector('#base64');
input.value = JSON.stringify(o.virtualDom);
Loading

0 comments on commit 63390ed

Please sign in to comment.