editImage 组件
<template>
<!-- v-show="$store.state.showDoodling" -->
<div class="editImage">
<div class="optBtn">
<!-- style="width: 100vw;height: 100vh;display: flex;justify-content: center;align-items: center;" -->
<div>
<canvas
ref="canvas"
:id="radom"
:class="{ canDraw: 'canvas' }"
@mousedown="canvasDown($event)"
@mouseup="canvasUp($event)"
@mousemove="canvasMove($event)"
@touchstart="canvasDown($event)"
@touchend="canvasUp($event)"
@touchmove="canvasMove($event)"
>
</canvas>
</div>
<div class="tuyaTit">
<span class="tycancel" @click="goBack">取消</span>
<span class="tysure" @click="toSaveImage">完成</span>
</div>
<div
class="inpContainer"
:style="{
left: moveX < 120 ? 0 : moveX - 120 + 'px',
top: moveY + 'px',
}"
v-if="showTextStatus"
>
<input
type="text"
name=""
class="addTextInp"
placeholder="请输入添加的文字"
v-model="textValue"
/>
<div class="addBtn" @click="drawText(moveX > 120 ? moveX - 60 : moveX, moveY - 128)">添加</div>
</div>
<div class="colorType">
<div class="coloSing">
<span
class="selColor"
v-for="(item, index) in colorList"
:key="index"
:style="{ backgroundColor: item }"
@click="changeColor(item, index)"
:class="{ isChooseColor: isChooseColor == index }"
></span>
<span class="selRevoke" @click="revokeDraw">
<img src="@/assets/images/revoke.png" width="20px" height="20px" />
</span>
<span class="text-icon" @click="showTextContainer">
<img
src="@/assets/images/text-icon.png"
width="20px"
height="20px"
/>
</span>
</div>
</div>
</div>
<!-- 点击完成选择接下的操作 -->
<div class="doodlingModel" v-if="isComplete">
<ul>
<li @click="transmit">发送给朋友</li>
<!-- <li @click="toSaveImage">保存图片</li> -->
</ul>
</div>
<div class="fog" v-if="isComplete" @click="closeModel"></div>
</div>
</template>
<script>
import { fileUpload } from '@/utils';
export default {
name: 'EditImage',
data() {
const data = {
showModal: true,
imageurl: '',
width: 400,
height: 500,
canDraw: true,
url: '',
info: '',
lineColor: 'red',
lineWidth: '2',
lineType: 'rec',
colorList: [
'#FFFFFF',
'#030102',
'#E75D58',
'#F6C543',
'#FF8500',
'#4FABF8',
'#6467E8',
],
// 同一页面多次渲染时,用于区分元素的id
radom: 'myCanvas',
// canvas对象
context: {},
// 是否处于绘制状态
canvasMoveUse: false,
// 绘制矩形和椭圆时用来保存起始点信息
beginRec: {
x: '',
y: '',
imageData: '',
},
moveX: '',
moveY: '',
// 储存坐标信息
drawInfo: [],
// 背景图片缓存
img: new Image(),
canvasHistory: [],
restore: [],
repeat: [],
myCanvas: null,
hub: 0,
showImg: false,
editIamgeUrl: '',
isComplete: false,
msgData: {},
inited: false,
top: 0,
left: 0,
isChooseColor: false,
textValue: '',
showTextStatus: false,
};
return data;
},
props: {
propsImageurl: {
type: String,
default() {
return '';
},
},
},
created() {
const self = this;
let originLink = '';
originLink = this.propsImageurl;
self.url = originLink;
this.msgData = self.$route.query.msg;
self.width = document.documentElement.clientWidth; // 可见区域宽度
self.height = document.documentElement.clientHeight; // 可见区域高度
if (localStorage.getItem('lineColor')) {
self.lineColor = localStorage.getItem('lineColor');
}
},
mounted() {
const self = this;
self.initDraw();
},
methods: {
// 转发消息
transmit() {
const self = this;
if (
self.msgData.type === 'custom' &&
self.msgData.content.type === 'imageurl'
) {
self.msgData.content.imageurl = self.imageurl;
} else if (self.msgData.type === 'image') {
self.msgData.originLink = self.imageurl;
}
self.$router.push({
path: '/forwardmsg',
query: {
sendMsg: self.msgData,
},
});
},
goBack() {
// this.context.clearRect(0, 0, this.width, this.height);
this.$emit('hide-edit');
},
// canvas转base64
toSaveImage() {
this.imageurl = this.myCanvas.toDataURL('image/png');
const blob = this.base64ToBlob(this.imageurl);
const file = this.blobToFile(blob, 'newImg.png');
const type = 'png';
let obj = '';
fileUpload(this, file, type, (url) => {
obj = {
image: url,
content: '',
};
if (obj.image) {
this.$emit('getNewImg', obj.image);
}
});
},
// base64转blob解决ios兼容问题
base64ToBlob(base64Data) {
const arr = base64Data.split(',');
const fileType = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let l = bstr.length;
const u8Arr = new Uint8Array(l);
while (l--) {
u8Arr[l] = bstr.charCodeAt(l);
}
return new Blob([u8Arr], {
type: fileType,
});
},
// blob转file上传
blobToFile(newBlob, fileName) {
const Obj = newBlob;
Obj.lastModifiedDate = new Date();
Obj.name = fileName;
return newBlob;
},
// 撤销
revokeDraw() {
if (this.canvasHistory.length > 1) {
this.context.putImageData(
this.canvasHistory[this.canvasHistory.length - 2],
0,
0,
);
this.canvasHistory.length--;
}
},
// huanse
changeColor(item, index) {
this.lineColor = item;
this.context.strokeStyle = this.lineColor;
this.isChooseColor = index;
localStorage.setItem('lineColor', this.lineColor);
},
// 关闭窗口
closeModule() {
this.showModal = false;
},
// 操作按钮 1 涂鸦 2 编辑文字 3、确定
operation(index) {
if (index === 1) {
this.canDraw = true;
} else if (index === 2) {
this.canDraw = false;
} else if (index === 3) {
this.canDraw = false;
}
this.closeModule();
},
// 初始化绘制信息
initDraw() {
// 初始化画布
this.myCanvas = document.getElementById(this.radom);
this.context = this.myCanvas.getContext('2d');
// 初始化背景图片
this.img.setAttribute('crossOrigin', 'Anonymous');
this.img.src = this.url;
this.img.onerror = () => {
const timeStamp = +new Date();
this.img.src = this.url + '?' + timeStamp;
};
this.img.onload = () => {
this.clean();
};
// 初始化画笔
this.context.lineWidth = this.lineWidth;
this.context.strokeStyle = this.lineColor;
},
// 鼠标按下
canvasDown(e) {
this.showModal = false;
if (this.canDraw) {
this.repeat.length = 0;
this.canvasMoveUse = true;
// client是基于整个页面的坐标,offset是cavas距离pictureDetail顶部以及左边的距离
const canvasX =
event.changedTouches[0].clientX - e.target.parentNode.offsetLeft;
const canvasY =
event.changedTouches[0].clientY - e.target.parentNode.offsetTop;
// 记录起始点和起始状态
this.beginRec.x = canvasX;
this.beginRec.y = canvasY;
this.beginRec.imageData = this.context.getImageData(
0,
0,
this.width,
this.height,
);
// 存储本次绘制坐标信息
this.drawInfo.push({
x: canvasX / this.width,
y: canvasY / this.height,
type: this.lineType,
});
this.moveX = event.changedTouches[0].clientX;
this.moveY = event.changedTouches[0].clientY;
// console.log(this.moveX);
// console.log(this.moveY);
}
},
// 鼠标移动时绘制
canvasMove() {
if (this.canvasMoveUse && this.canDraw) {
this.context.beginPath(); // 划线改变颜色必须用这个
this.context.lineCap = 'round';
this.context.lineJoin = 'round';
this.context.moveTo(this.moveX - this.left, this.moveY - this.top);
this.context.lineTo(
event.changedTouches[0].clientX - this.left,
event.changedTouches[0].clientY - this.top,
);
this.context.closePath();
this.context.stroke();
this.moveX = event.changedTouches[0].clientX;
this.moveY = event.changedTouches[0].clientY;
}
},
showTextContainer() {
this.showTextStatus = true;
},
// 绘制文字
drawText(x, y) {
let handldX = x;
if (x <= 0) {
handldX = 40;
}
if (this.textValue) {
this.showTextStatus = false;
}
this.context.font = 'bold 16px Arial';
this.context.textAlign = 'center';
this.context.textBaseline = 'bottom';
this.context.zIndex = '99';
this.context.fillStyle = '#ccc';
this.context.strokeText(this.textValue, handldX, y);
this.context.closePath();
this.context.stroke();
this.textValue = '';
},
// 鼠标抬起
canvasUp() {
if (this.canDraw) {
this.canvasMoveUse = false;
this.showModal = true;
this.canvasHistory.push(this.context.getImageData(0, 0, this.width, this.height));
}
},
// 保持纵横比缩放图片,使图片的长边能完全显示出来
containImg(sx, sy, boxW, boxH, sourceW, sourceH) {
let dx = sx;
let dy = sy;
let dWidth = boxW;
let dHeight = boxH;
if (sourceW > sourceH || (sourceW === sourceH && boxW < boxH)) {
dHeight = (sourceH * dWidth) / sourceW;
dy = sy + ((boxH - dHeight) / 2);
} else if (sourceW < sourceH || (sourceW === sourceH && boxW > boxH)) {
dWidth = (sourceW * dHeight) / sourceH;
dx = sx + ((boxW - dWidth) / 2);
}
return {
dx,
dy,
dWidth,
dHeight,
};
},
// 清空画布
// img 规定要使用的图像、画布或视频。
// sx 可选。开始剪切的 x 坐标位置。
// sy 可选。开始剪切的 y 坐标位置。
// swidth 可选。被剪切图像的宽度。
// sheight 可选。被剪切图像的高度。
// x 在画布上放置图像的 x 坐标位置。
// y 在画布上放置图像的 y 坐标位置。
// width 可选。把图像绘制到画布上的宽度。(伸展或缩小图像)
// height 可选。把图像绘制到画布上的高度。(伸展或缩小图像)
// ctx.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
clean() {
// 计算宽高比
const self = this;
const imgRect = this.containImg(
0,
0,
this.width,
this.height,
this.img.width,
this.img.height,
);
self.width = imgRect.dWidth;
self.height = imgRect.dHeight;
// 给canvas赋值宽高
self.$refs.canvas.width = self.width;
self.$refs.canvas.height = self.height;
// canvas必须有高
self.$refs.canvas.style.height = self.height;
// 清空canvas画布
self.context.clearRect(0, 0, self.width, self.height);
// 获取水平垂直偏移距离
self.top = (document.documentElement.clientHeight - self.height) / 2;
self.left = (document.documentElement.clientWidth - self.width) / 2;
// this.context.drawImage(this.img, imgRect.dx, imgRect.dy, imgRect.dWidth, imgRect.dHeight);
// 渲染图片
self.context.drawImage(self.img, 0, 0, imgRect.dWidth, imgRect.dHeight);
// this.context.drawImage(this.img, imgRect.dx, imgRect.dy, imgRect.dWidth, imgRect.dHeight);
// 画布垂直居中
self.$refs.canvas.style.marginTop = self.top + 'px';
// 初始化鼠标在画布的位置
self.moveY = self.top;
// 画布水平居中
self.$refs.canvas.style.left = self.left + 'px';
// 修改画笔颜色
for (let i = 0, len = this.colorList.length; i < len; i++) {
if (self.lineColor === self.colorList[i]) {
self.changeColor(self.lineColor, i);
break;
}
}
// 记录第一次canvas图像(防止撤回到最后空白)
self.canvasHistory.push(self.context.getImageData(0, 0, self.width, self.height));
},
// 关闭模态框
closeModel() {
self.isComplete = false;
},
},
};
</script>
<style lang="scss" scoped>
.editImage {
position: fixed;
z-index: 999;
width: 100%;
height: 100vh;
background-color: #fff;
.fog {
position: fixed;
top: 0px;
left: 0px;
background-color: #000;
opacity: 0.5;
width: 100%;
height: 100%;
z-index: 100;
}
.doodlingModel {
width: 85%;
position: absolute;
left: 7.5%;
top: 35%;
background-color: #ffffff;
border-radius: 5px;
z-index: 1000;
}
.doodlingModel > ul {
width: 100%;
}
.doodlingModel > ul > li {
width: 90%;
color: #000000;
font-size: 16px;
height: 50px;
line-height: 50px;
border-bottom: 1px solid #dedede;
margin: 0 auto;
}
.doodlingModel > ul > li:last-of-type {
border-bottom: none;
}
}
.optBtn {
width: 100%;
height: 100vh;
position: relative;
bottom: 0;
left: 0;
display: flex;
justify-content: space-around;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1;
.inpContainer {
position: absolute;
display: flex;
.addTextInp {
width: 120px;
}
.addBtn {
height: 20px;
text-align: center;
line-height: 20px;
font-size: 14px;
border: 2px solid #ccc;
color: rgb(246, 197, 67);
}
}
.colorType {
bottom: 0px;
position: absolute;
padding: 0 1.8125rem;
height: 3.8125rem;
width: 100%;
padding: 0 10px;
background-image: url(../../assets/images/doodlingbg.png);
.huabi {
width: 1rem;
height: 1rem;
margin-top: 1.172rem;
img {
width: 100%;
height: 100%;
}
}
.coloSing {
width: 100%;
display: flex;
justify-content: space-around;
margin-top: 50px;
.selColor {
width: 20px;
height: 20px;
border-radius: 50%;
border: 1px solid #fff;
}
.isChooseColor {
border: 3px solid #fff;
}
}
}
.tuyaTit {
/* width: calc(100% - 1.25rem); */
width: 90%;
display: flex;
justify-content: space-between;
align-items: center;
/* margin: 3rem 0.625rem 0 0.625rem; */
color: #fff;
/* font-size: .5rem; */
font-size: 15px;
font-weight: 500;
position: absolute;
top: 20px;
/* left: 0.625rem; */
left: 5%;
}
.tysure {
padding: 5px 15px;
background-color: #ff8500;
border-radius: 3px;
}
}
</style>