一、起应以及分析
最近面试的时候被面试官问到了canvas做个window画图,就想着试试能不能撸一个出来。
首先嘛,就先撸个简洁版,能画能擦处能撤回。那么需求定好了,就开始分析吧!
首先能画,就是画笔这个功能,我看了一下,canvas里面有这么几个函数,组合起来可以实现画笔这个功能
橡皮擦嘛!就用清除
那么剩下的就是业务需求了,存档和撤回
撤回嘛!我们肯定要有存档才能撤回,存档我们不可能每次把自己做过的步骤都得存下来,撤回一次多麻烦,那么我们就得把整个画布得保存成base64的数据,用来撤回,好巧不巧,canvas就有这个方法
二、实战
template
<template>
<div id="loon_canvas_editor">
<canvas :ref="id" :width="width" :height="height"></canvas>
<div id="toolbar">
<img :class="toolStatus==1?'select':''" @click="useBrush" src="/components/brush.png" alt="">
<img :class="toolStatus==2?'select':''" @click="useEraser" src="/components/eraser.png" alt="">
<div class="interval"></div>
<img @click="withdraw" src="/components/withdraw.png" alt="">
<img @click="save" src="/components/save.png" alt="">
</div>
</div>
</template>
复制代码
css
<style lang="less" scoped>
#loon_canvas_editor{
width: 100%;
height: 100%;
font-size: 0;
margin: 0 auto;
canvas{
border: 1px solid #39f;
}
#toolbar{
display: flex;
width: 300px;
padding: 10px 20px;
background: #deeeff;
border: 1px solid #39f;
margin-top: -1px;
img{
width: 20px;
height: 20px;
margin-right: 10px;
}
.shape{
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
}
.select{
border: 1px solid #222;
}
.interval{
width: 2px;
height: 20px;
background: #aaa;
margin-right: 10px;
}
}
}
</style>
复制代码
样式效果图
接下来就是js代码了
首先肯定是画笔
useBrush(){
// 这个函数暂且忽略后续会讲
this.clearAllTool()
// 首先判断是否使用画笔
if(this.toolStatus != 1){
// 开启
this.toolStatus = 1
// 2.在移动的路径内做点连线
var mouseMoveEvent = (e)=>{
// 节流 保存一秒60帧以内
if(this.throttleDate==0 || this.throttleDate+16<Date.now()){
// 2.获取当前鼠标的坐标,做一个坐标点
this.ctx.lineTo(e.layerX,e.layerY);
// 与上一个坐标点连线
this.ctx.stroke();
}
}
// 1.单击做一个起始点(按顺序看)
this.brushEvents.down = (e) => {
this.ctx.beginPath();
this.ctx.moveTo(e.layerX,e.layerY);
this.canvas.addEventListener('mousemove',mouseMoveEvent)
}
// 3.松开鼠标后,清空move事件,存档
this.brushEvents.up = (e) => {
this.canvas.removeEventListener('mousemove', mouseMoveEvent)
this.ctx.stroke();
this.save()
}
this.canvas.addEventListener('mousedown', this.brushEvents.down);
this.canvas.addEventListener('mouseup', this.brushEvents.up)
}else{
// 关闭
this.toolStatus = 0
}
},
复制代码
以上代码用一句话来说就是,在鼠标单击的时候做个坐标点,然后每次触发移动事件的时候,获取到坐标进行连线,松开后则存档
能画之后就可以开始写保存和撤回
save(){
// 如果没有撤回存档
if(this.saveRecords.length == this.page){
console.info('保存')
// 则直接保存
this.saveRecords.push(this.canvas.toDataURL())
this.page++;
}else{
console.info('覆盖缓存')
// 否者,则清空当前页之后的存档
for (let i = 0; i < this.saveRecords.length-this.page; i++) {
this.saveRecords.pop()
}
}
},
withdraw(){
var img = new Image();
this.page --
img.src = this.saveRecords[this.page-1];
// 清空
this.ctx.clearRect(0, 0, this.width, this.height);
// 撤回
img.onload = ()=>{
this.ctx.drawImage(img, 0, 0);
}
},
复制代码
存档这边得先说撤回,撤回的逻辑就是,把你当前这一页的坐标page-1,然后拿到上一页覆盖,(不删除存档,到时候可以做前进Ctrl+Y),保存的话,如果撤回就覆盖之后的存档
橡皮擦
useEraser(){
this.clearAllTool()
if(this.toolStatus != 2){
this.toolStatus = 2
var mouseMoveEvent = (e)=>{
if(this.throttleDate==0 || this.throttleDate+16<Date.now()){
// 核心代码,清除
this.ctx.clearRect(e.layerX-5,e.layerY-5, 10,10);
}
}
this.eraserEvents.down = (e) => {
this.ctx.clearRect(e.layerX-5,e.layerY-5, 10,10);
this.canvas.addEventListener('mousemove',mouseMoveEvent)
}
this.eraserEvents.up = (e) => {
this.canvas.removeEventListener('mousemove', mouseMoveEvent)
this.save()
}
this.canvas.addEventListener('mousedown', this.eraserEvents.down);
this.canvas.addEventListener('mouseup', this.eraserEvents.up)
}else{
// 关闭
this.toolStatus = 0
}
},
复制代码
这个我就不多说了,核心代码就是clearRect,逻辑和画笔一样
别忘了清空事件监听器
clearAllTool(){
this.toolStatus = 0
console.info('清除所有工具',this.canvas.removeEventListener,this.brushEvents.down)
this.canvas.removeEventListener('mousedown',this.brushEvents.down)
this.canvas.removeEventListener('mouseup',this.brushEvents.up)
this.canvas.removeEventListener('mousedown',this.eraserEvents.down)
this.canvas.removeEventListener('mouseup',this.eraserEvents.up)
}
复制代码
三、完整代码
<script>
import { uuid } from 'uuidv4'
export default {
name:'loonCanvasEditor',
props:[
'width',
'height'
],
data(){
return{
id:uuid(),
saveRecords:[],// 历史存档
canvas:null,
ctx:null,
toolStatus:0,
page:0, // pageIndex
throttleBrush:null, // 画笔节流
throttleDate: 0,
brushEvents:{
down:()=>{},
up:()=>{}
},
eraserEvents:{
down:()=>{},
up:()=>{}
},
}
},
methods:{
save(){
// 如果没有撤回存档
if(this.saveRecords.length == this.page){
console.info('保存')
// 则直接保存
this.saveRecords.push(this.canvas.toDataURL())
this.page++;
}else{
console.info('覆盖缓存')
// 否者,则清空当前页之后的存档
for (let i = 0; i < this.saveRecords.length-this.page; i++) {
this.saveRecords.pop()
}
}
},
withdraw(){
var img = new Image();
this.page --
img.src = this.saveRecords[this.page-1];
this.ctx.clearRect(0, 0, this.width, this.height);
img.onload = ()=>{
this.ctx.drawImage(img, 0, 0);
}
},
// 使用画笔
useBrush(){
this.clearAllTool()
if(this.toolStatus != 1){
// 开启
this.toolStatus = 1
var mouseMoveEvent = (e)=>{
if(this.throttleDate==0 || this.throttleDate+16<Date.now()){
this.ctx.lineTo(e.layerX,e.layerY);
this.ctx.stroke();
}
}
this.brushEvents.down = (e) => {
this.ctx.beginPath();
this.ctx.moveTo(e.layerX,e.layerY);
this.canvas.addEventListener('mousemove',mouseMoveEvent)
}
this.brushEvents.up = (e) => {
this.canvas.removeEventListener('mousemove', mouseMoveEvent)
this.ctx.stroke();
this.save()
}
this.canvas.addEventListener('mousedown', this.brushEvents.down);
this.canvas.addEventListener('mouseup', this.brushEvents.up)
}else{
// 关闭
this.toolStatus = 0
}
},
// 使用橡皮
useEraser(){
this.clearAllTool()
if(this.toolStatus != 2){
this.toolStatus = 2
var mouseMoveEvent = (e)=>{
if(this.throttleDate==0 || this.throttleDate+16<Date.now()){
this.ctx.clearRect(e.layerX-5,e.layerY-5, 10,10);
}
}
this.eraserEvents.down = (e) => {
this.ctx.clearRect(e.layerX-5,e.layerY-5, 10,10);
this.canvas.addEventListener('mousemove',mouseMoveEvent)
}
this.eraserEvents.up = (e) => {
this.canvas.removeEventListener('mousemove', mouseMoveEvent)
this.save()
}
this.canvas.addEventListener('mousedown', this.eraserEvents.down);
this.canvas.addEventListener('mouseup', this.eraserEvents.up)
}else{
// 关闭
this.toolStatus = 0
}
},
clearAllTool(){
this.toolStatus = 0
console.info('清除所有工具',this.canvas.removeEventListener,this.brushEvents.down)
this.canvas.removeEventListener('mousedown',this.brushEvents.down)
this.canvas.removeEventListener('mouseup',this.brushEvents.up)
this.canvas.removeEventListener('mousedown',this.eraserEvents.down)
this.canvas.removeEventListener('mouseup',this.eraserEvents.up)
}
},
mounted(){
this.canvas = this.$refs[this.id]
this.ctx = this.canvas.getContext('2d')
this.canvas.style.width = this.width
this.canvas.style.height = this.height
this.save()
}
}
</script>
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END