前言
由于业务需求,需要开发一个可以批改作业的组件,网上搜的一些插件不太符合业务需求,没办法>_
{
const canvas = this.$refs.canvasRef
this.canvasCenter(canvas)
})
},
// 加载图片
loadImg(canvas, ctx) {
const img = new Image()
// 图片加载成功
img.onload = () => {
console.log('图片加载成功')
this.imgIsLoad = true
canvas.width = img.width
canvas.height = img.height
this.$set(this.canvasValue, 'width', img.width)
this.$set(this.canvasValue, 'height', img.height)
canvas.style.backgroundImage = `url(${this.imgUrl})`
this.canvasCenter(canvas)
// this.loadHistory(ctx)
}
// 图片加载失败
img.onerror = () => {
console.log('图片加载失败!')
}
img.src = this.imgUrl
},
// 加载历史画笔记录 img是保存的base64格式的画笔轨迹图
loadHistory(ctx, img) {
const imgCatch = new Image()
imgCatch.src = img
imgCatch.onload = () => {
ctx.drawImage(imgCatch, 0, 0, imgCatch.width, imgCatch.height)
}
},
// 切换工具
changeTool(name) {
console.log(name)
// 清除拖拽的按下事件
const wrapRef = this.$refs.canvasWrapRef
wrapRef.onmousedown = null
const canvas = this.$refs.canvasRef
const ctx = canvas.getContext('2d')
switch (name) {
case 'drag':
this.dragCanvas(canvas)
break
case 'draw':
this.drawPaint(canvas, ctx)
break
case 'eraser':
this.eraser(canvas, ctx)
break
case 'revoke':
this.revoke(canvas, ctx)
break
case 'clear':
this.clearCanvas(canvas, ctx)
break
case 'save':
this.saveCanvas(canvas)
break
case 'rotate':
this.$set(this.canvasValue, 'rotate', this.canvasValue.rotate + 90)
break
case 'enlarge':
this.$set(this.canvasValue, 'scale', this.canvasValue.scale + 0.2)
break
case 'narrow':
this.$set(this.canvasValue, 'scale', this.canvasValue.scale - 0.2)
break
default:
break
}
},
// 拖拽画布
dragCanvas(canvas) {
console.log('dragCanvas')
// 清除上次监听的事件
const wrapRef = this.$refs.canvasWrapRef
const container = this.getPosition(this.$refs.canvasContRef)
let isDown = false
wrapRef.onmousedown = (e) => {
isDown = true
this.$set(this.canvasValue, 'cursor', 'move')
// 算出鼠标相对元素的位置
const disX = e.clientX - wrapRef.offsetLeft
const disY = e.clientY - wrapRef.offsetTop
document.onmousemove = (e) => {
if (!isDown) return
// 用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
let left = e.clientX - disX
let top = e.clientY - disY
// 判断canvas是否在显示范围内,减4是border=2px的原因
const width = container.width - canvas.width / 2 - 4
const height = container.height - canvas.height / 2 - 4
left = Math.min(Math.max(-canvas.width / 2, left), width)
top = Math.min(Math.max(-canvas.height / 2, top), height)
this.$set(this.canvasValue, 'left', left)
this.$set(this.canvasValue, 'top', top)
}
document.onmouseup = (e) => {
isDown = false
document.onmousemove = null
this.$set(this.canvasValue, 'cursor', 'default')
}
}
},
// 画笔
drawPaint(canvas, ctx) {
// const wrapRef = this.$refs.canvasWrapRef
canvas.onmousedown = (e) => {
this.$set(this.canvasValue, 'cursor', 'crosshair')
this.imgData = ctx.getImageData(0, 0, canvas.width, canvas.height)
this.preDrawAry.push(this.imgData)
ctx.beginPath()
ctx.lineWidth = 2
ctx.strokeStyle = 'red'
ctx.moveTo(e.offsetX, e.offsetY)
canvas.onmousemove = (e) => {
ctx.lineTo(e.offsetX, e.offsetY)
ctx.stroke()
}
}
// 鼠标抬起取消鼠标移动的事件
document.onmouseup = (e) => {
canvas.onmousemove = null
ctx.closePath()
this.$set(this.canvasValue, 'cursor', 'default')
}
// 鼠标移出画布时 移动事件取消
// document.onmouseout = (e) => {
// document.onmousemove = null
// ctx.closePath()
// }
},
// 橡皮擦
eraser(canvas, ctx, r = 10) {
// const wrapRef = this.$refs.canvasWrapRef
canvas.onmousedown = (e) => {
this.imgData = ctx.getImageData(0, 0, canvas.width, canvas.height)
this.preDrawAry.push(this.imgData)
let x1 = e.offsetX
let y1 = e.offsetY
// 鼠标第一次点下的时候擦除一个圆形区域,同时记录第一个坐标点
ctx.save()
ctx.beginPath()
ctx.arc(x1, y1, r, 0, 2 * Math.PI)
ctx.clip()
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.restore()
canvas.onmousemove = (e) => {
const x2 = e.offsetX
const y2 = e.offsetY
// 获取两个点之间的剪辑区域四个端点
const asin = r * Math.sin(Math.atan((y2 - y1) / (x2 - x1)))
const acos = r * Math.cos(Math.atan((y2 - y1) / (x2 - x1)))
// 保证线条的连贯,所以在矩形一端画圆
ctx.save()
ctx.beginPath()
ctx.arc(x2, y2, r, 0, 2 * Math.PI)
ctx.clip()
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.restore()
// 清除矩形剪辑区域里的像素
ctx.save()
ctx.beginPath()
ctx.moveTo(x1 + asin, y1 - acos)
ctx.lineTo(x2 + asin, y2 - acos)
ctx.lineTo(x2 - asin, y2 + acos)
ctx.lineTo(x1 - asin, y1 + acos)
ctx.closePath()
ctx.clip()
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.restore()
// 记录最后坐标
x1 = x2
y1 = y2
}
}
// 鼠标抬起取消鼠标移动的事件
document.onmouseup = (e) => {
canvas.onmousemove = null
}
// 鼠标移出画布时 移动事件取消
// canvas.onmouseout = (e) => {
// canvas.onmousemove = null
// }
},
// 撤销
revoke(canvas, ctx) {
if (this.preDrawAry.length > 0) {
const popData = this.preDrawAry.pop()
ctx.putImageData(popData, 0, 0)
} else {
this.clearCanvas(canvas, ctx)
}
},
// 清空画布
clearCanvas(canvas, ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
},
// 保存
saveCanvas(canvas) {
const wrapRef = this.$refs.stepWrapRef.$el
const { width, height } = this.canvasValue
const image = canvas.toDataURL('image/png')
console.log(this.preDrawAry)
domtoimage.toPng(wrapRef)
.then((dataUrl) => {
console.log(dataUrl)
const mixConfig = {
'base': {
'backgroundImg': image,
'width': width,
'height': height,
'quality': 0.8,
'fileType': 'png'
},
'dynamic': [{
'type': 1,
'size': {
'dWidth': width,
'dHeight': height
},
'position': {
'x': 0,
'y': 0
},
'imgUrl': dataUrl
}]
}
mixImg(mixConfig).then(res => {
console.log(res.data.base64)
})
})
.catch((error) => {
console.error('oops, something went wrong!', error)
})
},
// 获取dom元素在页面中的位置与大小
getPosition(target) {
const width = target.offsetWidth
const height = target.offsetHeight
let left = 0
let top = 0
do {
left += target.offsetLeft || 0
top += target.offsetTop || 0
target = target.offsetParent
} while (target)
return { width, height, left, top }
},
// canvas居中显示
canvasCenter(canvas) {
const wrap = this.getPosition(this.$refs.canvasContRef)
const left = (wrap.width - canvas.width) / 2
const top = (wrap.height - canvas.height) / 2
this.$set(this.canvasValue, 'left', left)
this.$set(this.canvasValue, 'top', top)
},
drop(e) {
// e.preventDefault()
const { type, value } = JSON.parse(e.dataTransfer.getData('data'))
console.log(e.offsetX, e.offsetY)
this.dragList.push({
x: e.offsetX,
y: e.offsetY,
type,
value
})
},
dragover(e) {
// 取消默认动作是为了drop事件可以触发
e.preventDefault()
// console.log(e)
}
}
}
.canvas-container{
position: relative;
width: 100%;
height: 400px;
border: 2px solid #f0f;
background-color: lightblue;
box-sizing: border-box;
overflow: hidden;
}
.canvas-container .canvas-wrap{
position: absolute;
transition: transform .3s;
/* background-color: #ff0; */
}
.canvas-toolbar{
width: 720px;
height: 40px;
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, .3);
user-select: none;
}
canvasToolbar.vue
{{ tool.name }}
export default {
props: {
},
data() {
return {
tools: [{
code: 'drag',
name: '拖动'
}, {
code: 'draw',
name: '画笔'
}, {
code: 'eraser',
name: '橡皮'
}, {
code: 'revoke',
name: '撤销'
}, {
code: 'clear',
name: '重置'
}, {
code: 'save',
name: '保存'
}, {
code: 'rotate',
name: '旋转'
}, {
code: 'enlarge',
name: '放大'
}, {
code: 'narrow',
name: '缩小'
}]
}
},
methods: {
changeTool(name, value) {
this.$emit('changeTool', name)
},
changeScale() {
}
}
}
dragStep.vue
{{ step.value }}
export default {
props: {
// 是否隐藏在下方(用于domtoimg截图)
isHide: {
type: Boolean,
default: false
},
// 拖拽的元素列表
dragList: {
type: Array,
default: () => []
},
// 应该与 isHide=true 时使用
dragStyle: {
type: Object,
default: () => ({})
}
},
methods: {
clickStepItem(value) {
console.log(value)
}
}
}
.drag-step.hide{
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
.drag-item{
position: absolute;
user-select: none;
}
.drag-item.step{
width: 60px;
height: 30px;
text-align: center;
line-height: 30px;
color: #fff;
border-radius: 4px;
background-color: aquamarine;
}
.drag-item.chapter{
}
|