mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-05-31 05:51:47 +00:00
add ClipPath element support
This commit is contained in:
@@ -7,13 +7,15 @@ import Svg, {
|
||||
Defs,
|
||||
RadialGradient,
|
||||
Stop,
|
||||
Line,
|
||||
Rect,
|
||||
Text,
|
||||
G
|
||||
Ellipse,
|
||||
G,
|
||||
Polygon,
|
||||
Circle
|
||||
} from 'react-native-art-svg';
|
||||
|
||||
class ClipPathExample extends Component{
|
||||
class ClipPathAttr extends Component{
|
||||
static title = 'Clip by set clip-path with a path data';
|
||||
render() {
|
||||
return <Svg
|
||||
@@ -46,7 +48,7 @@ class ClipPathExample extends Component{
|
||||
}
|
||||
}
|
||||
|
||||
class ClipRulePathExample extends Component{
|
||||
class ClipRule extends Component{
|
||||
static title = 'Clip a group with clipRule="evenodd"';
|
||||
render() {
|
||||
return <Svg
|
||||
@@ -90,6 +92,46 @@ class ClipRulePathExample extends Component{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ClipPathElement extends Component{
|
||||
static title = 'Clip by set clip-path with a path data';
|
||||
render() {
|
||||
return <Svg
|
||||
height="100"
|
||||
width="100"
|
||||
>
|
||||
<Defs>
|
||||
<RadialGradient id="grad" cx="50%" cy="50%" rx="50%" ry="50%" fx="50%" fy="50%">
|
||||
<Stop
|
||||
offset="0%"
|
||||
stopColor="#ff0"
|
||||
stopOpacity="1"
|
||||
/>
|
||||
<Stop
|
||||
offset="100%"
|
||||
stopColor="#00f"
|
||||
stopOpacity="1"
|
||||
/>
|
||||
</RadialGradient>
|
||||
<ClipPath id="clip">
|
||||
<Circle cx="30" cy="30" r="20"/>
|
||||
<Ellipse cx="60" cy="70" rx="20" ry="10" />
|
||||
<Rect x="65" y="15" width="30" height="30" />
|
||||
<Polygon points="20,60 20,80 50,70" />
|
||||
</ClipPath>
|
||||
</Defs>
|
||||
<Rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="100"
|
||||
height="100"
|
||||
fill="url(#grad)"
|
||||
clipPath="url(#clip)"
|
||||
/>
|
||||
</Svg>;
|
||||
}
|
||||
}
|
||||
|
||||
const icon = <Svg
|
||||
height="20"
|
||||
width="20"
|
||||
@@ -132,7 +174,7 @@ const icon = <Svg
|
||||
</G>
|
||||
</Svg>;
|
||||
|
||||
const samples = [ClipPathExample, ClipRulePathExample];
|
||||
const samples = [ClipPathAttr, ClipRule, ClipPathElement];
|
||||
|
||||
export {
|
||||
icon,
|
||||
|
||||
+7
-1
@@ -16,10 +16,16 @@ class Circle extends Component{
|
||||
cy: 0
|
||||
};
|
||||
|
||||
static getPath = props => Ellipse.getPath({
|
||||
cx: props.cx,
|
||||
cy: props.cy,
|
||||
rx: props.r,
|
||||
ry: props.r
|
||||
});
|
||||
|
||||
render() {
|
||||
return <Ellipse
|
||||
{...this.props}
|
||||
r={null}
|
||||
rx={+this.props.r}
|
||||
ry={+this.props.r}
|
||||
/>
|
||||
|
||||
+35
-2
@@ -4,6 +4,7 @@ import React, {
|
||||
Children
|
||||
} from 'react-native';
|
||||
import {NativeGroup} from './G';
|
||||
import {set, remove} from '../lib/extract/extractClipping';
|
||||
|
||||
class ClipPath extends Component{
|
||||
static displayName = 'ClipPath';
|
||||
@@ -11,9 +12,41 @@ class ClipPath extends Component{
|
||||
id: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.id = this.props.id + ':' + this.props.svgId;
|
||||
}
|
||||
|
||||
componentWillReceiveProps = nextProps => {
|
||||
let id = nextProps.id + ':' + nextProps.svgId;
|
||||
if (id !== this.id) {
|
||||
remove(this.id);
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount = () => {
|
||||
remove(this.id);
|
||||
};
|
||||
|
||||
_combinePaths = children => {
|
||||
// TODO: combine g elements and their children
|
||||
// TODO: combine text elements
|
||||
Children.forEach(children, child => {
|
||||
let {props, type: {getPath}} = child;
|
||||
|
||||
if (getPath) {
|
||||
this._path += getPath(props);
|
||||
}
|
||||
|
||||
this._combinePaths(props.children);
|
||||
});
|
||||
};
|
||||
|
||||
_path = '';
|
||||
|
||||
render() {
|
||||
// TODO: 合并children路径
|
||||
// TODO: clip-rule
|
||||
this._combinePaths(this.props.children);
|
||||
set(this.id, this._path);
|
||||
return <NativeGroup />;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -9,6 +9,7 @@ let map = {};
|
||||
|
||||
import LinearGradient from './LinearGradient';
|
||||
import RadialGradient from './RadialGradient';
|
||||
import ClipPath from './ClipPath';
|
||||
let onlyChild = Children.only;
|
||||
|
||||
class DefsItem extends Component{
|
||||
@@ -82,7 +83,9 @@ class Defs extends Component{
|
||||
|
||||
getChildren = () => {
|
||||
return Children.map(this.props.children, child => {
|
||||
if (child.type === LinearGradient || child.type === RadialGradient) {
|
||||
let {type} = child;
|
||||
|
||||
if (type === LinearGradient || type === RadialGradient || type === ClipPath) {
|
||||
return cloneElement(child, {
|
||||
svgId: this.props.svgId
|
||||
});
|
||||
|
||||
+9
-8
@@ -12,21 +12,22 @@ class Ellipse extends Component{
|
||||
rx: propType,
|
||||
ry: propType
|
||||
};
|
||||
render() {
|
||||
let {props} = this;
|
||||
let {cx, cy, rx, ry} = this.props;
|
||||
let d = `
|
||||
|
||||
static getPath = props => {
|
||||
let {cx, cy, rx, ry} = props;
|
||||
return `
|
||||
M ${cx - rx} ${cy}
|
||||
a ${rx}, ${ry} 0 1, 0 ${rx * 2}, 0
|
||||
a ${rx}, ${ry} 0 1, 0 ${-rx * 2}, 0
|
||||
Z
|
||||
`;
|
||||
};
|
||||
|
||||
render() {
|
||||
let {props} = this;
|
||||
let d = Ellipse.getPath(this.props);
|
||||
return <Path
|
||||
{...props}
|
||||
cx={null}
|
||||
cy={null}
|
||||
rx={null}
|
||||
ry={null}
|
||||
d={d}
|
||||
/>;
|
||||
}
|
||||
|
||||
+4
-5
@@ -18,16 +18,15 @@ class Line extends Component{
|
||||
strokeCap: PropTypes.oneOf(['butt', 'square', 'round'])
|
||||
};
|
||||
|
||||
|
||||
_convertPath = (props = this.props) => {
|
||||
return `M${props.x1},${props.y1}L${props.x2},${props.y2}Z`;
|
||||
};
|
||||
static getPath = (props) => (
|
||||
`M${props.x1},${props.y1}L${props.x2},${props.y2}Z`
|
||||
);
|
||||
|
||||
render() {
|
||||
return <Path
|
||||
{...this.props}
|
||||
ref="shape"
|
||||
d={this._convertPath()}
|
||||
d={Line.getPath(this.props)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ class Path extends Component{
|
||||
strokeDasharray: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.number)])
|
||||
};
|
||||
|
||||
static getPath = props => props.d;
|
||||
|
||||
_dimensions = null;
|
||||
|
||||
componentWillReceiveProps = nextProps => {
|
||||
|
||||
+4
-5
@@ -9,13 +9,12 @@ class Polygon extends Component{
|
||||
static propTypes = {
|
||||
points: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||
};
|
||||
static getPath = props => (`M${props.points.trim().replace(/\s+/g, 'L')}z`);
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
let d = 'M' + props.points.trim().replace(/\s+/g, 'L') + 'z';
|
||||
return <Path
|
||||
{...props}
|
||||
points={null}
|
||||
d={d}
|
||||
{...this.props}
|
||||
d={Polygon.getPath(this.props)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,12 @@ class Polyline extends Component{
|
||||
strokeLinejoin: PropTypes.oneOf(['miter', 'bevel', 'round']),
|
||||
strokeJoin: PropTypes.oneOf(['miter', 'bevel', 'round'])
|
||||
};
|
||||
static getPath = props => (`M${props.points.trim().replace(/\s+/g, 'L')}`);
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
let d = 'M' + props.points.trim().replace(/\s+/g, 'L');
|
||||
return <Path
|
||||
{...props}
|
||||
points={null}
|
||||
d={d}
|
||||
{...this.props}
|
||||
d={Polyline.getPath(this.props)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
+46
-25
@@ -12,6 +12,38 @@ function processRadius(radius) {
|
||||
return radius || 0;
|
||||
}
|
||||
|
||||
function getR(props) {
|
||||
let {
|
||||
width,
|
||||
height,
|
||||
rx,
|
||||
ry
|
||||
} = props;
|
||||
|
||||
rx = processRadius(rx);
|
||||
ry = processRadius(ry);
|
||||
|
||||
if ((rx && !ry) || (ry && !rx)) {
|
||||
if (rx) {
|
||||
ry = rx;
|
||||
} else {
|
||||
rx = ry;
|
||||
}
|
||||
}
|
||||
|
||||
if (rx > width / 2) {
|
||||
rx = width / 2;
|
||||
}
|
||||
if (ry > height / 2) {
|
||||
ry = height / 2;
|
||||
}
|
||||
|
||||
return {
|
||||
rx,
|
||||
ry
|
||||
};
|
||||
}
|
||||
|
||||
class Rect extends Component{
|
||||
static displayName = 'Rect';
|
||||
static propTypes = {
|
||||
@@ -32,36 +64,23 @@ class Rect extends Component{
|
||||
rx: 0,
|
||||
ry: 0
|
||||
};
|
||||
render() {
|
||||
let {props} = this;
|
||||
static getPath = (props, r) => {
|
||||
let {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
rx,
|
||||
ry
|
||||
height
|
||||
} = props;
|
||||
|
||||
rx = processRadius(rx);
|
||||
ry = processRadius(ry);
|
||||
if (!r) {
|
||||
r = getR(props);
|
||||
|
||||
if ((rx && !ry) || (ry && !rx)) {
|
||||
if (rx) {
|
||||
ry = rx;
|
||||
} else {
|
||||
rx = ry;
|
||||
}
|
||||
}
|
||||
|
||||
let {rx, ry} = r;
|
||||
|
||||
if (rx > width / 2) {
|
||||
rx = width / 2;
|
||||
}
|
||||
if (ry > height / 2) {
|
||||
ry = height / 2;
|
||||
}
|
||||
|
||||
let d = (rx || ry) ? `
|
||||
return (rx || ry) ? `
|
||||
M ${x}, ${y}
|
||||
h ${width - 2 * rx}
|
||||
a ${rx},${ry} 0 0 1 ${rx},${ry}
|
||||
@@ -79,15 +98,17 @@ class Rect extends Component{
|
||||
h ${-width}
|
||||
Z
|
||||
`;
|
||||
};
|
||||
|
||||
render() {
|
||||
let r = getR(this.props);
|
||||
return <Path
|
||||
{...props}
|
||||
rx={null}
|
||||
ry={null}
|
||||
{...this.props}
|
||||
width={null}
|
||||
height={null}
|
||||
x={rx || null}
|
||||
x={r.rx || null}
|
||||
y={null}
|
||||
d={d}
|
||||
d={Rect.getPath(this.props, r)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import Defs from './elements/Defs';
|
||||
import LinearGradient from './elements/LinearGradient';
|
||||
import RadialGradient from './elements/RadialGradient';
|
||||
import Stop from './elements/Stop';
|
||||
import ClipPath from './elements/ClipPath';
|
||||
|
||||
export {
|
||||
Svg,
|
||||
@@ -34,7 +35,8 @@ export {
|
||||
Defs,
|
||||
LinearGradient,
|
||||
RadialGradient,
|
||||
Stop
|
||||
Stop,
|
||||
ClipPath
|
||||
};
|
||||
|
||||
export default Svg;
|
||||
|
||||
@@ -1,18 +1,47 @@
|
||||
import SerializablePath from 'react-native/Libraries/ART/ARTSerializablePath';
|
||||
import clipReg from './patternReg';
|
||||
|
||||
let clipPatterns = {};
|
||||
const clipRules = {
|
||||
evenodd: 0,
|
||||
nonzero: 1
|
||||
};
|
||||
|
||||
function set(id, pattern) {
|
||||
clipPatterns[id] = pattern;
|
||||
}
|
||||
|
||||
function remove(id) {
|
||||
delete clipPatterns[id];
|
||||
}
|
||||
|
||||
|
||||
export default function (props) {
|
||||
let {clipPath, clipRule} = props;
|
||||
let clippingProps = {};
|
||||
|
||||
if (clipPath) {
|
||||
clippingProps.clipPath = new SerializablePath(clipPath).toJSON();
|
||||
clippingProps.clipRule = clipRules[clipRule] === 0 ? 0 : 1;
|
||||
|
||||
let matched = clipPath.match(clipReg);
|
||||
|
||||
if (matched) {
|
||||
let patternName = `${matched[1]}:${props.svgId}`;
|
||||
let pattern = clipPatterns[patternName];
|
||||
if (pattern) {
|
||||
clippingProps.clipPath = new SerializablePath(pattern).toJSON();
|
||||
} else {
|
||||
// TODO: warn
|
||||
}
|
||||
} else {
|
||||
clippingProps.clipPath = new SerializablePath(clipPath).toJSON();
|
||||
}
|
||||
}
|
||||
|
||||
return clippingProps;
|
||||
}
|
||||
|
||||
export {
|
||||
set,
|
||||
remove
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import rgba from '../rgba';
|
||||
import {LinearGradientGenerator} from '../../elements/LinearGradient';
|
||||
import {RadialGradientGenerator} from '../../elements/RadialGradient';
|
||||
import extractBrush from './extractBrush';
|
||||
import fillReg from './patternReg';
|
||||
|
||||
let fillPatterns = {};
|
||||
let fillReg = /^url\(#(\w+?)\)$/;
|
||||
|
||||
function isGradient(obj) {
|
||||
return obj instanceof LinearGradientGenerator || obj instanceof RadialGradientGenerator;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default /^url\(#(\w+?)\)$/;
|
||||
Reference in New Issue
Block a user