Gradient supports percent value
Gradient supports percent value
This commit is contained in:
Родитель
da5a23c5dd
Коммит
e181a1a546
|
@ -8,7 +8,8 @@ import Svg, {
|
|||
RadialGradient,
|
||||
Stop,
|
||||
Ellipse,
|
||||
Circle
|
||||
Circle,
|
||||
Text
|
||||
} from 'react-native-art-svg';
|
||||
|
||||
class LinearGradientHorizontal extends Component{
|
||||
|
@ -20,8 +21,8 @@ class LinearGradientHorizontal extends Component{
|
|||
>
|
||||
<Defs>
|
||||
<LinearGradient id="grad" x1="0" y1="0" x2="170" y2="0">
|
||||
<Stop offset="0%" stopColor="rgb(255,255,0)" stopOpacity="0" />
|
||||
<Stop offset="100%" stopColor="red" stopOpacity="1" />
|
||||
<Stop offset="0" stopColor="rgb(255,255,0)" stopOpacity="0" />
|
||||
<Stop offset="1" stopColor="red" stopOpacity="1" />
|
||||
</LinearGradient>
|
||||
</Defs>
|
||||
<Ellipse cx="150" cy="75" rx="85" ry="55" fill="url(#grad)" />
|
||||
|
@ -47,6 +48,26 @@ class LinearGradientVertical extends Component{
|
|||
}
|
||||
}
|
||||
|
||||
class LinearGradientPercent extends Component{
|
||||
static title = 'Define a linear gradient in percent unit';
|
||||
render() {
|
||||
return <Svg
|
||||
height="150"
|
||||
width="300"
|
||||
>
|
||||
<Defs>
|
||||
<LinearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<Stop offset="0%" stopColor="rgb(255,255,0)" stopOpacity="0" />
|
||||
<Stop offset="100%" stopColor="red" stopOpacity="1" />
|
||||
</LinearGradient>
|
||||
</Defs>
|
||||
<Text x="25" y="70" fill="#333">x1=0%</Text>
|
||||
<Text x="235" y="70" fill="#333">x2=100%</Text>
|
||||
<Ellipse cx="150" cy="75" rx="85" ry="55" fill="url(#grad)" />
|
||||
</Svg>;
|
||||
}
|
||||
}
|
||||
|
||||
class RadialGradientExample extends Component{
|
||||
static title = 'Define an ellipse with a radial gradient from yellow to purple';
|
||||
render() {
|
||||
|
@ -57,12 +78,12 @@ class RadialGradientExample extends Component{
|
|||
<Defs>
|
||||
<RadialGradient id="grad" cx="150" cy="75" rx="85" ry="55" fx="150" fy="75">
|
||||
<Stop
|
||||
offset="0%"
|
||||
offset="0"
|
||||
stopColor="#ff0"
|
||||
stopOpacity="1"
|
||||
/>
|
||||
<Stop
|
||||
offset="100%"
|
||||
offset="1"
|
||||
stopColor="#83a"
|
||||
stopOpacity="1"
|
||||
/>
|
||||
|
@ -73,7 +94,7 @@ class RadialGradientExample extends Component{
|
|||
}
|
||||
}
|
||||
|
||||
class RadialGradientExample2 extends Component{
|
||||
class RadialGradientPart extends Component{
|
||||
static title = 'Define another ellipse with a radial gradient from white to blue';
|
||||
render() {
|
||||
return <Svg
|
||||
|
@ -99,6 +120,32 @@ class RadialGradientExample2 extends Component{
|
|||
}
|
||||
}
|
||||
|
||||
class RadialGradientPercent extends Component{
|
||||
static title = 'Define a radial gradient in percent unit';
|
||||
render() {
|
||||
return <Svg
|
||||
height="150"
|
||||
width="300"
|
||||
>
|
||||
<Defs>
|
||||
<RadialGradient id="grad" cx="50%" cy="50%" rx="50%" ry="50%" fx="50%" fy="50%">
|
||||
<Stop
|
||||
offset="0%"
|
||||
stopColor="#fff"
|
||||
stopOpacity="1"
|
||||
/>
|
||||
<Stop
|
||||
offset="100%"
|
||||
stopColor="#00f"
|
||||
stopOpacity="1"
|
||||
/>
|
||||
</RadialGradient>
|
||||
</Defs>
|
||||
<Ellipse cx="150" cy="75" rx="85" ry="55" fill="url(#grad)" />
|
||||
</Svg>;
|
||||
}
|
||||
}
|
||||
|
||||
const icon = <Svg
|
||||
height="20"
|
||||
width="20"
|
||||
|
@ -113,7 +160,7 @@ const icon = <Svg
|
|||
</Svg>;
|
||||
|
||||
|
||||
const samples = [LinearGradientHorizontal, LinearGradientVertical, RadialGradientExample, RadialGradientExample2];
|
||||
const samples = [LinearGradientHorizontal, LinearGradientVertical, LinearGradientPercent, RadialGradientExample, RadialGradientPercent, RadialGradientPart];
|
||||
|
||||
export {
|
||||
icon,
|
||||
|
|
|
@ -18,3 +18,7 @@
|
|||
3. textPath (wait for official supports)
|
||||
4. pattern
|
||||
5. animations https://github.com/maxwellito/vivus
|
||||
|
||||
### Thanks
|
||||
[SVG bounding Algorithm](https://github.com/icons8/svg-path-bounding-box)
|
||||
[Circle drawing with svg arc path](http://stackoverflow.com/questions/5737975/circle-drawing-with-svgs-arc-path/10477334#10477334)
|
||||
|
|
|
@ -9,9 +9,10 @@ let {
|
|||
LinearGradient: ARTLinearGradient
|
||||
} = ART;
|
||||
import {set, remove} from '../lib/fillFilter';
|
||||
import percentFactory from '../lib/percentFactory';
|
||||
import percentToFloat from '../lib/percentToFloat';
|
||||
import Stop from './Stop';
|
||||
import Color from 'color';
|
||||
let percentReg = /^(\-?\d+(?:\.\d+)?)(%?)$/;
|
||||
let propType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]);
|
||||
class LinearGradient extends Component{
|
||||
static displayName = 'LinearGradient';
|
||||
|
@ -49,16 +50,26 @@ class LinearGradient extends Component{
|
|||
let stops = {};
|
||||
Children.forEach(this.props.children, child => {
|
||||
if (child.type === Stop && child.props.stopColor && child.props.offset) {
|
||||
|
||||
// convert percent to float.
|
||||
let matched = child.props.offset.match(percentReg);
|
||||
let offset = matched[2] ? matched[1] / 100 : matched[1];
|
||||
let offset = percentToFloat(child.props.offset);
|
||||
|
||||
// add stop
|
||||
stops[offset] = Color(child.props.stopColor).alpha(+child.props.stopOpacity).rgbaString();
|
||||
|
||||
// TODO: convert percent to float.
|
||||
let factories = percentFactory(x1, y1, x2, y2);
|
||||
if (factories) {
|
||||
set(this.id, function (boundingBox) {
|
||||
return new ARTLinearGradient(
|
||||
stops,
|
||||
factories[0](boundingBox.width),
|
||||
factories[1](boundingBox.height),
|
||||
factories[2](boundingBox.width),
|
||||
factories[3](boundingBox.height)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
set(this.id, new ARTLinearGradient(stops, x1, y1, x2, y2));
|
||||
}
|
||||
} else {
|
||||
console.warn(`'LinearGradient' can only receive 'Stop' elements as children`);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ let {
|
|||
Shape
|
||||
} = ART;
|
||||
import Defs from './Defs';
|
||||
import calculateBoundingBox from '../lib/calculateBoundingBox';
|
||||
|
||||
import fillFilter from '../lib/fillFilter';
|
||||
import strokeFilter from '../lib/strokeFilter';
|
||||
|
@ -25,6 +26,23 @@ class Path extends Component{
|
|||
strokeJoin: PropTypes.oneOf(['miter', 'bevel', 'round']),
|
||||
strokeDasharray: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.number)])
|
||||
};
|
||||
|
||||
_dimensions = null;
|
||||
|
||||
componentWillReceiveProps = nextProps => {
|
||||
if (nextProps.d !== this.props.d) {
|
||||
this._dimensions = null;
|
||||
}
|
||||
};
|
||||
|
||||
getBoundingBox = () => {
|
||||
if (!this._dimensions) {
|
||||
this._dimensions = calculateBoundingBox(this.props.d);
|
||||
}
|
||||
|
||||
return this._dimensions;
|
||||
};
|
||||
|
||||
render() {
|
||||
let {props} = this;
|
||||
if (props.id) {
|
||||
|
@ -40,7 +58,7 @@ class Path extends Component{
|
|||
{...props}
|
||||
{...strokeFilter(props)}
|
||||
{...transformFilter(props)}
|
||||
fill={fillFilter(props)}
|
||||
fill={fillFilter.call(this, props)}
|
||||
id={null}
|
||||
/>;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ let {
|
|||
RadialGradient: ARTRadialGradient
|
||||
} = ART;
|
||||
import {set, remove} from '../lib/fillFilter';
|
||||
import percentFactory from '../lib/percentFactory';
|
||||
import percentToFloat from '../lib/percentToFloat';
|
||||
import Stop from './Stop';
|
||||
import Color from 'color';
|
||||
let percentReg = /^(\-?\d+(?:\.\d+)?)(%?)$/;
|
||||
|
@ -60,17 +62,30 @@ class RadialGradient extends Component{
|
|||
let stops = {};
|
||||
Children.forEach(this.props.children, child => {
|
||||
if (child.type === Stop && child.props.stopColor && child.props.offset) {
|
||||
|
||||
// convert percent to float.
|
||||
let matched = child.props.offset.match(percentReg);
|
||||
let offset = matched[2] ? matched[1] / 100 : matched[1];
|
||||
let offset = percentToFloat(child.props.offset);
|
||||
|
||||
// add stop
|
||||
stops[offset] = Color(child.props.stopColor).alpha(+child.props.stopOpacity).rgbaString();
|
||||
|
||||
// TODO: convert percent to float.
|
||||
set(this.id, new ARTRadialGradient(stops, fx, fy, rx, ry, cx, cy));
|
||||
|
||||
let factories = percentFactory(fx, fy, rx, ry, cx, cy);
|
||||
if (factories) {
|
||||
set(this.id, function (boundingBox) {
|
||||
let {x1,y1,width, height} = boundingBox;
|
||||
return new ARTRadialGradient(
|
||||
stops,
|
||||
x1 + factories[0](width),
|
||||
y1 + factories[1](height),
|
||||
factories[2](width),
|
||||
factories[3](height),
|
||||
x1 + factories[4](width),
|
||||
y1 + factories[5](height)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
set(this.id, new ARTRadialGradient(stops, fx, fy, rx, ry, cx, cy));
|
||||
}
|
||||
} else {
|
||||
console.warn(`'RadialGradient' can only receive 'Stop' elements as children`);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ class Text extends Component{
|
|||
<Text {...this.props} id={null} />
|
||||
</Defs.Item>;
|
||||
}
|
||||
|
||||
// TODO: support percent gradients
|
||||
return <ARTText
|
||||
{...props}
|
||||
{...transformFilter(props)}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
// from https://github.com/gabelerner/canvg/blob/860e418aca67b9a41e858a223d74d375793ec364/canvg.js#L449
|
||||
|
||||
class BoundingBox{
|
||||
constructor(x1, y1, x2, y2) {
|
||||
this.x1 = NaN;
|
||||
this.y1 = NaN;
|
||||
this.x2 = NaN;
|
||||
this.y2 = NaN;
|
||||
|
||||
this.addPoint(x1, y1);
|
||||
this.addPoint(x2, y2);
|
||||
}
|
||||
|
||||
width = () => this.x2 - this.x1;
|
||||
|
||||
height = () => this.y2 - this.y1;
|
||||
|
||||
addPoint = (x, y) => {
|
||||
if (x != null) {
|
||||
if (isNaN(this.x1) || isNaN(this.x2)) {
|
||||
this.x1 = x;
|
||||
this.x2 = x;
|
||||
}
|
||||
if (x < this.x1) this.x1 = x;
|
||||
if (x > this.x2) this.x2 = x;
|
||||
}
|
||||
|
||||
if (y != null) {
|
||||
if (isNaN(this.y1) || isNaN(this.y2)) {
|
||||
this.y1 = y;
|
||||
this.y2 = y;
|
||||
}
|
||||
if (y < this.y1) this.y1 = y;
|
||||
if (y > this.y2) this.y2 = y;
|
||||
}
|
||||
};
|
||||
|
||||
addX = x => this.addPoint(x, null);
|
||||
|
||||
addY = y => this.addPoint(null, y);
|
||||
|
||||
addQuadraticCurve = (p0x, p0y, p1x, p1y, p2x, p2y) => {
|
||||
let cp1x = p0x + 2 / 3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
|
||||
let cp1y = p0y + 2 / 3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
|
||||
let cp2x = cp1x + 1 / 3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
|
||||
let cp2y = cp1y + 1 / 3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
|
||||
this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);
|
||||
};
|
||||
|
||||
addBezierCurve = (p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) => {
|
||||
// from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
|
||||
var p0 = [p0x, p0y],
|
||||
p1 = [p1x, p1y],
|
||||
p2 = [p2x, p2y],
|
||||
p3 = [p3x, p3y];
|
||||
|
||||
this.addPoint(p0[0], p0[1]);
|
||||
this.addPoint(p3[0], p3[1]);
|
||||
|
||||
[0, 1].forEach(i => {
|
||||
let f = t => {
|
||||
return Math.pow(1 - t, 3) * p0[i]
|
||||
+ 3 * Math.pow(1 - t, 2) * t * p1[i]
|
||||
+ 3 * (1 - t) * Math.pow(t, 2) * p2[i]
|
||||
+ Math.pow(t, 3) * p3[i];
|
||||
};
|
||||
|
||||
let b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
|
||||
let a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
|
||||
let c = 3 * p1[i] - 3 * p0[i];
|
||||
|
||||
if (a == 0) {
|
||||
if (b != 0) {
|
||||
let t = -c / b;
|
||||
if (0 < t && t < 1) {
|
||||
if (i == 0) this.addX(f(t));
|
||||
if (i == 1) this.addY(f(t));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let b2ac = Math.pow(b, 2) - 4 * c * a;
|
||||
if (b2ac < 0) {
|
||||
return;
|
||||
}
|
||||
let t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
|
||||
if (0 < t1 && t1 < 1) {
|
||||
i == 0 && this.addX(f(t1));
|
||||
i == 1 && this.addY(f(t1));
|
||||
}
|
||||
let t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
|
||||
if (0 < t2 && t2 < 1) {
|
||||
i == 0 && this.addX(f(t2));
|
||||
i == 1 && this.addY(f(t2));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default BoundingBox;
|
|
@ -0,0 +1,59 @@
|
|||
import SvgPath from 'svgpath';
|
||||
import BoundingBox from './BoundingBox';
|
||||
|
||||
export default function(d) {
|
||||
let pathDriver = new SvgPath(d);
|
||||
let boundingBox = new BoundingBox();
|
||||
pathDriver
|
||||
.abs()
|
||||
.unarc()
|
||||
.unshort()
|
||||
.iterate((seg, index, x, y) => {
|
||||
switch(seg[0]) {
|
||||
case 'M':
|
||||
case 'L':
|
||||
boundingBox.addPoint(
|
||||
seg[1],
|
||||
seg[2]
|
||||
);
|
||||
break;
|
||||
case 'H':
|
||||
boundingBox.addX(seg[1]);
|
||||
break;
|
||||
case 'V':
|
||||
boundingBox.addY(seg[1]);
|
||||
break;
|
||||
case 'Q':
|
||||
boundingBox.addQuadraticCurve(
|
||||
x,
|
||||
y,
|
||||
seg[1],
|
||||
seg[2],
|
||||
seg[3],
|
||||
seg[4]
|
||||
);
|
||||
break;
|
||||
case 'C':
|
||||
boundingBox.addBezierCurve(
|
||||
x,
|
||||
y,
|
||||
seg[1],
|
||||
seg[2],
|
||||
seg[3],
|
||||
seg[4],
|
||||
seg[5],
|
||||
seg[6]
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
width: boundingBox.width(),
|
||||
height: boundingBox.height(),
|
||||
x1: boundingBox.x1,
|
||||
y1: boundingBox.y1,
|
||||
x2: boundingBox.x2,
|
||||
y2: boundingBox.y2
|
||||
};
|
||||
}
|
|
@ -10,11 +10,16 @@ let {
|
|||
|
||||
let fillPatterns = {};
|
||||
let fillReg = /^url\(#(\w+?)\)$/;
|
||||
|
||||
function isGradient(obj) {
|
||||
return obj instanceof LinearGradient || obj instanceof RadialGradient;
|
||||
}
|
||||
|
||||
export default function (props) {
|
||||
let {fill} = props;
|
||||
|
||||
if (fill) {
|
||||
if (fill instanceof LinearGradient || fill instanceof RadialGradient) {
|
||||
if (isGradient(fill)) {
|
||||
return fill;
|
||||
}
|
||||
|
||||
|
@ -22,12 +27,17 @@ export default function (props) {
|
|||
let matched = fill.match(fillReg);
|
||||
if (matched) {
|
||||
let patternName = `${matched[1]}:${props.svgId}`;
|
||||
if (fillPatterns[patternName]) {
|
||||
return fillPatterns[patternName];
|
||||
let pattern = fillPatterns[patternName];
|
||||
if (pattern) {
|
||||
if (typeof pattern === 'function') {
|
||||
let dimensions = this.getBoundingBox();
|
||||
return pattern(dimensions);
|
||||
} else {
|
||||
return null;
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return rgba(props.fill === undefined ? '#000' : props.fill, props.fillOpacity);
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import percentToFloat from './percentToFloat';
|
||||
let percentReg = /%/;
|
||||
|
||||
export default function (...args) {
|
||||
let hasPercent = percentReg.test(args.join(''));
|
||||
if (hasPercent) {
|
||||
return args.map(arg => {
|
||||
return function (base) {
|
||||
return percentReg.test(arg) ? percentToFloat(arg) * base : +arg;
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
let percentReg = /^(\-?\d+(?:\.\d+)?)(%?)$/;
|
||||
export default function (percent) {
|
||||
let matched = percent.match(percentReg);
|
||||
return matched[2] ? matched[1] / 100 : +matched[1];
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.5",
|
||||
"name": "react-native-art-svg",
|
||||
"description": "react native svg library based on `ART`",
|
||||
"repository": {
|
||||
|
@ -17,6 +17,7 @@
|
|||
"VML"
|
||||
],
|
||||
"dependencies": {
|
||||
"color": "^0.11.1"
|
||||
"color": "^0.11.1",
|
||||
"svgpath": "^2.1.5"
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче