前言
我目前在维护一款功能强大的开源创意画板。这个画板集成了多种创意画笔,可以让用户体验到全新的绘画效果。无论是在移动端还是PC端,都能享受到较好的交互体验和效果展示。并且此项目拥有许多强大的辅助绘画功能,包括但不限于前进后退、复制删除、上传下载、多画板和多图层等等。详细功能我就不一一罗列了,期待你的探索。
Link: https://songlh.top/paint-board/
Github: https://github.com/LHRUN/paint-board 欢迎Star⭐️
在项目的逐渐迭代中,我计划撰写一些文章,一方面是为了记录技术细节,这是我一直以来的习惯。另一方面则是为了推广一下,期望得到你的使用和反馈,当然如果能点个 Star 就是对我最大的支持。
我准备分3篇文章讲解创意画笔的实现, 本篇文章是第三篇, 所有的实现源码我都会上传到我的 Github 上.
多点连接
- 多点连接效果如下
- 多点连接是通过对鼠标移动中坐标进行随机生成附近点位, 然后对随机点位进行圆形绘制, 并且每次都会记录上一次移动的点位, 并对两个点位进行线段连接, 就会有以上效果.
interface Point {
x: number
y: number
}
let isMouseDown = false
let lastPoints: Point[] = [] // 上一个随机圆形绘制点位数组
/**
* 矩形内生成随机点位
*/
const generateRandomCoordinates = (
centerX: number, // 矩形中心点 X
centerY: number, // 矩形中心点 Y
size: number, // 矩形大小
count: number // 生成数量
) => {
const halfSize = size / 2
const points = []
for (let i = 0; i < count; i++) {
const randomX = Math.floor(centerX - halfSize + Math.random() * size)
const randomY = Math.floor(centerY - halfSize + Math.random() * size)
points.push({ x: randomX, y: randomY })
}
return points
}
function PaintBoard() {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const [context2D, setContext2D] = useState<CanvasRenderingContext2D | null>(null)
useEffect(() => {
if (canvasRef?.current) {
const context2D = canvasRef?.current.getContext('2d')
if (context2D) {
context2D.fillStyle = '#000000';
context2D.strokeStyle = '#000000';
context2D.lineWidth = 1;
setContext2D(context2D)
}
}
}, [canvasRef])
const onMouseDown = () => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = true
}
const onMouseMove = (event: MouseEvent) => {
if (!canvasRef?.current || !context2D) {
return
}
if (isMouseDown) {
const { clientX, clientY } = event
// 生成随机点位
const points = generateRandomCoordinates(clientX, clientY, 50, 3)
draw(points)
lastPoints = points
}
}
const draw = (points: Point[]) => {
if (!context2D) {
return
}
// 如果有上一个随机点位, 就进行直线连接
if (lastPoints.length) {
lastPoints.forEach(({ x, y }, index) => {
context2D.beginPath();
context2D.save();
context2D.moveTo(x, y);
context2D.lineTo(points[index].x, points[index].y);
context2D.stroke();
context2D.restore();
})
}
// 对当前的随机点位进行圆形绘制
points.map((curPoint) => {
context2D.beginPath();
context2D.save();
context2D.arc(curPoint.x, curPoint.y, 7, 0, 2 * Math.PI, false);
context2D.fill();
context2D.restore();
})
}
const onMouseUp = () => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = false
lastPoints = []
}
return (
<div>
<canvas
ref={canvasRef}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
/>
</div>
)
}
export default PaintBoard
波浪曲线
- 波浪曲线效果如下
- 波浪曲线是在鼠标移动中, 根据鼠标移动的两点距离和角度进行半圆绘制, 然后半圆是每一次都会进行翻转, 所以就会有一种波浪的效果, 并且移动快就半圆变大, 移动慢就半圆变小
interface Point {
x: number
y: number
}
let isMouseDown = false
let movePoint: Point = { x: 0, y: 0 } // 移动坐标记录
let flip = 1 // 翻转记录
/**
* 根据勾股定理得出距离
*/
const getDistance = (start: Point, end: Point) => {
return Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2))
}
function PaintBoard() {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const [context2D, setContext2D] = useState<CanvasRenderingContext2D | null>(null)
useEffect(() => {
if (canvasRef?.current) {
const context2D = canvasRef?.current.getContext('2d')
if (context2D) {
context2D.strokeStyle = '#000'
context2D.lineJoin = 'round'
context2D.lineCap = 'round'
setContext2D(context2D)
}
}
}, [canvasRef])
const onMouseDown = (event: MouseEvent) => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = true
const { clientX, clientY } = event
movePoint = {
x: clientX,
y: clientY
}
}
const onMouseMove = (event: MouseEvent) => {
if (!canvasRef?.current || !context2D) {
return
}
if (isMouseDown) {
const { clientX, clientY } = event
// 获取两点距离
const distance = getDistance(movePoint, { x: clientX, y: clientY })
// 得出两点的中间点, 这个也是半圆的圆心
const midX = (movePoint.x + clientX) / 2
const midY = (movePoint.y + clientY) / 2
context2D.beginPath();
context2D.save();
// 计算两点的角度
const angle = Math.atan2(clientY - movePoint.y, clientX - movePoint.x)
// 计算是上翻转还是下翻转
const flipAngle = (flip % 2) * Math.PI
// 绘制半圆
context2D.arc(
midX,
midY,
distance / 2,
angle + flipAngle,
angle + flipAngle + Math.PI
);
context2D.stroke();
context2D.restore();
// 更新数据
flip++;
movePoint = {
x: clientX,
y: clientY
}
}
}
const onMouseUp = () => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = false
movePoint = { x: 0, y: 0 }
}
return (
<div>
<canvas
ref={canvasRef}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
/>
</div>
)
}
荆棘画笔
- 荆棘画笔效果如下
- 荆棘画笔是在鼠标移动中, 通过移动的两点距离和角度进行椭圆的绘制, 因为椭圆的高度比较小, 再加上宽度是通过移动距离计算, 所以就会有一种尖锐的效果
interface Point {
x: number
y: number
}
let isMouseDown = false
let movePoint: Point = { x: 0, y: 0 } // 鼠标点位记录
const minSize = 3 // 椭圆最小宽高
/**
* 根据勾股定理得出距离
*/
const getDistance = (start: Point, end: Point) => {
return Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2))
}
function PaintBoard() {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const [context2D, setContext2D] = useState<CanvasRenderingContext2D | null>(null)
useEffect(() => {
if (canvasRef?.current) {
const context2D = canvasRef?.current.getContext('2d')
if (context2D) {
context2D.fillStyle = '#000'
context2D.lineJoin = 'round'
context2D.lineCap = 'round'
setContext2D(context2D)
}
}
}, [canvasRef])
const onMouseDown = (event: MouseEvent) => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = true
const { clientX, clientY } = event
movePoint = {
x: clientX,
y: clientY
}
}
const onMouseMove = (event: MouseEvent) => {
if (!canvasRef?.current || !context2D) {
return
}
if (isMouseDown) {
const { clientX, clientY } = event
// 根据两点得出距离
const distance = getDistance(movePoint, point)
// 得出两点的中间点, 这个也是椭圆的圆心
const midX = (movePoint.x + clientX) / 2
const midY = (movePoint.y + clientY) / 2
context2D.beginPath();
context2D.save();
// 得出移动的角度
const angle = Math.atan2(clientY - movePoint.y, clientX - movePoint.x)
/**
* 绘制椭圆
* 高度固定 minSize
* 宽度为 距离 * 5 + minSize
*/
context2D.ellipse(
midX,
midY,
distance * 5 + minSize,
minSize,
angle,
0,
2 * Math.PI
);
context2D.fill();
context2D.restore();
movePoint = {
x: clientX,
y: clientY
}
}
}
const onMouseUp = () => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = false
movePoint = { x: 0, y: 0 }
}
return (
<div>
<canvas
ref={canvasRef}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
/>
</div>
)
}
总结
感谢你的阅读。以上就是本文的全部内容,希望这篇文章对你有所帮助,欢迎点赞和收藏。如果有任何问题,欢迎在评论区进行讨论