记录常见svg绘图姿势,包括可拖动的锚点、画圆角、画角度圆弧、画可以显示角度的三角形、自由多边形等等。
类型
1. 锚点
可以用鼠标拖着走
- 用法
import React, { useState } from 'react';
import AnchorPoint from './anchor-point';
export interface Point {
x: number;
y: number;
}
export default () => {
const [point, setPoint] = useState({ x: 100, y: 100 } as Point);
return (
<AnchorPoint x={point.x} y={point.y} onChange={setPoint} />
);
};
- 源码
import React, { useState, useEffect } from 'react';
export interface Point {
x: number;
y: number;
}
interface IProps {
x?: number;
y?: number;
radius?: number;
stroke?: string;
strokeWidth?: number;
fill?: string;
onChange: (point: Point) => void;
}
const initPoint: Point = { x: -1, y: -1 };
export default (props: IProps) => {
const {
x = 0,
y = 0,
radius = 5,
onChange = () => 1,
stroke = '#999',
strokeWidth = '1',
fill = 'orange',
...rest
} = props;
const [startPoint, setStartPoint] = useState(initPoint);
const [startAnchorPoint, setStartAnchorPoint] = useState(initPoint);
const [moving, setMoving] = useState(false);
// 鼠标拖动锚点
const onMouseMove = (e: MouseEvent) => {
const { pageX, pageY } = e;
// 计算在鼠标相对锚点的坐标
const newPoint = {
x: pageX - startPoint.x + startAnchorPoint.x,
y: pageY - startPoint.y + startAnchorPoint.y,
};
onChange(newPoint);
};
// 鼠标抬起锚点
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove, true);
document.removeEventListener('mouseup', onMouseUp, true);
setStartPoint(initPoint);
setMoving(false);
};
// 鼠标按下锚点
const onMouseDown = (e: React.MouseEvent) => {
const { pageX, pageY } = e;
setStartPoint({ x: pageX, y: pageY });
setStartAnchorPoint({ x, y });
setMoving(true);
};
useEffect(() => {
if (moving) {
document.addEventListener('mousemove', onMouseMove, true);
document.addEventListener('mouseup', onMouseUp, true);
}
}, [moving]);
return (
<circle
style={{ cursor: 'pointer', zIndex: 99 }}
{...rest}
stroke={stroke}
strokeWidth={strokeWidth}
fill={fill}
cx={x}
cy={y}
r={radius}
onMouseDown={onMouseDown}></circle>
);
};
2. 贝塞尔曲线
export default () => {
const A = { x: 100, y: 200 };
const B = { x: 200, y: 100 };
const C = { x: 300, y: 200 };
return (
<path
d={`M${A.x},${A.y}
Q${B.x} ${B.y} ${C.x} ${C.y}`}
stroke="#000"
strokeWidth="1"
fill="none"
/>
);
};
3. 圆角
// 计算两点之间的距离
export const distance = (p1: Point, p2: Point) => {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
};
// 计算角点
const getPoints = (
start: Point,
center: Point,
end: Point,
radius = 15,
) => {
const A = start;
const B = center;
const C = end;
const AB = distance(A, B);
const CB = distance(B, C);
return {
startPoint: {
x: B.x + (radius / AB) * (A.x - B.x),
y: B.y + (radius / AB) * (A.y - B.y),
},
endPoint: {
x: B.x + (radius / CB) * (C.x - B.x),
y: B.y + (radius / CB) * (C.y - B.y),
},
};
};
const renderBorderRadiusPath = (A: Point, B: Point, C: Point) => {
const { startPoint, endPoint } = getPoints(A, B, C);
const borderRadius = 15;
return (
<path
d={`M${startPoint.x},${startPoint.y}
Q${B.x} ${B.y} ${endPoint.x} ${endPoint.y}`}
stroke="#000"
strokeWidth="1"
fill="none"
/>
);
};
4. 角度圆弧
// 计算两点之间的距离
export const distance = (p1: Point, p2: Point) => {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
};
// 计算角点
const getPoints = (
start: Point,
center: Point,
end: Point,
radius = 15,
) => {
const A = start;
const B = center;
const C = end;
const AB = distance(A, B);
const CB = distance(B, C);
return {
startPoint: {
x: B.x + (radius / AB) * (A.x - B.x),
y: B.y + (radius / AB) * (A.y - B.y),
},
endPoint: {
x: B.x + (radius / CB) * (C.x - B.x),
y: B.y + (radius / CB) * (C.y - B.y),
},
};
};
const renderBorderRadiusPath = (A: Point, B: Point, C: Point) => {
const { startPoint, endPoint } = getPoints(A, B, C);
const borderRadius = 15;
return (
<path
d={`M${startPoint.x},${startPoint.y}
A${borderRadius},${borderRadius} 0,0,0
${endPoint.x},${endPoint.y}`}
stroke="#000"
strokeWidth="1"
fill="none"
/>
);
};
应用
1. 三角形
// 计算两点之间的距离
export const distance = (p1: Point, p2: Point) => {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
};
// 计算角点
const getPoints = (
start: Point,
center: Point,
end: Point,
radius = 15,
) => {
const A = start;
const B = center;
const C = end;
const AB = distance(A, B);
const CB = distance(B, C);
return {
startPoint: {
x: B.x + (radius / AB) * (A.x - B.x),
y: B.y + (radius / AB) * (A.y - B.y),
},
endPoint: {
x: B.x + (radius / CB) * (C.x - B.x),
y: B.y + (radius / CB) * (C.y - B.y),
},
};
};
const renderBorderRadiusPath = (A: Point, B: Point, C: Point) => {
const { startPoint, endPoint } = getPoints(A, B, C);
const borderRadius = 15;
return (
<path
d={`M${startPoint.x},${startPoint.y}
A${borderRadius},${borderRadius} 0,0,0
${endPoint.x},${endPoint.y}`}
stroke="#000"
strokeWidth="1"
fill="none"
/>
);
};
export default () => {
const A = { x: 200, y: 100 };
const B = { x: 100, y: 200 };
const C = { x: 300, y: 240 };
return (
<>
<path
d={`M${A.x},${A.y}
L${B.x},${B.y}
L${C.x},${C.y}
Z`}
stroke="#000"
strokeWidth="1"
fill="none"
/>
{renderBorderRadiusPath(A, B, C)}
</>
);
};
2.自由多边形
import React from 'react';
// import Polygon from "./polygon";
import AnchorPoint from '../anchor-point';
import { Point } from './interface';
import './index.css';
export * from './interface';
export type Coordinate = Point;
interface IProps {
radius?: number;
borderRadius?: number;
points?: Point[];
onPointsChange: (points: Point[]) => void;
}
export default (props: IProps) => {
const { points = [], radius = 5, onPointsChange, ...rest } = props;
/** ******************** handler **********************************/
/** ******************** render **********************************/
return (
<g>
<polygon
stroke="#000"
fill="none"
{...rest}
points={points.map((p: Point) => `${p.x},${p.y}`).join(' ')}
/>
{points.map((p: Point, index: number) => {
return (
<g key={`${p.x},${p.y}-g`}>
{/* 锚点 */}
<AnchorPoint
x={p.x}
y={p.y}
radius={radius}
onChange={(newPoint: Point) => {
const newPoints = [...points];
newPoints[index] = newPoint;
onPointsChange(newPoints);
}}
/>
</g>
);
})}
</g>
);
};
- 待补充