简介
仿微信截图
实现:vue2+html2canvas+canvas,其实用什么写都差不多,只是几个状态控制好。
使用场景:审批时,对提交的资料指出错误项,如图片,这里其实还可以截视频的,指出视频中某项不合规或错误的地方。
功能
截图功能,大部分功能实现,画正方形,圆,箭头,画笔,文字,图片下载,颜色选择,以及回退。 以及可以移动绘制区域和放大缩小绘制区域
实现思路
总体组成:首先使用html2canvas 截一张需要绘制的底图。中间共使用三层。最下面一层(图片/canvas)用于这是,中间canvas层是绘制层,最上一层(canvas)用作蒙版。这样做的好处是将静态部分,相当静态部分归于一层,那么就不会每次绘制的时候绘制一次静态内容
绘制:每次绘制,将绘制的内容保存在数组中(包括颜色,粗细等状态),根据主要几个状态:c_draw_area_f(表示 框选完绘制区域),screenshot_f(代表按下截图完成),editImage(表示正在编辑),is_move_draw_area(鼠标表示正在移动可视区域)配合实现
回退:将绘制的内容放入到一个栈中,这里还可以去使用双栈实现回退和重写
移动绘制区域:根据鼠标在canvas上移动的距离,修改蒙版的坐标,重绘绘制蒙版层,以及框框上的8个点的坐标,框框上的8个点使用的是div。
改变绘制区域大小:同理修改蒙版的坐标和框框上的8个点的坐标,重绘绘制蒙版层
screenshot_f:表示按下截图完成
c_draw_area_f:表示框选完 待绘制,因为做了可以重新框选的功能(类微信)
绘制完成后下载对应图片:由于绘制的内容是在编辑层,图片在最下层层,所以下载时,新建一个canvas,将cancas1(图片),canvas3(编辑层) 一起绘制到新的画布上,然后导出
代码部分
//html
<div id="app">
<el-image
@click.native="preview=true"
style="width: 100px; height: 100px"
:src="url"
ref="img0"
:preview-src-list="[url]"
>
</el-image>
<el-image
v-if="imgUrl_c"
style="width: 100px; height: 100px"
:src="imgUrl_c"
:preview-src-list="[imgUrl_c]"
>
</el-image>
<el-image
v-if="imgUrl_f"
style="width: 100px; height: 100px"
:src="imgUrl_f"
:preview-src-list="[imgUrl_f]"
>
</el-image>
<!-- <el-button class="btn" @click="getImg"> 裁剪 </el-button> -->
<el-button class="btn2" @click="getImg" v-show="preview"> 编辑 </el-button>
<el-button class="btn" @click="closeAll" v-show="preview"> 关闭 </el-button>
<!-- <el-button class="btn3" @click="draw_overlay" v-show="preview"> test </el-button> -->
<div class="canvas_box" v-if="editImage">
<div v-show="c_draw_area_f" class="point" @mousedown="(e)=>point_down(e,i)" @mouseup="(e)=>point_up(e,i)" :style="show_positon(i)" v-for="(item,i) in 8" :key="i" ></div>
<!-- //图片层 -->
<canvas
id="canvas"
class="canvas"
:height="c_h"
:width="c_w"
:style="{ zIndex: canvasZ }"
>
</canvas>
<!-- 编辑层 -->
<canvas
id="canvas3"
class="canvas3"
:height="c_h"
:width="c_w"
:style="{ zIndex: canvasZ + 10 }"
>
</canvas>
<!-- 蒙层 -->
<canvas
id="canvas2"
class="canvas2"
:height="c_h"
:width="c_w"
@mouseup="c_mouseup"
@mousedown="c_mousedown"
@mousemove="c_mousemove"
:style="{ zIndex: canvasZ + 20,cursor: is_in_rect ? 'move' : 'crosshair' }"
>
</canvas>
<div
class="tool"
:style="{ top: Math.max(edit_rect.y1,edit_rect.y0 )+5+'px', left:Math.min(edit_rect.x0,edit_rect.x1 )+5+'px' }"
v-show="c_draw_area_f && screenshot_f&&move_point_i==-1"
>
<div style="background: #fff;padding: 5px 10px;">
<i
:style="{ backgroundColor: tool.type == edit_type ? '#d3e3fd' : '' }"
v-for="tool in tools"
:key="tool.icon"
@click.stop="tool_click(tool.type)"
:class="['iconfont', tool.icon]"
>
</i>
</div>
<div class="ctx_style" v-show="isToolEdit">
<div @click="choose_line_w(index,line.lineWidth)" class="line_box" :style="{background: line_index==index? '#d3e3fd':''}" v-for="(line,index) in lines" :key="line.class">
<div :class="['line_w',line.class]" :style="{background: line_index==index? ctx_color:''}" > </div>
</div>
<input type="color" class=" fill_color" @change="change_ctx_color" v-model="ctx_color">
<div clas="to_up_arrow"></div>
</div>
</div>
<div
class="input_div"
:style="{
zIndex: canvasZ + 30,
top: start_point.pageY + 'px',
left: start_point.pageX + 'px',
}"
v-show="show_input_div"
>
<input ref="remark" :style="{color:ctx_color}" class="input_in_canvas" v-model="remark" />
</div>
</div>
<div class="mask" v-show="isloading">loading...</div>
</div>
数据部分data
// line 的粗细选择
lines:[
{
class:'w1',
lineWidth:1
},
{
class:'w3',
lineWidth:3
},
{
class:'w5',
lineWidth:5
},
],
// 工具列表
tools: [
{
icon: "icon-kuangxuan",
type: "0",
},
{
icon: "icon-huayuan",
type: "1",
},
{
icon: "icon-jiantou",
type: "2",
},
{
icon: "icon-huabi",
type: "3",
},
{
icon: "icon-text",
type: "4",
},
{
icon: "icon-xiazai",
type: "5",
},
{
icon: "icon-yulanxuanzhuan",
type: "6",
},
{
icon: "icon-chacha",
type: "7",
},
{
icon: "icon-gou",
type: "8",
},
],
c_draw_area_f: false, //表示 框选完绘制区域
screenshot_f: false, // 代表截图完成
editImage: false, //表示编辑
is_move_draw_area:false, //表示正在移动可视区域
move_point_i:-1, //可视区域移动的8个点
is_in_rect:false,
isloading: false,
preview:false, //显示编辑按钮
line_index:1,
ctx_lineWidth:3,
ctx_color:'#d62424',
canvasZ: 3000,
url: require("./assets/test.jpg"),
imgUrl: "",
full_img_src: "",
imgUrl_c: "",
imgUrl_f: "",
caik_w: 0,
remark: "",
start_point: {
x: "",
y: "",
pageX: "",
pageY: "",
},
edit_rect: {}, //可编辑区域
start_point_store: { x: "", y: "" },
edit_type: "-1",
show_input_div: false,
drawing_obj: null,
stack: [],
computed:{
isToolEdit() { // 选中一种工具
return this.edit_type != "-1";
},
}
关键代码
鼠标按下事件
// 蒙层事件监听
c_mousedown(e) {
//完成拍照
if (this.screenshot_f) {
// 如果选中工具,则进入绘制阶段
if (this.isToolEdit) {
if(this.move_point_i>-1)return
// 鼠标在绘制范围内
if (this.isInRect( e.offsetX,e.offsetY )) {
// if(this.edit_type==4){
// this.show_input_div=true
// }else{
// 对文字方式特殊处理
if (this.edit_type == 4) {
this.start_point_store.x = this.start_point.x;
this.start_point_store.y = this.start_point.y;
if (this.show_input_div && this.start_point_store.x!=='' && this.start_point_store.y!=='') {
this.draw_text(
this.start_point_store.x,
this.start_point_store.y
);
}
this.show_input_div = !this.show_input_div;
if (this.show_input_div) {
setTimeout(() => {
this.$refs.remark.focus();
});
}
}else{
// this.mouse_down_start_time=Date.now();
if (this.edit_type == 2) {
var canvas3 = document.getElementById("canvas3");
const ctx3 = canvas3.getContext("2d");
ctx3.beginPath();
// ctx3.moveTo(e.offsetX, e.offsetY);
this.drawing_obj = {
type: "fillArrow",
// id:'line_'+this.stack.length,
moveTo: [e.offsetX, e.offsetY],
endTo: [],
};
} else if (this.edit_type == 3) {
const canvas3 = document.getElementById("canvas3");
const ctx3 = canvas3.getContext("2d");
ctx3.beginPath();
ctx3.moveTo(e.offsetX, e.offsetY);
this.drawing_obj = {
type: "fillLine",
// id:'line_'+this.stack.length,
moveTo: [e.offsetX, e.offsetY],
lineTo: [],
};
} else if (this.edit_type == 0) {
this.drawing_obj = {
type: "strokeRect",
// id:'line_'+this.stack.length,
x: e.offsetX,
y: e.offsetY,
w: 0,
h: 0,
};
} else if (this.edit_type == 1) {
this.drawing_obj = {
type: "ellipse",
// id:'line_'+this.stack.length,
x0: e.offsetX,
y0: e.offsetY,
xn: 0,
yn: 0,
};
}
}
this.start_point.x = e.offsetX;
this.start_point.y = e.offsetY;
this.start_point.pageX = e.pageX;
this.start_point.pageY = e.pageY;
}
} else {
this.start_point.x = e.offsetX;
this.start_point.y = e.offsetY;
this.start_point.pageX = e.pageX;
this.start_point.pageY = e.pageY;
// 已经绘制可视区域
if(this.c_draw_area_f){
// 表示将进行移动可视区域
if(this.isInRect(e.offsetX,e.offsetY)){
this.is_move_draw_area=true
this.start_point.x = '';
}else{
this.c_draw_area_f = false; // 重新绘制可视需求
}
}
}
}
return false
},
鼠标移动事件
c_mousemove(e) {
if ( !this.screenshot_f ) {
return;
}
// 移动可视区域
if(this.is_move_draw_area){
let canvas = document.getElementById("canvas2");
const ctx = canvas.getContext("2d");
const {movementX,movementY}=e
this.draw_overlay() // 重新绘制蒙层
this.edit_rect.x0+=movementX
this.edit_rect.y0+=movementY
this.edit_rect.x1+=movementX
this.edit_rect.y1+=movementY
// 可视区域
ctx.clearRect(this.edit_rect.x0, this.edit_rect.y0, this.edit_rect.w, this.edit_rect.h);
ctx.strokeStyle='#549cd6'
ctx.strokeRect(this.edit_rect.x0, this.edit_rect.y0, this.edit_rect.w, this.edit_rect.h);
return
}
// 移动的是 可视区域上的8个点,重新绘制可视区域
if(this.move_point_i>-1){
this.point_move(e)
return
}
if ( this.edit_type == "4" ) {
return;
}
if(!this.isToolEdit&&this.c_draw_area_f){
this.is_in_rect= this.isInRect(e.offsetX,e.offsetY ) //todo
}
if(this.start_point.x == "" ){
return
}
let canvas = document.getElementById("canvas2");
const ctx = canvas.getContext("2d");
//设置画笔
let xn = e.offsetX;
let yn = e.offsetY;
let w = xn - this.start_point.x;
let h = yn - this.start_point.y;
ctx.strokeStyle = "red";
if (this.isToolEdit) {
this.draw_3_canvas(w, h, xn, yn); // 进入绘制
} else {
// 第一次在蒙版上绘制可视区域
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
// ctx.strokeStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);
this.caik_w = w;
this.edit_rect = {
x0: this.start_point.x,
y0: this.start_point.y,
x1: xn,
y1: yn,
w: w,
h: h,
};
ctx.clearRect(this.start_point.x, this.start_point.y, w, h);
ctx.strokeStyle='#549cd6'
ctx.strokeRect(this.start_point.x, this.start_point.y, w, h);
}
},
下载图片
getMImg() {
var canvas = document.getElementById("canvas"); // 原图
var canvas3 = document.getElementById("canvas3");// 编辑层
var mergedCanvas = document.createElement("canvas");
var mergedContext = mergedCanvas.getContext("2d");
//this.edit_rect; 截图的范围 左上角,右下角,宽高
const { x0, x1, y0, y1, w, h } = this.edit_rect;
let x_s = Math.min(x0, x1); //只需要截出来那一部分
let y_s = Math.min(y0, y1); //只需要截出来那一部分
mergedCanvas.width = w;
mergedCanvas.height = h;
mergedContext.drawImage(canvas, x_s, y_s, w, h, 0, 0, w, h);
mergedContext.drawImage(canvas3, x_s, y_s, w, h, 0, 0, w, h);
return mergedCanvas;
},