原生 Canvas 实现画板功能,包含曲线、直线、矩形、原型、箭头、截图、橡皮擦等功能
原生 Canvas 实现画板功能,包含曲线、直线、矩形、原型、箭头、截图、橡皮擦等功能
.gif)
.gif)
import React, { Component, Fragment } from 'react'
import { Form, Button, Input, InputNumber } from 'antd'
import DrawerArrow from './DrawerArrow'
import styles from './index.less'
const LIST = [
{
key: 'pen',
name: '铅笔',
},
{
key: 'line',
name: '直线',
},
{
key: 'rect',
name: '矩形',
},
{
key: 'arc',
name: '圆形',
},
{
key: 'arrow',
name: '箭头',
},
{
key: 'robber',
name: '橡皮檫',
},
{
key: 'screenshot',
name: '截图',
},
]
class Main extends Component {
constructor(props) {
super(props)
this.state = {
bgUrlList: [], // 背景图 历史记录列表
isDraw: false,
brushType: null, // 默认为铅笔
originX: undefined,
originY: undefined,
hisImgList: [], // 用于存储历史记录的数组
step: -1, // 记录的步数
areaSize: [600, 600], // 画板的尺寸
screenshotArea: {
x0: 0,
y0: 0,
x1: 600,
y1: 600,
}, // 截图区域的大小
showSreenshoot: false, // 是否正在截图
}
this.cavasDom = null
this.ctx = null
this.originBgUrl =
'https://data.znds.com/attachment/forum/201606/09/175354uvk8ck3wmxk5zv3k.jpg' // 背景图
this.snapImg = new Image() // 用于动态实时记录上次绘制的 canvas 快照(不包含背景图片)
}
componentDidMount() {
this.initCanvas()
}
initCanvas = () => {
if (document.getElementById('cavasDom')) {
this.cavasDom = document.getElementById('cavasDom')
this.ctx = this.cavasDom.getContext('2d')
this.bindHistory()
}
}
// 选择画笔类型
selectType = brushType => {
this.setState({
brushType,
})
}
onMouseDown = event => {
event.stopPropagation()
const {
form: { getFieldValue },
} = this.props
const { brushType, areaSize } = this.state
if (!brushType) {
return
}
const color = getFieldValue('color')
const lineWidth = getFieldValue('lineWidth')
const originX =
event.pageX - document.getElementById('canvasWrapper').offsetLeft // 原点x坐标
const originY =
event.pageY - document.getElementById('canvasWrapper').offsetTop // 原点y坐标
this.setState({
originX,
originY,
isDraw: true,
})
if (brushType !== 'robber') {
this.snapImg.src = this.cavasDom.toDataURL('image/png')
}
this.ctx.beginPath()
if (brushType === 'screenshot') {
this.ctx.strokeStyle = '#000'
this.ctx.lineWidth = 1
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'
this.drawRect(0, 0, areaSize[0], areaSize[1], true)
this.setState({
screenshotArea: {
x0: 0,
y0: 0,
x1: areaSize[0],
y1: areaSize[1],
},
})
} else {
this.ctx.strokeStyle = color
this.ctx.lineWidth = lineWidth
}
this.ctx.moveTo(originX, originY)
}
onMouseMove = event => {
event.stopPropagation()
const { originX, originY, isDraw, brushType, areaSize } = this.state
if (isDraw && brushType) {
let x = event.pageX - document.getElementById('canvasWrapper').offsetLeft
let y = event.pageY - document.getElementById('canvasWrapper').offsetTop
if (brushType === 'pen') {
this.ctx.lineTo(x, y)
this.ctx.stroke()
} else if (brushType === 'robber') {
this.ctx.strokeStyle = '#fff'
this.ctx.clearRect(x - 10, y - 10, 20, 20)
} else if (brushType === 'line') {
this.drawLine(originX, originY, x, y)
} else if (brushType === 'rect') {
this.drawRect(originX, originY, x, y)
} else if (brushType === 'arc') {
this.drawCircle(originX, originY, x, y)
} else if (brushType === 'arrow') {
this.ctx.clearRect(0, 0, areaSize[0], areaSize[1])
this.ctx.drawImage(this.snapImg, 0, 0, areaSize[0], areaSize[1])
DrawerArrow(
this.ctx,
originX,
originY,
x,
y,
this.props.form.getFieldValue('color'),
)
} else if (brushType === 'screenshot') {
this.drawRect(originX, originY, x, y, true)
this.setState({
screenshotArea: {
x0: originX,
y0: originY,
x1: x,
y1: y,
},
})
}
}
}
onMouseLeave = event => {
event.stopPropagation()
const { isDraw, brushType } = this.state
if (!brushType) {
return
}
if (isDraw) {
this.setState({
isDraw: false,
showSreenshoot: brushType === 'screenshot',
})
this.ctx.closePath()
}
}
onMouseUp = event => {
const { brushType } = this.state
event.stopPropagation()
if (!brushType) {
return
}
this.setState({
isDraw: false,
showSreenshoot: brushType === 'screenshot',
})
if (brushType !== 'screenshot') {
this.bindHistory() // 当绘画结束时,调用history,保存这一步的历史记录
}
}
// 取消截图
cancelShoot = () => {
const { hisImgList, bgUrlList, areaSize } = this.state
this.ctx.clearRect(0, 0, areaSize[0], areaSize[1]) // 清空画布
let tempImg = new Image()
tempImg.src = hisImgList[hisImgList.length - 1]
this.originBgUrl = bgUrlList[bgUrlList.length - 1]
// 从数组中调取历史记录的最后一条,进行重绘
tempImg.onload = () => {
this.ctx.drawImage(tempImg, 0, 0, areaSize[0], areaSize[1])
}
this.setState({
showSreenshoot: false,
brushType: null,
})
}
// 截图
toShoot = () => {
const { screenshotArea: SA, bgUrlList, hisImgList, areaSize } = this.state
let newOriginX = SA.x0
let newOriginY = SA.y0
if (SA.x1 < SA.x0) {
newOriginX = SA.x1
}
if (SA.y1 < SA.y0) {
newOriginY = SA.y1
}
this.snapImg.src = hisImgList[hisImgList.length - 1]
let bgImg = new Image()
bgImg.src = this.originBgUrl
bgImg.onload = () => {
this.ctx.drawImage(bgImg, 0, 0, areaSize[0], areaSize[1])
this.ctx.drawImage(this.snapImg, 0, 0, areaSize[0], areaSize[1])
let img2 = new Image()
img2.src = this.cavasDom.toDataURL('image/png')
img2.onload = () => {
this.ctx.drawImage(
img2,
newOriginX,
newOriginY,
Math.abs(SA.x0 - SA.x1),
Math.abs(SA.y0 - SA.y1),
0,
0,
areaSize[0],
areaSize[1],
)
this.setState(
{
bgUrlList: [...bgUrlList, this.cavasDom.toDataURL('image/png')],
hisImgList: [...hisImgList, this.cavasDom.toDataURL('image/png')],
},
() => {
this.cancelShoot()
},
)
}
}
}
// 画直线
drawLine = (originX, originY, x, y) => {