Skip to content

Commit f20e6f2

Browse files
committed
feat: 添加解析伪类能力
1 parent 88f972a commit f20e6f2

File tree

6 files changed

+216
-20
lines changed

6 files changed

+216
-20
lines changed

example/pages/index.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
import React, { useState } from 'react';
2-
import { Button, Row, Col, Radio } from 'antd';
2+
import { Button, Row, Col, Radio, Switch } from 'antd';
33
import ReactJson from 'react-json-view';
44

55
import { nodeToSketchGroup, parserSymbol } from '../../lib';
66
export default () => {
77
const [json, setJSON] = useState({});
88
const generate = () => {
99
const el = document.getElementById('test');
10-
const json = nodeToSketchGroup(el).toSketchJSON();
10+
const switchObj = nodeToSketchGroup(el);
11+
12+
const json = switchObj.toSketchJSON();
13+
json.name = 'Switch';
14+
console.log(switchObj);
1115

1216
setJSON(json);
1317
};
1418
const generateSymbol = () => {
1519
const el = document.getElementById('symbol');
1620
const json = parserSymbol(el).toSketchJSON();
1721

22+
json.name = 'Switch';
1823
setJSON(json);
1924
};
2025

2126
return (
2227
<Row>
2328
<Col span={12}>
2429
<div id="test">
25-
<Radio id="symbol">123</Radio>
30+
<Switch defaultChecked />
2631
{/* <Button type={'dashed'} id="symbol">
2732
测试
2833
</Button> */}

src/function/nodeToSketchGroup.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { getName } from '../helpers/name';
44
import { Group, Style } from '../model';
55
import { AnyLayer } from '../model/utils';
66

7+
import { isExistPseudo } from '../helpers/shape';
8+
79
/**
810
* 获得可用的节点子级
911
*/
@@ -86,7 +88,7 @@ const nodeToSketchGroup = (node: Element, options?: any): AnyLayer => {
8688
return layer;
8789
}
8890

89-
if (group.layers.length === 0) {
91+
if (group.layers.length === 0 && !isExistPseudo(node)) {
9092
console.log('该 group 是空的,丢弃...');
9193
return;
9294
}

src/function/nodeToSketchLayers.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import { defaultNodeStyle } from '../model/utils';
33
import transferToSvg from '../parser/svg';
44
import transferToShape from '../parser/shape';
55
import transferToText from '../parser/text';
6+
import praserPseudo from '../parser/pseudo';
67

78
import { isTextVisible } from '../helpers/visibility';
89
import { isTextNode } from '../helpers/nodeType';
910
import { AnyLayer } from '../model/utils';
11+
import { isExistPseudo } from '../helpers/shape';
1012

1113
/**
1214
* 是否是默认样式
@@ -69,26 +71,28 @@ const nodeToSketchLayers = (node: Element): AnyLayer[] => {
6971
const isText = isTextNode(node);
7072

7173
// 如果图层存在样式(阴影 边框等 返回 shape 节点
72-
if (isImage || isShape) {
74+
if (isImage || isShape || isExistPseudo(node)) {
7375
// 判断一下是否有伪类
74-
const beforeElt: CSSStyleDeclaration = getComputedStyle(node, ':before');
75-
if (beforeElt.content === 'none') {
76-
console.log('No before Pseudo');
77-
} else {
78-
console.log('有伪类');
76+
const afterEl = praserPseudo(node, 'after');
77+
console.log(afterEl);
78+
79+
if (afterEl) {
80+
layers.push(afterEl);
7981
}
8082

81-
const shape = transferToShape(node);
82-
console.log('转换为 Rectangle: ', shape);
83-
layers.push(shape);
84-
// 添加后继续执行,不终止
83+
if (isImage || isShape) {
84+
// 添加后继续执行,不终止
85+
const shape = transferToShape(node);
86+
console.log('转换为 Rectangle: ', shape);
87+
layers.push(shape);
88+
}
8589

86-
const afterElt: CSSStyleDeclaration = getComputedStyle(node, ':after');
90+
// 判断一下是否有伪类
91+
const beforeEl = praserPseudo(node, 'before');
92+
console.log(beforeEl);
8793

88-
if (afterElt.content === 'none') {
89-
console.log('No after Pseudo');
90-
} else {
91-
console.log('有伪类');
94+
if (beforeEl) {
95+
layers.push(beforeEl);
9296
}
9397
}
9498

src/helpers/shape.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import parsePseudo from '../parser/pseudo';
2+
3+
/**
4+
* 解析圆角
5+
* @param borderRadius
6+
* @param width
7+
* @param height
8+
*/
9+
export const parserBorderRadius = (
10+
borderRadius: string,
11+
width: number,
12+
height: number
13+
) => {
14+
const matches = borderRadius.match(/^([0-9.]+)(.+)$/);
15+
16+
// Sketch uses 'px' units for border radius, so we need to convert % to px
17+
if (matches && matches[2] === '%') {
18+
const baseVal = Math.max(width, height);
19+
const percentageApplied = baseVal * (parseInt(matches[1], 10) / 100);
20+
21+
return Math.round(percentageApplied);
22+
}
23+
return parseInt(borderRadius, 10);
24+
};
25+
26+
/**
27+
* 判断是否存在伪类
28+
*/
29+
export const isExistPseudo = (node: Element) =>
30+
!!(parsePseudo(node, 'after') || parsePseudo(node, 'before'));

src/parser/pseudo.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { Style, Rectangle } from '../model';
2+
import { parserBorderRadius } from '../helpers/shape';
3+
import { defaultNodeStyle } from '../model/utils';
4+
import { splitShadowString, shadowStringToObject } from '../helpers/shadow';
5+
6+
/**
7+
* 解析伪类
8+
*/
9+
const parsePseudo = (node: Element, pseudoElt: 'before' | 'after') => {
10+
// 判断一下是否有伪类
11+
const pseudoEl: CSSStyleDeclaration = getComputedStyle(node, ':' + pseudoElt);
12+
if (pseudoEl.content === 'none') {
13+
console.log(`No ${pseudoElt} Pseudo`);
14+
return null;
15+
}
16+
console.log('有伪类');
17+
const bcr = node.getBoundingClientRect();
18+
const { left, top } = bcr;
19+
const width = bcr.right - bcr.left;
20+
const height = bcr.bottom - bcr.top;
21+
22+
const style = new Style();
23+
24+
const {
25+
// 背景颜色
26+
backgroundColor,
27+
28+
// 边框
29+
borderColor,
30+
borderWidth,
31+
borderTopWidth,
32+
borderRightWidth,
33+
borderBottomWidth,
34+
borderLeftWidth,
35+
borderTopColor,
36+
borderRightColor,
37+
borderBottomColor,
38+
borderLeftColor,
39+
borderTopLeftRadius,
40+
borderTopRightRadius,
41+
borderBottomLeftRadius,
42+
borderBottomRightRadius,
43+
borderBottomStyle,
44+
borderLeftStyle,
45+
borderTopStyle,
46+
borderRightStyle,
47+
boxShadow,
48+
} = pseudoEl;
49+
50+
if (backgroundColor) {
51+
style.addColorFill(backgroundColor);
52+
}
53+
54+
const rect: Rectangle | null = new Rectangle({
55+
width,
56+
height,
57+
58+
x: left,
59+
y: top,
60+
});
61+
// support for one-side borders (using inner shadow because Sketch doesn't support that)
62+
if (borderWidth.indexOf(' ') === -1) {
63+
style.addBorder({
64+
color: borderColor,
65+
thickness: parseFloat(borderWidth),
66+
});
67+
68+
// 如果是虚线
69+
const isDashed =
70+
borderBottomStyle === 'dashed' &&
71+
borderLeftStyle === 'dashed' &&
72+
borderTopStyle === 'dashed' &&
73+
borderRightStyle === 'dashed';
74+
if (isDashed) {
75+
style.setBorderDashed({
76+
dash: 3 * parseFloat(borderWidth),
77+
spacing: 3 * parseFloat(borderWidth),
78+
});
79+
}
80+
// 如果是点
81+
const isDotted =
82+
borderBottomStyle === 'dotted' &&
83+
borderLeftStyle === 'dotted' &&
84+
borderTopStyle === 'dotted' &&
85+
borderRightStyle === 'dotted';
86+
87+
if (isDotted) {
88+
style.setBorderDashed({
89+
dash: parseFloat(borderWidth),
90+
spacing: parseFloat(borderWidth),
91+
});
92+
}
93+
} else {
94+
const borderTopWidthFloat = parseFloat(borderTopWidth);
95+
const borderRightWidthFloat = parseFloat(borderRightWidth);
96+
const borderBottomWidthFloat = parseFloat(borderBottomWidth);
97+
const borderLeftWidthFloat = parseFloat(borderLeftWidth);
98+
99+
if (borderTopWidthFloat !== 0) {
100+
style.addInnerShadow({
101+
color: borderTopColor,
102+
offsetY: borderTopWidthFloat,
103+
});
104+
}
105+
if (borderRightWidthFloat !== 0) {
106+
style.addInnerShadow({
107+
color: borderRightColor,
108+
offsetX: -borderRightWidthFloat,
109+
});
110+
}
111+
if (borderBottomWidthFloat !== 0) {
112+
style.addInnerShadow({
113+
color: borderBottomColor,
114+
offsetY: -borderBottomWidthFloat,
115+
});
116+
}
117+
if (borderLeftWidthFloat !== 0) {
118+
style.addInnerShadow({
119+
color: borderLeftColor,
120+
offsetX: borderLeftWidthFloat,
121+
});
122+
}
123+
}
124+
125+
if (boxShadow !== defaultNodeStyle.boxShadow) {
126+
const shadowStrings = splitShadowString(boxShadow);
127+
128+
shadowStrings.forEach((shadowString: string) => {
129+
const shadowObject = shadowStringToObject(shadowString);
130+
131+
if (shadowObject!.inset) {
132+
if (borderWidth.indexOf(' ') === -1) {
133+
shadowObject!.spread += parseFloat(borderWidth);
134+
}
135+
style.addInnerShadow(shadowObject);
136+
} else {
137+
style.addShadow(shadowObject);
138+
}
139+
});
140+
}
141+
rect.style = style;
142+
//TODO borderRadius can be expressed in different formats and use various units - for simplicity we assume "X%"
143+
const cornerRadius = {
144+
topLeft: parserBorderRadius(borderTopLeftRadius, width, height),
145+
topRight: parserBorderRadius(borderTopRightRadius, width, height),
146+
bottomLeft: parserBorderRadius(borderBottomLeftRadius, width, height),
147+
bottomRight: parserBorderRadius(borderBottomRightRadius, width, height),
148+
};
149+
150+
rect.cornerRadius = cornerRadius;
151+
152+
return rect;
153+
};
154+
155+
export default parsePseudo;

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@
1919
"esModuleInterop": true,
2020
"lib": ["dom", "es2015", "es2016", "es2017"],
2121
"allowSyntheticDefaultImports": true,
22-
"baseUrl": "."
22+
"baseUrl": "./"
2323
}
2424
}

0 commit comments

Comments
 (0)