<template>
|
<div class="paint">
|
<canvas id="canvas" :width="canvasWidth" :height="canvasHeight"></canvas>
|
<!-- 操作按钮 -->
|
<ul class="paint-btn">
|
<li class="paint-btn-box">
|
<button @click="changeDrawMode">
|
{{ isDraw ? "框选模式" : "绘图模式" }}
|
</button>
|
<button @click="clearCanvas">清除</button>
|
<button @click="setModelEraser">
|
{{ isEraser ? "画笔" : "橡皮擦" }}
|
</button>
|
<button @click="saveImgData">保存</button>
|
</li>
|
<li></li>
|
<li>
|
<label>画笔:</label>
|
<select name="" id="" @change="changeBrush" v-model="brush">
|
<option
|
v-for="(item, index) in modelList"
|
:value="item.value"
|
:key="index"
|
>
|
{{ item.name }}
|
</option>
|
</select>
|
</li>
|
<li>
|
<label>线色:</label>
|
<input type="color" v-model="lineColor" @input="changeLineColor" />
|
</li>
|
<li>
|
<label>线宽:</label>
|
<input type="range" v-model="lineWidth" @input="changeLineWidth" />
|
</li>
|
<li>
|
<label>阴影色:</label>
|
<input type="color" v-model="showColor" @input="changeShowColor" />
|
</li>
|
<li>
|
<label>阴影宽度:</label>
|
<input type="range" v-model="showWidth" @input="changeShowWidth" />
|
</li>
|
<li>
|
<label>阴影偏移量:</label>
|
<input type="range" v-model="showOffset" @input="changeShowOffset" />
|
</li>
|
</ul>
|
</div>
|
</template>
|
|
<script>
|
import { fabric } from "fabric-with-erasing";
|
export default {
|
data() {
|
return {
|
canvasWidth:400,
|
backgroundImgUrl: "", // 背景
|
isDraw: true, // 绘画、框选模式
|
brush: "Pencil", // 画笔类型
|
lineColor: "#000",
|
lineWidth: 1,
|
isEraser: false,
|
showColor: "#000", // 阴影色
|
showWidth: 0, // 阴影宽度
|
showOffset: 0,
|
modelList: [
|
{
|
name: "Pencil",
|
value: "Pencil",
|
},
|
{
|
name: "Circle",
|
value: "Circle",
|
},
|
{
|
name: "Spray",
|
value: "Spray",
|
},
|
{
|
name: "Pattern",
|
value: "Pattern",
|
},
|
{
|
name: "hline",
|
value: "hline",
|
},
|
{
|
name: "vline",
|
value: "vline",
|
},
|
{
|
name: "square",
|
value: "square",
|
},
|
{
|
name: "diamond",
|
value: "diamond",
|
},
|
{
|
name: "texture",
|
value: "texture",
|
},
|
],
|
// 画笔模式
|
vLinePatternBrush: null,
|
hLinePatternBrush: null,
|
squarePatternBrush: null,
|
diamondPatternBrush: null,
|
texturePatternBrush: null,
|
};
|
},
|
props: {
|
imgUrl: {
|
type: String,
|
default:
|
"https://cdn.learnku.com/uploads/images/202206/29/97252/aArKOJpl2A.png!large",
|
},
|
page: {
|
type: Number,
|
default: 1,
|
},
|
canvasHeight: {
|
type: Number,
|
default: 380,
|
},
|
},
|
mounted() {
|
this.handleCanvasWidth()
|
this.init();
|
},
|
methods: {
|
handleCanvasWidth() {
|
if(window.innerWidth < 450) {
|
this.canvasWidth = window.innerWidth - 30
|
}
|
},
|
// 初始化画布
|
init() {
|
this.canvas = new fabric.Canvas(
|
(this.container ? this.container : document).querySelector("#canvas"),
|
{
|
isDrawingMode: true,
|
}
|
);
|
// 设置背景
|
this.setBackgroundImage();
|
//
|
fabric.Object.prototype.transparentCorners = false;
|
this.setBrush();
|
},
|
// 创建各种笔刷
|
setBrush() {
|
if (fabric.PatternBrush) {
|
// 画笔样式
|
this.vLinePatternBrush = new fabric.PatternBrush(this.canvas);
|
this.vLinePatternBrush.getPatternSrc = () => {
|
let patternCanvas =
|
fabric[this.container ? this.container : document].createElement(
|
"canvas"
|
);
|
patternCanvas.width = patternCanvas.height = 10;
|
let ctx = patternCanvas.getContext("2d");
|
ctx.strokeStyle = this.lineColor;
|
ctx.lineWidth = 5;
|
ctx.beginPath();
|
ctx.moveTo(0, 5);
|
ctx.lineTo(10, 5);
|
ctx.closePath();
|
ctx.stroke();
|
return patternCanvas;
|
};
|
this.hLinePatternBrush = new fabric.PatternBrush(this.canvas);
|
this.hLinePatternBrush.getPatternSrc = function () {
|
let patternCanvas =
|
fabric[this.container ? this.container : document].createElement(
|
"canvas"
|
);
|
patternCanvas.width = patternCanvas.height = 10;
|
let ctx = patternCanvas.getContext("2d");
|
ctx.strokeStyle = this.lineColor;
|
ctx.lineWidth = 5;
|
ctx.beginPath();
|
ctx.moveTo(5, 0);
|
ctx.lineTo(5, 10);
|
ctx.closePath();
|
ctx.stroke();
|
return patternCanvas;
|
};
|
this.squarePatternBrush = new fabric.PatternBrush(this.canvas);
|
this.squarePatternBrush.getPatternSrc = function () {
|
const squareWidth = 10;
|
const squareDistance = 2;
|
const patternCanvas =
|
fabric[this.container ? this.container : document].createElement(
|
"canvas"
|
);
|
patternCanvas.width = patternCanvas.height =
|
squareWidth + squareDistance;
|
const ctx = patternCanvas.getContext("2d");
|
ctx.fillStyle = this.color;
|
ctx.fillRect(0, 0, squareWidth, squareWidth);
|
return patternCanvas;
|
};
|
this.diamondPatternBrush = new fabric.PatternBrush(this.canvas);
|
this.diamondPatternBrush.getPatternSrc = function () {
|
const squareWidth = 10;
|
const squareDistance = 5;
|
const patternCanvas =
|
fabric[this.container ? this.container : document].createElement(
|
"canvas"
|
);
|
const rect = new fabric.Rect({
|
width: squareWidth,
|
height: squareWidth,
|
angle: 45,
|
fill: this.color,
|
});
|
var canvasWidth = rect.getBoundingRect().width;
|
patternCanvas.width = patternCanvas.height =
|
canvasWidth + squareDistance;
|
rect.set({ left: canvasWidth / 2, top: canvasWidth / 2 });
|
var ctx = patternCanvas.getContext("2d");
|
rect.render(ctx);
|
return patternCanvas;
|
};
|
const img = new Image();
|
this.texturePatternBrush = new fabric.PatternBrush(this.canvas);
|
this.texturePatternBrush.source = img;
|
}
|
},
|
// 设置背景图方法
|
setBackgroundImage() {
|
// 使用fabric的Image.fromURL方法来加载图像
|
const oldData = localStorage.getItem(
|
this.config.activeBook.name + "-paint-" + this.page
|
);
|
this.backgroundImgUrl = oldData || this.imgUrl;
|
fabric.Image.fromURL(
|
this.backgroundImgUrl,
|
(img) => {
|
// 图像加载完成后,将其设置为画布的背景
|
img
|
.scale(
|
this.canvas.width / img.width,
|
this.canvas.height / img.height
|
)
|
.set({
|
left: 0,
|
top: 0,
|
originX: "left",
|
originY: "top",
|
});
|
|
// 将图像添加到画布中,并将其放在最底层
|
this.canvas.setBackgroundImage(
|
img,
|
this.canvas.renderAll.bind(this.canvas),
|
{
|
// 可以设置图像的样式,比如不透明度
|
// opacity: 0.5,
|
}
|
);
|
// 渲染画布
|
this.canvas.renderAll();
|
},
|
{
|
crossOrigin: "Anonymous", // 如果图像在不同域上,需要设置crossOrigin
|
}
|
);
|
},
|
// 绘图 框选 模式切换
|
changeDrawMode() {
|
this.isDraw = !this.isDraw;
|
this.canvas.isDrawingMode = !this.canvas.isDrawingMode;
|
},
|
// 清空画布
|
clearCanvas() {
|
this.canvas.clear();
|
this.setBackgroundImage();
|
},
|
// 修改画笔颜色
|
changeLineColor(e) {
|
let brush = this.canvas.freeDrawingBrush;
|
brush.color = e.srcElement.value;
|
if (brush.getPatternSrc) {
|
brush.source = brush.getPatternSrc.call(brush);
|
}
|
},
|
// 修改画笔粗细
|
changeLineWidth(e) {
|
this.canvas.freeDrawingBrush.width =
|
parseInt(e.srcElement.value, 10) || 1;
|
},
|
// 画笔样式切换
|
changeBrush() {
|
if (this.brush == "hline") {
|
this.canvas.freeDrawingBrush = this.vLinePatternBrush;
|
} else if (this.brush == "vline") {
|
this.canvas.freeDrawingBrush = this.hLinePatternBrush;
|
} else if (this.brush == "square") {
|
this.canvas.freeDrawingBrush = this.squarePatternBrush;
|
} else if (this.brush == "diamond") {
|
this.canvas.freeDrawingBrush = this.diamondPatternBrush;
|
} else if (this.brush == "texture") {
|
this.canvas.freeDrawingBrush = this.texturePatternBrush;
|
} else {
|
this.canvas.freeDrawingBrush = new fabric[this.brush + "Brush"](
|
this.canvas
|
);
|
}
|
if (this.canvas.freeDrawingBrush) {
|
var brush = this.canvas.freeDrawingBrush;
|
brush.color = this.lineColor;
|
if (brush.getPatternSrc) {
|
brush.source = brush.getPatternSrc.call(brush);
|
}
|
brush.width = parseInt(this.lineWidth, 10) || 1;
|
brush.shadow = new fabric.Shadow({
|
blur: parseInt(this.showWidth, 10) || 0,
|
offsetX: 0,
|
offsetY: 0,
|
affectStroke: true,
|
color: this.showColor,
|
});
|
}
|
},
|
// 橡皮擦
|
setModelEraser() {
|
this.isEraser = !this.isEraser;
|
if (this.isEraser) {
|
this.canvas.freeDrawingBrush = new fabric.EraserBrush(this.canvas); // 使用橡皮擦画笔
|
this.canvas.freeDrawingBrush.width = this.lineWidth;
|
} else {
|
this.changeBrush();
|
// this.canvas.freeDrawingBrush = new fabric.PencilBrush(this.canvas); // 使用橡皮擦画笔
|
// this.canvas.freeDrawingBrush.width = this.lineWidth; // 设置画笔粗细
|
}
|
},
|
// 修改阴影色
|
changeShowColor(e) {
|
this.canvas.contextContainer.shadowColor = e.srcElement.value;
|
},
|
// 阴影宽度
|
changeShowWidth(e) {
|
console.log(this.canvas);
|
this.canvas.contextContainer.shadowBlur =
|
parseInt(e.srcElement.value, 10) || 0;
|
},
|
// 阴影偏移量
|
changeShowOffset(e) {
|
this.canvas.contextContainer.shadowOffsetX =
|
parseInt(e.srcElement.value, 10) || 0;
|
this.canvas.contextContainer.shadowOffsetY =
|
parseInt(e.srcElement.value, 10) || 0;
|
},
|
// 保存图片
|
saveImgData() {
|
const imgData = this.canvas.toDataURL({
|
format: "png", // 指定输出格式,通常是'png'或'jpeg'
|
quality: 0.8, // 仅在输出格式为'jpeg'时有效
|
multiplier: 1, // 提高分辨率,可选参数,默认为1
|
left: 0, // 裁剪区域的左边界(可选)
|
top: 0, // 裁剪区域的上边界(可选)
|
width: canvas.width, // 裁剪区域的宽度(可选,默认为画布的宽度)
|
height: canvas.height, // 裁剪区域的高度(可选,默认为画布的高度)
|
});
|
localStorage.setItem(
|
this.config.activeBook.name + "-paint-" + this.page,
|
imgData
|
);
|
console.log("本地图", imgData);
|
},
|
},
|
};
|
</script>
|
|
<style lang="less" scoped>
|
.paint {
|
margin-top: 20px;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
}
|
#canvas {
|
border: 1px solid #ccc;
|
}
|
.paint-btn {
|
width: 96%;
|
margin-top: 40px;
|
padding: 20px;
|
border: 1px solid #ededed;
|
li {
|
margin-bottom: 6px;
|
}
|
}
|
.paint-btn-box {
|
display: flex;
|
justify-content: space-evenly;
|
}
|
</style>
|