基于vue和canvas开发的作业批改,包含画笔、橡皮擦、拖拽到指定位置、保存批改痕迹等功能案例

您所在的位置:网站首页 微信批改作业怎么标注 基于vue和canvas开发的作业批改,包含画笔、橡皮擦、拖拽到指定位置、保存批改痕迹等功能案例

基于vue和canvas开发的作业批改,包含画笔、橡皮擦、拖拽到指定位置、保存批改痕迹等功能案例

2024-07-02 14:22| 来源: 网络整理| 查看: 265

前言

由于业务需求,需要开发一个可以批改作业的组件,网上搜的一些插件不太符合业务需求,没办法>_ { 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{ }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3