刚刚学习election,心血来潮做些小工具,工具包含常用的矩形、椭圆框线,箭头绘制,笔刷以及文字。
支持框选截图范围后拖动以及裁剪。
大多都是查找网上大佬提供的思路,其中箭头绘制来自大佬代码魔改如何用canvas画一个漂亮的箭头用于永中文档批注场景https://blog.csdn.net/codingMonkeyKing/article/details/51459487
老缝合怪了,第一次做这种,很多不完善的地方,闲暇时候再慢慢优化吧
效果图:
?
上正菜:
单独用main.js写工具的主线程,免得代码太多分不清
这里面遇到的坑就是打开窗口时会出现闪屏,原因是因为家在页面有一定的延迟,所以用了个比较笨的办法:主线程加载时提前开启窗口并隐藏,调用时直接显示,这种做法费内存但很有效!
为了方便调试没有开启窗口强制置顶,并开启了调试器
//main.js
const {BrowserWindow, ipcMain, globalShortcut, desktopCapturer, screen} = require('electron')
const os = require('os')
const path = require('path')
let captureWin = null;
const captureScreen = () => {
var mainScreen = screen.getPrimaryDisplay();
var allScreens = screen.getAllDisplays();
// let size = screen.getPrimaryDisplay().workAreaSize
//捕获屏幕截图
desktopCapturer.getSources({
types: ['screen'],
thumbnailSize: {width: mainScreen.size.width, height: mainScreen.size.height}
}).then(imgs => {
let imageData=imgs[0].thumbnail.toDataURL()
global._cut_img_data_temp=imageData; //临时全局变量
if (captureWin) {
captureWin.webContents.send("imageData",imageData)
captureWin.show();
if (!process.env.IS_TEST) captureWin.webContents.openDevTools()
}
})
};
const useScreenshot = () => {
//提前创建窗口防止闪屏
captureWin = new BrowserWindow({
fullscreen: true,
transparent: true,
frame: false,
resizable: false,
//enableLargerThanScreen: true,
//skipTaskbar: true,
//alwaysOnTop: true,
show: false,
icon: path.join(__static, '/favicon.ico'), // 更换图标, 这里的图标仅支持svg 和icon 图标
webPreferences: {
webSecurity: false, // 是否禁用浏览器的跨域安全特性
enableRemoteModule: true,
nodeIntegration: true, // 是否完整支持node
contextIsolation: false,//--增加改行解决我的报错
preload: __dirname + '/preload.js',
// preload:'/src/preload.js'
}
});
captureWin.loadURL(global.winURL + '#/Screenshot');
captureWin.on('ready-to-show', function () {
// cs_edit_win.webContents.send('imageData',imageData);
});
captureWin.hide()
globalShortcut.register('Esc', () => {
closeScreenshot()
})
globalShortcut.register('CmdOrCtrl+Shift+A', captureScreen)
ipcMain.on('captureScreen', (e, {type = 'start', screenId} = {}) => {
if (type === 'start') {
captureScreen()
} else if (type === 'complete') {
// nothing
} else if (type === 'select') {
captureWin.webContents.send('captureScreen', {type: 'select', screenId})
}
})
ipcMain.on('closeScreenshot', (e) => {
closeScreenshot()
})
};
const closeScreenshot=function(){
if (captureWin) {
captureWin.hide();
setTimeout(function () {
captureWin.close()
useScreenshot()
},200)
// captureWin.hide()
}
}
exports.useScreenshot = useScreenshot;
exports.captureScreen = captureScreen;
background.js
election默认主线程中引入并初始化
//background.js
....
const { useScreenshot } = require('../mainProcess/screenshot/main');
...
app.on('ready', async () => {
// 初始化截图
useScreenshot()
});
Screenshot.vue?
截图窗口的操作页面
Screenshot.vue
<template>
<div id="layout">
<div id="mask"></div>
<div id="mask_tran"></div>
<img id="imgBase" :src="imageData" draggable="false">
<canvas id="_canvas" ctrl-type="move" tabindex="0"></canvas>
<div id="resize-bar-r" class="canvas-resize-bar canvas-resize-r"></div>
<div id="resize-bar-b" class="canvas-resize-bar canvas-resize-b"></div>
<div id="resize-bar-rb" class="canvas-resize-bar canvas-resize-rb"></div>
<div id="sizeBar"></div>
<div id="edit-bar">
<ul>
<!-- <li :class="{selected:currentSelect==='move'}" title="移动工具" @click="move($event)" >
<i class="sofunfont sficonmove"></i>
</li>-->
<li class="edit-item" :class="{selected:currentSelect==='rect'}" title="矩形工具" @click="rect($event)">
<i class="sofunfont sficonxingzhuang-juxing"></i>
</li>
<li class="edit-item" :class="{selected:currentSelect==='circ'}" title="椭圆工具" @click="circ($event)">
<i class="sofunfont sficonquan"></i>
</li>
<li class="edit-item" :class="{selected:currentSelect==='arrow'}" title="箭头工具" @click="arrow($event)">
<i class="sofunfont sficonyidong"></i>
</li>
<li class="edit-item" :class="{selected:currentSelect==='brush'}" title="画笔工具" @click="brush($event)">
<i class="sofunfont sficonhuabi"></i>
</li>
<li class="edit-item" :class="{selected:currentSelect==='words'}" title="文字工具" @click="words($event)">
<i class="sofunfont sficonwenzi"></i>
</li>
<li class="edit-item" title="颜色工具" @click="colorSelect($event)">
<i class="sofunfont sficoncolorSelector" :style="{color:color}"></i>
</li>
<li class="edit-item" title="下载" @click="downloadToLocal">
<i class="sofunfont sficonxiazai3"></i>
</li>
<li class="edit-item" title="取消" @click="close">
<i class="sofunfont sficonclose-bold"></i>
</li>
<li class="edit-item" title="完成" @click="done">
<i class="sofunfont sficongouxuan"></i>
</li>
</ul>
<div id="colorSelector" v-if="currentSelect === 'color'">
<div class="color-item" v-for="item in colorList" :style="{background:item}" @click="onColor(item)"></div>
</div>
</div>
</div>
</template>
<script>
import {EditTools} from "./utils/imageEdit"
export default {
name: "Screenshot",
data() {
return {
imageData: "",
img: null,
canvas: null,
ctx: null,
currentSelect:"move",
color:"#ff0000",
colorList:[
"#ff0000",
"#ffbc38",
"#07b101",
"#0634b1",
"#b11b9b",
"#39b19a",
"#ffffff",
"#000000",
"#737373",
"#1990c2",
"#32b7ff",
"#b13366",
"#b1004f",
],
//截图框起始坐标
canvasStartPosition: {x: 0, y: 0},
//截图框结束坐标
canvasEndPosition: {x: 0, y: 0},
//截图框中图片坐标
canvasImgPosition: {x: 0, y: 0},
//截图框尺寸
canvasSize: {width: 0, height: 0},
//0:未开始 | 1:正在截取(左键按下) | 2:截取完成 | 3:正在拖动(左键按下) | 4:正在改变尺寸(左键按下)
cutState: 0,
editTools:null
}
},
mounted() {
let that = this;
//获取截屏图片并注入
ipcRenderer.on("imageData", (e, imageData) => {
that.imageData = imageData
that.canvas = document.getElementById('_canvas');
that.ctx = this.canvas.getContext('2d');
that.canvas.width = 0;
that.canvas.height = 0;
that.init();
})
},
methods: {
init: function () {
let that = this;
let w_w = window.innerWidth;
let w_h = window.window.innerHeight;
//截图事件
let mask = document.getElementById("mask_tran")
mask.addEventListener('mousedown', (event) => {
if (that.cutState != 0) {
return false;
}
that.cutState = 1;
let x = event.offsetX, y = event.offsetY;
this.canvasStartPosition = {x: x, y: y};
this.canvas.style.top = y + "px";
this.canvas.style.left = x + "px";
this.canvas.style.display = "inline-block";
mask.addEventListener("mousemove", (e) => {
if (that.cutState != 1) {
return false;
}
that.setCanvas(e.offsetX, e.offsetY)
});
});
document.addEventListener('mouseup', (event) => {
//截图拖动结束
if (that.cutState === 1) {
that.cutState = 2;
that.showEditBar();
mask.style.display = "none";
that.canvasListener()
}
//移动截图框结束
if (that.cutState === 3) {
that.cutState = 2;
that.canvasListener()
}
//resize结束
if (that.cutState === 4) {
that.cutState = 2;
that.canvasListener()
}
});
document.oncontextmenu = () => {
if (that.cutState === 2) {
that.cutState = 0;
that.canvas.width = 0;
that.canvas.height = 0;
that.hideEditBar();
mask.style.display = "inline-block";
that.canvas.style.display = "none";
that.canvasListener();
}
//退出截图
if (that.cutState === 0) {
that.close();
}
};
//canvas拖动事件
that.canvas.addEventListener("mousedown", (e) => {
if(that.canvas.getAttribute("ctrl-type") !== "move"){
return false;
}
if (that.cutState !== 2) {
return false;
}
that.cutState = 3;
//鼠标相当于canvas的坐标
let rePosition = {
x: e.pageX - that.canvasStartPosition.x,
y: e.pageY - that.canvasStartPosition.y
};
document.addEventListener("mousemove", function (e) {
if (that.cutState !== 3) {
return false;
}
let x = e.pageX, y = e.pageY;
let reX = x - rePosition.x;
let reY = y - rePosition.y;
//限制在屏幕范围内
reX = reX >= 0 ? reX : 0;
reY = reY >= 0 ? reY : 0;
reX = reX + that.canvasSize.width <= w_w ? reX : w_w - that.canvasSize.width;
reY = reY + that.canvasSize.height <= w_h ? reY : w_h - that.canvasSize.height;
that.canvas.style.top = reY + "px";
that.canvas.style.left = reX + "px";
that.reSetCanvasParams();
that.canvasListener();
})
});
let resize_r=document.getElementById("resize-bar-r");
let resize_b=document.getElementById("resize-bar-b");
let resize_rb=document.getElementById("resize-bar-rb");
//canvas resize事件
let reType=0;
resize_r.addEventListener("mousedown", (e) => {
if (that.cutState !== 2) {
return false;
}
that.cutState = 4;
reType=0;
});
resize_b.addEventListener("mousedown", (e) => {
if (that.cutState !== 2) {
return false;
}
that.cutState = 4;
reType=1;
});
resize_rb.addEventListener("mousedown", (e) => {
if (that.cutState !== 2) {
return false;
}
that.cutState = 4;
reType=2;
});
document.addEventListener("mousemove", function (e) {
if (that.cutState !== 4) {
return false;
}
let x = e.pageX, y = e.pageY;
//右
if(reType===0){
that.canvasEndPosition.x=x;
that.canvasSize.width=that.canvasEndPosition.x-that.canvasStartPosition.x;
that.canvas.width=that.canvasSize.width;
}
//下
if(reType===1){
that.canvasEndPosition.y=y;
that.canvasSize.height=that.canvasEndPosition.y-that.canvasStartPosition.y;
that.canvas.height=that.canvasSize.height;
}
//右下
if(reType===2){
that.canvasEndPosition.x=x;
that.canvasSize.width=that.canvasEndPosition.x-that.canvasStartPosition.x;
that.canvas.width=that.canvasSize.width;
that.canvasEndPosition.y=y;
that.canvasSize.height=that.canvasEndPosition.y-that.canvasStartPosition.y;
that.canvas.height=that.canvasSize.height;
}
that.reSetCanvasParams();
that.canvasListener();
})
},
//canvas监听
canvasListener:function(){
let r=document.getElementById("resize-bar-r");
let b=document.getElementById("resize-bar-b");
let rb=document.getElementById("resize-bar-rb");
if(this.canvas.style.display==="none" || this.cutState<2){
r.style.top="10000px";
b.style.top="10000px";
rb.style.top="10000px";
return false
}
let width=this.canvas.offsetWidth;
let height=this.canvas.offsetHeight;
r.style.height=height+"px";
r.style.top=this.canvasStartPosition.y+"px";
r.style.left=this.canvasEndPosition.x+"px";
b.style.width=width+"px";
b.style.top=this.canvasEndPosition.y+"px";
b.style.left=this.canvasStartPosition.x+"px";
rb.style.top=(this.canvasEndPosition.y-5)+"px";
rb.style.left=(this.canvasEndPosition.x-5)+"px"
},
//重设canvas参数
reSetCanvasParams: function () {
let x = this.canvas.offsetLeft;
let y = this.canvas.offsetTop;
this.canvasStartPosition.x = x;
this.canvasStartPosition.y = y;
this.canvasEndPosition.x = x + this.canvasSize.width;
this.canvasEndPosition.y = y + this.canvasSize.height;
//重设编辑框位置
this.showEditBar();
//重设尺寸浮标位置
let sizeBar = document.getElementById("sizeBar");
sizeBar.innerHTML = (this.canvas.width + " x " + this.canvas.height);
sizeBar.style.top = (this.canvasStartPosition.y + 2) + "px";
sizeBar.style.left = (this.canvasStartPosition.x + 2) + "px";
//重设截取区域
this.canvasImgPosition.x = -this.canvasStartPosition.x - 1;
this.canvasImgPosition.y = -this.canvasStartPosition.y - 1;
this.ctx.drawImage(this.img, this.canvasImgPosition.x, this.canvasImgPosition.y);
},
//设置canvas坐标,尺寸
setCanvas: function (endX, endY) {
this.canvasImgPosition.x = -this.canvasStartPosition.x - 1;
this.canvasImgPosition.y = -this.canvasStartPosition.y - 1;
this.canvasEndPosition.x = endX;
this.canvasEndPosition.y = endY;
let canvasSize = {width: endX - this.canvasStartPosition.x, height: endY - this.canvasStartPosition.y};
if (this.canvasSize.width === canvasSize.width && this.canvasSize.height === canvasSize.height) {
return
}
this.canvasSize.width = canvasSize.width >= 0 ? canvasSize.width : 0;
this.canvasSize.height = canvasSize.height >= 0 ? canvasSize.height : 0;
this.canvas.width = this.canvasSize.width;
this.canvas.height = this.canvasSize.height;
let img = document.getElementById("imgBase");
this.img = img
this.ctx.drawImage(this.img, this.canvasImgPosition.x, this.canvasImgPosition.y);
let sizeBar = document.getElementById("sizeBar");
sizeBar.innerHTML = (this.canvas.width + " x " + this.canvas.height);
sizeBar.style.display = "inline-block";
sizeBar.style.top = (this.canvasStartPosition.y + 2) + "px";
sizeBar.style.left = (this.canvasStartPosition.x + 2) + "px";
this.canvasListener();
},
showEditBar: function () {
let bar = document.getElementById("edit-bar");
let w = bar.offsetWidth;
let h = bar.offsetHeight;
let w_w = window.innerWidth;
let w_h = window.window.innerHeight;
bar.style.left = "unset";
bar.style.right = "unset";
bar.style.top = "unset";
bar.style.bottom = "unset";
//工具栏默认置于截图框左下角
bar.style.top = (this.canvasEndPosition.y + 2) + "px";
bar.style.left = this.canvasStartPosition.x + "px";
//工具栏右侧超出屏幕
if (this.canvasEndPosition.x + w > w_w) {
bar.style.left = "unset";
bar.style.right = (w_w - this.canvasEndPosition.x) + "px"
}
//工具栏超出下方屏幕
if (this.canvasEndPosition.y + h > w_h) {
bar.style.top = (this.canvasStartPosition.y - 2 - h) + "px";
}
//工具上下方都超出
if (this.canvasEndPosition.y + h > w_h && this.canvasStartPosition.y - h - 2 < 0) {
bar.style.top = (this.canvasStartPosition.y + 2) + "px";
bar.style.left = (this.canvasEndPosition.x - w - 2) + "px";
}
this.editTools=new EditTools({
canvas:this.canvas,
ctx:this.ctx,
img:this.img,
canvasImgPosition:this.canvasImgPosition
})
},
hideEditBar: function () {
let bar = document.getElementById("edit-bar");
bar.style.left = "unset";
bar.style.right = "unset";
bar.style.top = "unset";
bar.style.bottom = "10000px";
document.getElementById("sizeBar").style.display = "none"
},
close:function(){
Platform.send("closeScreenshot")
},
//下载至本地
downloadToLocal:function(){
let self=this
this.downLoad(this.saveAsPNG(this.canvas),function () {
//下载完成后关闭截图
setTimeout(function () {
self.close()
},100)
});
},
// 保存成png格式的图片
saveAsPNG: function (canvas) {
return canvas.toDataURL("image/png");
},
downLoad: function (url,callback) {
let a = document.createElement("a");
a.download = "sofun_"+new Date().getTime();// 设置下载的文件名
a.href = url;
document.body.appendChild(a);
a.click();
a.remove(); // 下载之后把创建的元素删除
callback&&typeof callback=="function"&&callback()
},
onColor:function(color){
this.color=color
this.editTools.changeColor(color)
},
//工具栏
move:function(e) {
this.editTools.move(e);
this.currentSelect="move"
},
rect:function(e) {
this.editTools.drawRect(e);
this.currentSelect="rect"
},
circ:function (e) {
this.editTools.drawCirc(e);
this.currentSelect="circ"
},
brush:function (e) {
this.editTools.brush(e);
this.currentSelect="brush"
},
arrow:function (e) {
this.editTools.arrow(e);
this.currentSelect="arrow"
},
words:function (e) {
this.editTools.words(e);
this.currentSelect="words"
},
colorSelect:function(e){
this.currentSelect="color"
},
done:function () {
}
}
}
</script>
<style scoped>
* {
margin: 0;
padding: 0;
user-select: none;
-webkit-user-drag: none;
}
#layout {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
#imgBase {
position: absolute;
width: 100%;
z-index: -1;
}
#mask {
z-index: 1;
background: rgba(0, 0, 0, 0.6);
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
#mask_tran {
z-index: 99999;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
cursor: crosshair;
}
canvas {
position: absolute;
z-index: 5;
background: rgba(0, 0, 0, .6);
top: 230px;
left: 634px;
border: 1px dashed #ccc;
box-sizing: border-box;
display: none;
cursor: move;
}
.canvas-resize-bar{
position: absolute;
z-index: 99;
}
.canvas-resize-r{
width: 5px;
cursor: e-resize;
}
.canvas-resize-b{
height: 5px;
cursor: s-resize;
}
.canvas-resize-rb{
width: 10px;
height: 10px;
cursor: nw-resize;
}
#edit-bar {
position: absolute;
z-index: 999;
right: 0;
bottom: 10000px;
background: #fff;
height: 40px;
white-space: nowrap;
border: 1px solid #eaeaea;
}
#edit-bar ul li {
list-style: none;
display: inline-block;
width: 40px;
height: 40px;
text-align: center;
line-height: 40px;
cursor: default;
transition: background .3s;
position: relative;
}
#edit-bar ul li:hover {
background: #eaeaea;
}
#edit-bar .selected{
box-shadow: inset #ccc 0 0 6px 3px;
}
#sizeBar {
position: absolute;
display: none;
background: rgba(0, 0, 0, 0.5);
color: #fff;
z-index: 9;
padding: 5px;
border-radius: 5px;
font-size: 12px;
}
#colorSelector{
position: absolute;
top: 43px;
background: #fff;
box-shadow: #ccc 0 0 2px;
}
#colorSelector .color-item{
width: 15px;
height: 15px;
margin: 5px;
border: 1px solid #ccc;
cursor: pointer;
display: inline-block;
}
</style>
<style>
#app {
background: unset;
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
border-radius: 0;
}
</style>
imageEdit.js
这里基本都是截图后的工具栏中的相关操作
import $ from "jquery"
function EditTools(options) {
let self = this;
this.canvas = options.canvas;
this.ctx = options.ctx;
this.img = options.img;
this.canvasImgPosition = options.canvasImgPosition;
this.history = [];
this.historyAll = [];
this.color = "#ff0000";
this.fillColor = "#ff0000";
this.lineWidth = 1;
this.fontSize = 20;
this.initStyle();
this.typeEnum = {
move: "move", //移动
rect: "rect", //矩形
circ: "circ", //圆
brush: "brush", //画笔
arrow: "arrow", //箭头
words: "words", //文字
};
this.type = "";
this.tempParam = [];
this.canvas.onmousedown = function (event) {
if (self.type !== self.typeEnum.rect
&& self.type !== self.typeEnum.circ
&& self.type !== self.typeEnum.brush
&& self.type !== self.typeEnum.arrow
&& self.type !== self.typeEnum.words) {
return false;
}
switch (self.type) {
case self.typeEnum.rect:
self.doRect(event);
break;
case self.typeEnum.circ:
self.doCirc(event);
break;
case self.typeEnum.brush:
self.tempParam = []; //将临时参数容器类型设为数组,避免保存记录时失败
self.doBrush(event);
break;
case self.typeEnum.arrow:
self.doArrow(event);
break;
case self.typeEnum.words:
self.doWords(event);
break;
}
}
this.canvas.addEventListener('mouseup', (event) => {
if (self.type !== self.typeEnum.rect
&& self.type !== self.typeEnum.circ
&& self.type !== self.typeEnum.brush
&& self.type !== self.typeEnum.arrow) {
return false;
}
// 结束本次绘画
self.canvas.onmousemove = null;
self.canvas.onclick = null;
this.history.push({
method: self.type,
params: this.tempParam,
color: this.color
});
this.historyAll.push({
method: self.type,
params: this.tempParam,
color: this.color
});
this.tempParam = "";
this.ctx.closePath();
this.ctx.save()
})
//按键监听
this.canvas.addEventListener('keydown', (e) => {
let keyCode = e.keyCode || e.which || e.charCode;
let ctrlKey = e.ctrlKey;
if (ctrlKey) {
switch (keyCode) {
case 89://ctrl+y
self.redo();
break;
case 90://ctrl+z
self.undo();
break;
}
}
})
}
EditTools.prototype.initStyle = function () {
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.fillColor;
this.ctx.lineWidth = this.lineWidth;
}
EditTools.prototype.changeColor = function (color) {
this.color = color;
this.fillColor = color;
this.lineWidth = color;
this.initStyle();
}
/**
* 撤销
* @param e
*/
EditTools.prototype.undo = function (e) {
if (this.history.length > 0) {
this.ctx.clearRect(0, 0, this.width, this.height);
this.history.pop();
this.ctx.drawImage(this.img, this.canvasImgPosition.x, this.canvasImgPosition.y);
this.reDraw()
}
};
/**
* 重写
* @param e
*/
EditTools.prototype.redo = function (e) {
if (this.history.length < this.historyAll.length) {
this.ctx.clearRect(0, 0, this.width, this.height);
this.history.push(this.historyAll[this.history.length]);
this.ctx.drawImage(this.img, this.canvasImgPosition.x, this.canvasImgPosition.y);
this.reDraw()
}
};
/**
* 按照历史记录重新绘制
* @param e
*/
EditTools.prototype.reDraw = function (e) {
// 逐个执行历史动作
for (let item of this.history) {
this.ctx.strokeStyle = item.color;
this.ctx.fillStyle = item.color;
//矩形
if (item.method === this.typeEnum.rect) {
// this.ctx.beginPath();
this.ctx.strokeRect(...item.params);
// this.ctx.closePath();
}
//圆
if (item.method === this.typeEnum.circ) {
this.ctx.beginPath();
this.ctx.ellipse(...item.params);
this.ctx.stroke();
}
//画笔
if (item.method === this.typeEnum.brush) {
this.ctx.beginPath();
item.params.forEach(param => {
this.ctx.lineTo(...param);
this.ctx.stroke();
});
this.ctx.closePath();
}
//箭头
if (item.method === this.typeEnum.arrow) {
this.drawLineArrow(...item.params);
}
//文字
if (item.method === this.typeEnum.words) {
this.drawText(...item.params);
}
}
this.ctx.strokeStyle = this.color;
this.ctx.fillStyle = this.fillColor;
};
//坐标修正
// EditTools.prototype.correctPosition=function(x,y){
// this.ctx.translate(x,y);
// this.ctx.drawImage(this.img, this.canvasImgPosition.x-x, this.canvasImgPosition.y-y);
//
// }
EditTools.prototype.move = function (e) {
this.canvas.setAttribute("ctrl-type", this.typeEnum.move);
this.canvas.style.cursor = "move";
this.historyAll = [];
this.history = [];
};
/**
* 初始化到上一次的绘制,用于绘制清除痕迹
* @param e
*/
EditTools.prototype.clearAndUpdateParam = function (...param) {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(this.img, this.canvasImgPosition.x, this.canvasImgPosition.y);
this.tempParam = param;
this.reDraw()
}
EditTools.prototype.saveBrushPath = function (...param) {
this.tempParam.push(param)
};
EditTools.prototype.saveDrawText = function (...param) {
let self=this;
this.history.push({
method: self.type,
params: param,
color: self.color
});
this.historyAll.push({
method: self.type,
params: param,
color: self.color
});
self.tempParam = "";
}
EditTools.prototype.drawRect = function (e) {
this.canvas.setAttribute("ctrl-type", this.typeEnum.rect);
this.canvas.style.cursor = "crosshair";
this.type = this.typeEnum.rect;
};
EditTools.prototype.doRect = function (e) {
let that = this;
let offstL = this.canvas.offsetLeft;
let offstT = this.canvas.offsetTop;
let startX = e.clientX - offstL, startY = e.clientY - offstT;
this.canvas.onmousemove = function (e) {
let param = [startX, startY, e.clientX - offstL - startX, e.clientY - offstT - startY]
that.clearAndUpdateParam(...param);
that.ctx.strokeRect(...param);
};
};
EditTools.prototype.drawCirc = function (e) {
this.canvas.setAttribute("ctrl-type", this.typeEnum.circ);
this.canvas.style.cursor = "crosshair";
this.type = this.typeEnum.circ;
};
EditTools.prototype.doCirc = function (ev) {
let that = this;
let start = that.getCanvasPos(that.canvas, ev);
this.canvas.onmousemove = function (e) {
let mouse = that.getCanvasPos(that.canvas, e);
let center = {
x: (mouse.x - start.x) / 2 + start.x,
y: (mouse.y - start.y) / 2 + start.y,
};
let radiusX = Math.abs(e.clientX - ev.clientX) / 2;
let radiusY = Math.abs(e.clientY - ev.clientY) / 2;
//(起点x,起点y,半径x,半径y,旋转的角度,起始角,结果角,顺时针还是逆时针)
let param = [center.x, center.y, radiusX, radiusY, 0, 0, Math.PI * 2]
that.clearAndUpdateParam(...param);
that.ctx.beginPath();
that.ctx.ellipse(...param);
that.ctx.stroke();
};
};
/**
* 获取鼠标在canvas上的坐标
* @param canvas
* @param event
* @returns {{x: number, y: number}}
*/
EditTools.prototype.getCanvasPos = function (canvas, event) {
let rect = canvas.getBoundingClientRect();
return {
x: event.clientX - rect.left * (canvas.width / rect.width),
y: event.clientY - rect.top * (canvas.height / rect.height)
};
};
/**
* 画笔
* @param e
*/
EditTools.prototype.brush = function (e) {
let that = this;
this.canvas.setAttribute("ctrl-type", this.typeEnum.circ);
this.canvas.style.cursor = "default";
this.type = this.typeEnum.brush;
};
EditTools.prototype.doBrush = function (e) {
let that = this;
let offstL = this.canvas.offsetLeft;
let offstT = this.canvas.offsetTop;
this.ctx.beginPath();
this.canvas.onmousemove = function (e) {
that.ctx.lineTo(e.clientX - offstL, e.clientY - offstT);
that.ctx.stroke();
that.saveBrushPath(e.clientX - offstL, e.clientY - offstT)
};
};
/**
* 箭头
* @param e
*/
EditTools.prototype.arrow = function (e) {
let that = this;
this.canvas.setAttribute("ctrl-type", this.typeEnum.arrow);
this.canvas.style.cursor = "default";
this.type = this.typeEnum.arrow;
};
EditTools.prototype.doArrow = function (e) {
let that = this;
let offstL = this.canvas.offsetLeft;
let offstT = this.canvas.offsetTop;
this.canvas.onmousemove = function (ev) {
let param = [that.ctx, e.clientX - offstL, e.clientY - offstT, ev.clientX - offstL, ev.clientY - offstT];
that.clearAndUpdateParam(...param);
that.drawLineArrow(...param)
};
};
/**
*
* @param {*canvas context 对象} ctx
* @param {*起点横坐标} fromX
* @param {*起点纵坐标} fromY
* @param {*终点横坐标} toX
* @param {*终点纵坐标} toY
* 以下注释以终点在坐标第一象限内,且方向为右上方
*/
EditTools.prototype.drawLineArrow = function (ctx, fromX, fromY, toX, toY) {
let headlen = 0.2 * 1.41 * Math.sqrt((fromX - toX) * (fromX - toX) + (fromY - toY) * (fromY - toY));//箭头头部长度
headlen = headlen > 20 ? 20 : headlen;//箭头头部最大值
let theta = 30;//自定义箭头线与直线的夹角
let arrowX, arrowY;//箭头线终点坐标
// 计算各角度和对应的箭头终点坐标
let angle = Math.atan2(fromY - toY, fromX - toX) * 180 / Math.PI;
let angle1 = (angle + theta) * Math.PI / 180;
let angle2 = (angle - theta) * Math.PI / 180;
let topX = headlen * Math.cos(angle1);
let topY = headlen * Math.sin(angle1);
let botX = headlen * Math.cos(angle2);
let botY = headlen * Math.sin(angle2);
let toLeft = fromX > toX;
let toUp = fromY > toY;
//箭头最上点
arrowX = toX + topX;
arrowY = toY + topY;
//箭头下拐点
let arrowX1 = toX + botX;
let arrowY1 = toY + botY;
//箭头上拐点
let arrowX2 = toUp ? arrowX + 0.25 * Math.abs(arrowX1 - arrowX) : arrowX - 0.25 * Math.abs(arrowX1 - arrowX);
let arrowY2 = toLeft ? arrowY - 0.25 * Math.abs(arrowY1 - arrowY) : arrowY + 0.25 * Math.abs(arrowY1 - arrowY);
//箭头最下点
let arrowX3 = toUp ? arrowX + 0.75 * Math.abs(arrowX1 - arrowX) : arrowX - 0.75 * Math.abs(arrowX1 - arrowX);
let arrowY3 = toLeft ? arrowY - 0.75 * Math.abs(arrowY1 - arrowY) : arrowY + 0.75 * Math.abs(arrowY1 - arrowY);
ctx.beginPath();
//起点-起点,闭合
ctx.moveTo(fromX, fromY);
ctx.lineTo(arrowX2, arrowY2);
ctx.lineTo(arrowX, arrowY);
ctx.lineTo(toX, toY);
ctx.lineTo(arrowX1, arrowY1);
ctx.lineTo(arrowX3, arrowY3);
ctx.lineTo(fromX, fromY);
ctx.closePath();
ctx.fill();
ctx.stroke();
};
/**
* 箭头
* @param e
*/
EditTools.prototype.words = function (e) {
let that = this;
this.canvas.setAttribute("ctrl-type", this.typeEnum.words);
this.canvas.style.cursor = "text";
this.type = this.typeEnum.words;
};
EditTools.prototype.doWords = function (ev) {
let that = this;
let x = ev.offsetX, y = ev.offsetY;
this.createTextInput(this.canvas, x, y, function (setX, setY, text,maxWidth) {
let param=[that.ctx,text,setX, setY,maxWidth]
that.drawText(...param);
that.saveDrawText(...param);
})
}
EditTools.prototype.drawText=function(ctx,t,x,y,w){
let that=this;
//参数说明
let chr = t.split("")
let temp = ""
let row = []
for (let a = 0; a<chr.length;a++){
if(/\r|\r\n|\n/.test(chr[a]) ){
chr[a]="";
row.push(temp);
temp = chr[a];
}else if(ctx.measureText(temp).width < w && ctx.measureText(temp+(chr[a])).width <= w){
temp += chr[a];
}else{
row.push(temp);
temp = chr[a];
}
}
row.push(temp)
for(let b=0;b<row.length;b++){
ctx.font = that.fontSize+"px emoji";
ctx.fillText(row[b],x,y+(b+1)*that.fontSize);//每行字体y坐标间隔20
}
}
/**
* 创建文字输入框
*/
EditTools.prototype.createTextInput = function (canvas, startX, startY, onInputOver) {
let that = this;
if ($("._textInput").length > 0) {
return false
}
let rect = canvas.getBoundingClientRect();
let textareaMaxW = that.canvas.width - startX - 12;
let textareaMaxH = that.canvas.height - startY - 18;
//原生js元素操作实在麻烦,改用jq
let textarea = $("<textarea class='_textInput'></textarea>");
textarea.css({
position: "absolute",
left: rect.left + startX,
top: rect.top + startY - 15,
outline: "none",
border: "2px solid " + that.color,
resize: "none",
padding: "0 5px",
"font-size": that.fontSize,
color: that.color,
width: 80,
"max-width": textareaMaxW,
"font-family": "emoji",
height: 30
}).blur(function () {
onInputOver && typeof onInputOver == "function"
&& onInputOver(startX, startY - 15 , $(this).val(),textareaMaxW);
$(this).remove();
}).bind('input propertychange', function () {
let text = $(this).val();
let maxRow = 0
text.split(/\r|\r\n|\n/).forEach(e => {
if (e.length > maxRow) {
maxRow = e.length
}
})
if (maxRow * that.fontSize > $(this).width()) {
if (textareaMaxW > $(this).width()) {
$(this).css("width", maxRow * that.fontSize + that.fontSize)
}
} else if ($(this).width() - maxRow * that.fontSize > that.fontSize) {
$(this).css("width", maxRow * that.fontSize + that.fontSize)
}
$(this).css("height", $(this)[0].scrollHeight)
})
$("body").append(textarea)
setTimeout(function () {
textarea.focus();
})
};
// EditTools.prototype.doArrow = function (e) {
//
// };
export {EditTools}
没有接触过桌面程序的开发,vue和electron也是刚学,谁能想到我是个搞java的后端狗呢
当中的思路和方法基本都是自己琢磨出来的,在此发文也仅作为自己的学习笔记。其中也有很多明显的问题,需要后面慢慢的调整
方法很笨,别杠别喷,杠就是你对!!
|