简单讲解下用canvas做一个画板工具
起因
需要在图片上做文字,圆形,正方形的标记,查了一遍网上的一些第三方库,发现都不太合适,有些UI限制的太死了,有些提供的方法不足以支撑好看的UI,只能自己手写一个了
canvas做画板有哪些优势
- 可以实现较为复杂的绘制效果。通过 JavaScript 控制 canvas 的绘制操作,可以实现各种复杂、生动的绘制效果,如渐变、阴影、纹理等,大大增强了画板的表现力。
- 支持丰富的事件处理机制。与其他技术相比,canvas API 提供了丰富的事件处理机制,可以响应用户的鼠标或触摸事件,实现更加灵活的交互效果。
- 能够快速渲染图像。canvas 的绘制操作基本上都是 GPU 加速的,在渲染大量图像时能够保持流畅性能,同时消耗较少的系统资源。
- 方便与其他前端技术集成。由于 canvas API 是基于 HTML5 标准的,因此可以方便地与其他前端技术集成,例如 Vue、React、Angular 等框架,不会对现有代码产生影响。
先看效果
主要功能有在图片上画图,写字,圆形,矩形,键盘打字,撤回,解决网页图片跨域保存问题。
使用canvas做,未引用其他组件
在canvas上写字是采用div的方式,使用contenteditable=”true”属性,让div处于可编辑状态,然后打字,最后让文字在canvas上渲染
撤回功能是每做一步操作,保存一张图片,如果有撤回就将最新的图片删除,将之前保存的图片重新渲染
文字
const textBox = document.getElementById('textBox')
const that = this
this.canvas.onmousedown = function mouseDownAction(e) {
if (textBox.innerText) {
that.addHistory()
that.ctx.font = "28px orbitron";
that.ctx.fillStyle = that.color; // 填充颜色为红色
that.ctx.strokeStyle = that.color; // 画笔的颜色
that.ctx.lineWidth = 5; // 指定描边线的宽度
that.ctx.fillText(textBox.innerText, that.positions.x, that.positions.y)
textBox.style.display = "none"
textBox.innerText = ''
that.text = ''
return
}
that.positions.x = e.offsetX
that.positions.y = e.offsetY
textBox.style.left = (e.offsetX) + that.left + "px";
textBox.style.top = e.offsetY + that.top + "px";
textBox.style.display = "block"
setTimeout(()=>{
textBox.focus()
})
};
随机画图代码
const that = this
this.ctx.fillStyle="#FF0000";
this.ctx.lineWidth=5;
this.ctx.strokeStyle= this.color; //将画笔设置为红色
let x= undefined
let y = undefined
this.canvas.onmousedown = function(event) {
x = event.offsetX
y = event.offsetY
that.addHistory()
document.onmousemove = function(event) {
var x1 = event.offsetX
var y1 = event.offsetY
// this.huabi(x, y, x1, y1, ctx);
that.ctx.beginPath();
that.ctx.globalAlpha = 1;
that.ctx.lineWidth = 5;
that.ctx.strokeStyle = that.color;
that.ctx.moveTo(x, y);
that.ctx.lineTo(x1, y1);
that.ctx.closePath();
that.ctx.stroke();
x = x1;
y = y1;
};
};
document.onmouseup = function() {
this.onmousemove = null;
};
矩形画图代码
this.disfill()
let postx = {
startX: 0,
endX: 0,
startY: 0,
endY:0
}
const that = this
this.canvas.onmousedown = function mouseDownAction(e) {
postx.startX = e.offsetX
postx.startY = e.offsetY
that.addHistory()
};
this.canvas.onmouseup = function mouseUp(e) {
postx.endX = e.offsetX
postx.endY = e.offsetY
that.ctx.fillStyle = "red"; // 填充颜色为红色
that.ctx.strokeStyle = that.color; // 画笔的颜色
that.ctx.lineWidth = 5; // 指定描边线的宽度
that.ctx.beginPath()
that.ctx.strokeRect(postx.startX,postx.startY, postx.endX-postx.startX, postx.endY-postx.startY);
that.ctx.closePath();
postx = {
startX: 0,
endX: 0,
startY: 0,
endY:0
}
}
圆形画图
this.disfill()
let postx = {
startX: 0,
endX: 0,
startY: 0,
endY:0
}
const that = this
this.canvas.onmousedown = function mouseDownAction(e) {
postx.startX = e.offsetX
postx.startY = e.offsetY
that.addHistory()
};
this.canvas.onmouseup = function mouseUp(e) {
postx.endX = e.offsetX
postx.endY = e.offsetY
that.ctx.fillStyle = "red"; // 填充颜色为红色
that.ctx.strokeStyle = that.color; // 画笔的颜色
that.ctx.lineWidth = 5; // 指定描边线的宽度
that.ctx.beginPath();
const length = Math.sqrt( Math.pow((postx.startX-postx.endX), 2)+Math.pow((postx.startY-postx.endY), 2))
that.ctx.arc(postx.startX, postx.startY,length, 0, 2 * Math.PI)
that.ctx.stroke()
that.ctx.closePath();
postx = {
startX: 0,
endX: 0,
startY: 0,
endY:0
}
}
具体代码如下
<template>
<div class="main" id="main">
<div>
<canvas id="canvas" class="canvas-style"></canvas>
<div style="display: flex;justify-content:space-around;width: 60%;margin-left: 20%;color: white">
<div class="pater" @click = 'isActive = "SJ";fillS()'>
<img class="thumbnail_36" referrerpolicy="no-referrer"
:src=" isActive === 'SJ' ? require('../../../assets/icons/png/icon-sj-xz.png') : require('../../../assets/icons/png/icon-sj-wx.png')"/>
<div :style="{color : isActive === 'SJ' ? '#409EFF':'white'}">
随机选区
</div>
</div>
<div class="pater" @click = 'isActive = "JX";fillJ()'>
<img class="thumbnail_36" referrerpolicy="no-referrer"
:src=" isActive === 'JX' ? require('../../../assets/icons/png/icon-jx-xz.png') : require('../../../assets/icons/png/icon-jx-wx.png')"/>
<div :style="{color : isActive === 'JX' ? '#409EFF':'white'}">矩形选区</div>
</div>
<div class="pater" @click = 'isActive = "YX";fillY()'>
<img class="thumbnail_36" referrerpolicy="no-referrer"
:src=" isActive === 'YX' ? require('../../../assets/icons/png/icon-yx-xz.png') : require('../../../assets/icons/png/icon-yx-wx.png')"/>
<div :style="{color : isActive === 'YX' ? '#409EFF':'white'}">
圆形选区
</div>
</div>
<div class="pater" @click = 'isActive = "WZ";fillM()'>
<img class="thumbnail_36" referrerpolicy="no-referrer"
:src=" isActive === 'WZ' ? require('../../../assets/icons/png/icon-wz-xz.png') : require('../../../assets/icons/png/icon-wz-wx.png')"/>
<div :style="{color : isActive === 'WZ' ? '#409EFF':'white'}">文字</div>
</div>
<div class="pater dropdown" style="text-align: center" @click = 'isActive = "";disfill()'>
<div class="chat-bubble dropdown-content">
<div style="text-align: center;display: flex;justify-content: space-around;margin-top: 10px">
<img referrerpolicy="no-referrer" @click="color='#F54341';colorIcon = require('../../../assets/icons/png/color-red2.png')"
src="../../../assets/icons/png/color-red2.png"/>
<img class="thumbnail_36" referrerpolicy="no-referrer" @click="color='#FAAD14';colorIcon = require('../../../assets/icons/png/color-yellow.png')"
src="../../../assets/icons/png/color-yellow.png"/>
<img class="thumbnail_36" referrerpolicy="no-referrer" @click="color='#409EFF';colorIcon = require('../../../assets/icons/png/color-blue.png')"
src="../../../assets/icons/png/color-blue.png"/>
<img class="thumbnail_36" referrerpolicy="no-referrer" @click="color='#29D15B';colorIcon = require('../../../assets/icons/png/color-green.png')"
src="../../../assets/icons/png/color-green.png"/>
</div>
</div>
<img class="thumbnail_36 dropdown" referrerpolicy="no-referrer"
:src="colorIcon"
/>
<div class="dropdown" :style="{color : color}">
颜色
</div>
</div>
<div class="pater" @click = 'isActive = "CH";disFill()'>
<img class="thumbnail_36" referrerpolicy="no-referrer"
:src=" isActive === 'CH' ? require('../../../assets/icons/png/icon-ch-xz.png') : require('../../../assets/icons/png/icon-ch-wx.png')"/>
<div :style="{color : isActive === 'CH' ? '#409EFF':'white'}">
撤回
</div>
</div>
</div>
</div>
<div class="text-style dzm-input" id="textBox" mce-contenteditable="true" placeholder="请输入内容"></div>
</div>
</template>
<script>
export default {
name:"canvas",
data() {
return {
colorIcon: require('../../../assets/icons/png/color-red2.png'),
isActive: '',
color: 'red',
ctx: undefined,
canvas: undefined,
positions: {
x: undefined,
y: undefined
},
text: '',
clos: 20,
rows: 1,
canvasHistory: [],
step: 0
}
},
props: {
imgUrl:{
type:String,
// 限定必传
required: true,
// 指定默认值
},
width:{
type:Number,
// 限定必传
required: true,
// 指定默认值
default:1600
},
height:{
type:Number,
// 限定必传
required: true,
// 指定默认值
default:900
},
top:{
type: Number,
required: false,
default:0
},
left:{
type: Number,
required: false,
default:0
},
},
watch: {
imgUrl() {
this.canvasHistory = []
this.initCanvas()
}
},
created() {
const that = this
this.$nextTick(() => {
that.initCanvas()
})
},
methods: {
async initCanvas () {
// 获取画布
const canvas = document.getElementById('canvas')
this.canvas = canvas
// 定义画布
const ctx = canvas.getContext('2d');
this.ctx = ctx
let that = this
const img = new Image();
img.setAttribute('crossOrigin', 'anonymous')
img.src = this.imgUrl
img.onload = await function(){
canvas.width = that.width
canvas.height = that.height
document.getElementById('main').style.height = that.height + 70 + 'px'
ctx.drawImage(img, 0, 0,that.width,that.height)
var pattern = context.createPattern(ctx, 'no-repeat');
context.fillStyle = pattern;
context.fillRect(0, 0, that.width, that.height);
}
},
disfill () {
// 去掉所有绘制
document.getElementById('textBox').style.display = "none"
},
offlisten() {
this.canvas.onmousedown = () => {}
this.canvas.onmouseup = () => {}
},
async fillM () {
await this.offlisten()
// this.ctx.fillText('Hello world', 10, 50)
const textBox = document.getElementById('textBox')
const that = this
this.canvas.onmousedown = function mouseDownAction(e) {
if (textBox.innerText) {
that.addHistory()
that.ctx.font = "28px orbitron";
that.ctx.fillStyle = that.color; // 填充颜色为红色
that.ctx.strokeStyle = that.color; // 画笔的颜色
that.ctx.lineWidth = 5; // 指定描边线的宽度
that.ctx.fillText(textBox.innerText, that.positions.x, that.positions.y)
textBox.style.display = "none"
textBox.innerText = ''
that.text = ''
return
}
that.positions.x = e.offsetX
that.positions.y = e.offsetY
textBox.style.left = (e.offsetX) + that.left + "px";
textBox.style.top = e.offsetY + that.top + "px";
textBox.style.display = "block"
setTimeout(()=>{
textBox.focus()
})
};
},
async fillY () {
await this.offlisten()
this.disfill()
let postx = {
startX: 0,
endX: 0,
startY: 0,
endY:0
}
const that = this
this.canvas.onmousedown = function mouseDownAction(e) {
postx.startX = e.offsetX
postx.startY = e.offsetY
that.addHistory()
};
this.canvas.onmouseup = function mouseUp(e) {
postx.endX = e.offsetX
postx.endY = e.offsetY
that.ctx.fillStyle = "red"; // 填充颜色为红色
that.ctx.strokeStyle = that.color; // 画笔的颜色
that.ctx.lineWidth = 5; // 指定描边线的宽度
that.ctx.beginPath();
const length = Math.sqrt( Math.pow((postx.startX-postx.endX), 2)+Math.pow((postx.startY-postx.endY), 2))
that.ctx.arc(postx.startX, postx.startY,length, 0, 2 * Math.PI)
that.ctx.stroke()
that.ctx.closePath();
postx = {
startX: 0,
endX: 0,
startY: 0,
endY:0
}
}
},
async fillJ () {
await this.offlisten()
this.disfill()
let postx = {
startX: 0,
endX: 0,
startY: 0,
endY:0
}
const that = this
this.canvas.onmousedown = function mouseDownAction(e) {
postx.startX = e.offsetX
postx.startY = e.offsetY
that.addHistory()
};
this.canvas.onmouseup = function mouseUp(e) {
postx.endX = e.offsetX
postx.endY = e.offsetY
that.ctx.fillStyle = "red"; // 填充颜色为红色
that.ctx.strokeStyle = that.color; // 画笔的颜色
that.ctx.lineWidth = 5; // 指定描边线的宽度
that.ctx.beginPath()
that.ctx.strokeRect(postx.startX,postx.startY, postx.endX-postx.startX, postx.endY-postx.startY);
that.ctx.closePath();
postx = {
startX: 0,
endX: 0,
startY: 0,
endY:0
}
}
},
async fillS () {
await this.offlisten()
const that = this
this.ctx.fillStyle="#FF0000";
this.ctx.lineWidth=5;
this.ctx.strokeStyle= this.color; //将画笔设置为红色
let x= undefined
let y = undefined
this.canvas.onmousedown = function(event) {
x = event.offsetX
y = event.offsetY
that.addHistory()
document.onmousemove = function(event) {
var x1 = event.offsetX
var y1 = event.offsetY
// this.huabi(x, y, x1, y1, ctx);
that.ctx.beginPath();
that.ctx.globalAlpha = 1;
that.ctx.lineWidth = 5;
that.ctx.strokeStyle = that.color;
that.ctx.moveTo(x, y);
that.ctx.lineTo(x1, y1);
that.ctx.closePath();
that.ctx.stroke();
x = x1;
y = y1;
};
};
document.onmouseup = function() {
this.onmousemove = null;
};
},
async disFill () {
await this.disfill()
await this.offlisten()
if (this.canvasHistory.length){
let that = this
let canvasPic = new Image();
canvasPic.src = this.canvasHistory.pop()
canvasPic.addEventListener('load', () => {
that.ctx.drawImage(canvasPic, 0, 0);
});
}else{
this.$message.error('不能再撤回了')
}
},
addHistory () {
this.canvasHistory.push(document.getElementById('canvas').toDataURL('image/png'))
},
turnUrl () {
const url = document.getElementById('canvas').toDataURL('image/png')
console.log(url)
return url
},
isMark () {
// 有长度就是有标记
return this.canvasHistory.length
}
}
}
</script>
<style>
.main{
background: black;
margin: auto;
padding: 20px 0 0 0;
}
.canvas-style{
z-index: 5;
}
.text-style{
position: absolute;
font: 28px orbitron;
word-break: break-all;
background-color: transparent;
resize: none;
z-index: 1;
display: none;
border: none;
overflow: hidden;
color: #1e1e1e;
}
.pater{
cursor: pointer;
text-align: center;
}
.dzm-input {
padding: 5px;
border: 1px solid red;
}
/* 输入框为空时显示 placeholder */
.dzm-input:empty:before {
content: attr(placeholder);
color: red;
}
/* 输入框获取焦点时移除 placeholder */
.dzm-input:focus:before {
content: none;
}
.dropdown-content {
display: none;
margin: -30px 0 0 -40px;
position: absolute;
z-index: 100;
}
.dropdown:hover .chat-bubble {
display: block;
}
.chat-bubble{
/*display: none;*/
width: 130px;
height: 40px;
border: 1px solid #000000;
background: black;
position: absolute;
margin-top: -60px;
}
.chat-bubble::before{
content: '';
width: 0;
height: 0;
border: 15px solid;
position: absolute;
bottom: -30px;
left: 40px;
border-color: #000000 transparent transparent;
}
</style>
> 第一次发文,多多指教
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END