最近接到个需求,需要在图片上选择一块区域后,给里面填充颜色,这个就类似于ps里面的钢笔工具,可以参考下www.uupoop.com/ 里的钢笔工具
选择使用fabric.js来实现需求 ,对fabric.js不了解的同学可以看看这个中文文档github.com/Rookie-Bird…
实现步骤
1.实现添加点和路径,连接成图形
监听鼠标点击事件,这里的点使用circle对象实现,线和后面的图使用path路径对象来实现,这两个对象的基本介绍可参看github.com/Rookie-Bird…
var path, str; // path是绘制的对象,str是其路径
var pointPosition = [] // 点的位置坐标信息
var pointArray = [] // 点的对象组合
canvas.on('mouse:down', function (e) {
// addPoint添加点
addPoint(e)
})
// 增加点,和路径,开始绘画
function addPoint(e) {
var id = new Date().getTime() + Math.random() // id用来识别每个点
var circle = new fabric.Circle({
radius: 3,
fill: '#1a80ff',
left: e.absolutePointer.x,
top: e.absolutePointer.y,
selectable: false,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
id: id
})
canvas.add(circle)
pointPosition.push({
x: e.absolutePointer.x,
y: e.absolutePointer.y
})
// pointPosition.length == 1 说明是第一个点
// 这里用fabric.js里的path对象来绘制我们需要的图形对象
// 此时往路径里面添加'M','M'的意思是开始绘画,后面的两个数字是开始点的x,y的坐标
if (pointPosition.length == 1) {
str = 'M' + pointPosition[0].x + ' ' + pointPosition[0].y
path = new fabric.Path(str)
path.set({
fill: 'red',
// opacity: 0.5,
selectable: false,
hasBorders: false,
hasControls: false,
evented: false
})
canvas.add(path);
pointArray.push(circle)
}
// 增加第二个点时再执行下面的逻辑,这里开始绘制直线
if (pointPosition.length <= 1) return
var length = pointPosition.length - 1
canvas.remove(path) // 先移除之前的对象
// L在path里面就是线段的意思
str = str + 'L' + pointPosition[length].x + ' ' + pointPosition[length].y // 路径拼接
path = new fabric.Path(str)
path.set({
fill: '#7c4529',
// opacity: 0.5,
strokeWidth: 1.5,
stroke: '#1a80ff',
selectable: true,
hasBorders: false,
hasControls: false,
evented: false
})
canvas.add(path); // 重新添加
canvas.renderAll()
pointArray.push(circle)
}
效果 现在我们将这个图形封闭一下,就是当最后一个点与起始点重合的时候就说明图形已经画完了
canvas.on('mouse:down', function (e) {
if(e.target && pointArray[0] && e.target.id == pointArray[0].id) { // 目标点和第一个点id相等时,说明绘画动作已经完成,此时开始最后一步绘画
finishDrawing(e)
} else {
// canvas.getActiveObject() 为true说明这时候你选中了其它对象,这个时候不进行绘画
if (canvas.getActiveObject()) return
addPoint(e)
}
});
function finishDrawing(e) {
canvas.remove(path)
// 最后一个点的坐标就是第一个点的坐标
pointPosition.push({
x: pointPosition[0].x,
y: pointPosition[0].y
})
// 给路径加'z','z'说明整个path路径已经完结
str = str + 'z'
path = new fabric.Path(str)
path.set({
fill: '#7c4529',
// opacity: 0.5,
strokeWidth: 1.5,
stroke: '#1a80ff',
// selectable: true,
// hasBorders: true,
// hasControls: true,
// evented: true
})
canvas.add(path);
canvas.renderAll()
}
图形封闭完之后,再点击的话就是开始下一次绘图了,这里再次点击的话,我们将之前保存的点的坐标和路径全部清除,对鼠标按下事件在做些处理
canvas.on('mouse:down', function (e) {
// 如果str包含z,也就是说路径里面有z,说明图形已经绘画完毕了,将之前的所有点和点的位置信息全部清空
if (str && str.indexOf('z') !== -1) {
removePoint()
addPoint(e)
} else if (e.target && pointArray[0] && e.target.id == pointArray[0]
.id) { // 目标点和第一个点id相等时,说明绘画动作已经完成,此时开始最后一步绘画
finishDrawing(e)
} else {
if (canvas.getActiveObject()) return
addPoint(e)
}
});
// 移除点
function removePoint() {
for (let item of pointArray) {
canvas.remove(item)
}
str = ''
pointArray = []
pointPosition = []
path.set({
stroke: null, // 路径完成后讲path的边框去掉
})
canvas.renderAll()
}
效果
2.添加鼠标按下移动时,增加跟随鼠标转动的点和线,这也是为了完成别赛尔曲线的绘制
仿照UUPOOP的实现
首先,在鼠标按下时,监听鼠标移动事件(这里要注意,鼠标弹起时,一定得取消鼠标移动的监听事件,否则会造成重复监听多个鼠标移动事件,造成页面卡顿)
canvas.on('mouse:down', function (e) {
canvas.on('mouse:move', startDrawing);
});
canvas.on('mouse:up', function (e) {
// 鼠标弹起时,取消监听移动事件
canvas.off('mouse:move', startDrawing)
});
// 开始绘制跟随鼠标移动的点和线,具体请参看完整代码
var startDrawing = function (e) {
dragMousePoint(e)
}
3.绘制贝赛尔曲线的绘制
这里我们使用path自带的Q,也就是二阶贝塞尔曲线
可以看下这个动态的二阶贝塞尔曲线事例blogs.sitepointstatic.com/examples/te…
它需要三个点,一个是起始点'M' + pointPosition[length - 1].x + ' ' + pointPosition[length - 1].y
一个是它的控制点 x,y,还有一个就是结束点
这里我们新建一个名为besairObj的path对象来展示贝塞尔曲线的变化
// 跟随鼠标改动的贝塞尔曲线
function BeSaiEr(e, length) {
// x,y是跟随鼠标移动的线段2的末尾点的坐标
// pointPosition[length].x 鼠标移动事件的起始点
besair = 'M' + pointPosition[length - 1].x + ' ' + pointPosition[length - 1].y + ' Q ' + x + ' ' + y + ' ' + pointPosition[length ].x + ' ' + pointPosition[length ].y
// besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length-1].x + ' ' + pointPosition[length-1].y
// besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length].x + ' ' + pointPosition[length].y
if (besairObj) {
canvas.remove(besairObj)
}
besairObj = new fabric.Path(besair)
besairObj.set({
fill: '#7c4529',
// opacity: 0.5,
strokeWidth: 1.5,
stroke: '#1a80ff',
selectable: true,
hasBorders: false,
hasControls: false,
evented: false
})
canvas.add(besairObj)
// canvas.add(path);
besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length].x + ' ' + pointPosition[length].y
}
这里修改一下鼠标弹起事件,拼接路径
canvas.on('mouse:up', function (e) {
if (besair) {
// 在移动事件中,每次都将最后一个L的数据排除掉,避免bug
if (str.lastIndexOf('L') !== -1) {
str = str.substring(0, str.lastIndexOf('L'))
}
// 如果有z这说明本次绘图已经结束,不去掉z会造成图形失真,有bug
if (str.indexOf('z') !== -1) {
str = str.substring(0, str.indexOf('z'))
besair = besair + 'z'
}
// 鼠标弹起时,如果besair有值,则与str合并,并将其置为空字符串
str = str + besair
console.log(str)
besair = ''
}
// 鼠标弹起时,取消监听移动事件
canvas.off('mouse:move', startDrawing)
});
完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style>
* {
padding: 0;
margin: 0;
}
.backgrounds {
display: flex;
}
.backgrounds img {
width: 200px;
height: 200px;
}
#CanvasContainer {
width: 270px;
height: 519px;
margin-left: 15px;
}
#Canvas {
overflow: hidden;
}
.tag {
position: absolute;
z-index: 15;
padding: 0 5px;
min-width: 48px;
height: 16px;
line-height: 16px;
text-align: center;
font-size: 12px;
color: #505050;
border: 1px solid #fff;
background: hsla(0, 0%, 86.3%, .8);
border-radius: 10px;
-webkit-border-radius: 10px;
display: none;
}
.pointer {
position: absolute;
width: 10px;
height: 10px;
border: 1px solid rgb(148, 148, 226);
}
</style>
</head>
<body>
<div id="Backgrounds" class="backgrounds">
<img src="./鞋1.jpg" alt="" id="img1" />
<img src="./鞋2.png" alt="" id="img2" />
<img src="./鞋3.jpg" alt="" id="img3" />
</div>
<div class="container" style="position: relative;">
<div id="CanvasContainer" style="width: 800px;height: 800px;border: 1px solid #ccc;">
<canvas id="Canvas" width="800" height="800"></canvas>
</div>
<div class="tag" id="tag">3</div>
<img src="./DIY配色封面图-男.jpg" id="deleteBtn"
style="position:absolute;top: 0px;left: 0px;cursor:pointer;width:20px;height:20px;display: none;" />
</div>
<button onclick="downloadFabric(canvas, new Date().getTime())">导出</button>
<button onclick="unload()">离开</button>
<button onclick="cancelDraw()">手绘模式</button>
<button onclick="fillColor()">填充颜色</button>
<button onclick="huatu()">绘画</button>
<script src="https://cdn.bootcdn.net/ajax/libs/fabric.js/4.2.0/fabric.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
var canvas = new fabric.Canvas('Canvas');
$(document).ready(function () {
$("#Backgrounds img").click(function () {
var getId = $(this).attr("id");
var imgElement = document.getElementById(getId);
var imgInstance = new fabric.Image(imgElement, {
left: 0,
top: 0
});
imgInstance.set({
scaleX: 0.3,
scaleY: 0.3,
transparentCorners: false,
cornerColor: 'black',
cornerStrokeColor: 'black',
borderColor: '#686666',
cornerSize: 12,
padding: 10,
cornerStyle: 'circle',
borderDashArray: [3, 3],
tr: './aaaaa.jpg'
});
canvas.add(imgInstance);
});
});
// 设置画布背景
fabric.Image.fromURL('./aaaaa.jpg', (img) => {
img.set({
// 通过scale来设置图片大小,这里设置和画布一样大
scaleX: canvas.width / img.width,
scaleY: canvas.height / img.height,
});
// 设置背景
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
canvas.renderAll();
});
// 导出下载图片
function download(url, name) {
$('<a>').attr({
href: url,
download: name
})[0].click();
}
function downloadFabric(canvas, name) {
// 导出合并后的图片
download(canvas.toDataURL(), name + '.png');
// 导出单独的图片
// download(canvas._objects[0].toDataURL(),name+'.png');
}
var tag = document.getElementById('tag');
var zoom, zoomPoint;
canvas.on({
// 鼠标滚动缩放
"mouse:wheel": (e) => {
zoom = (event.deltaY > 0 ? -0.1 : 0.1) + canvas.getZoom();
zoom = Math.max(0.1, zoom); //最小为原来的1/10
zoom = Math.min(3, zoom); //最大是原来的3倍
// zoomPoint = new fabric.Point(e.pointer.x, e.pointer.y);
zoomPoint = new fabric.Point(400, 400); // 中心点
canvas.zoomToPoint(zoomPoint, zoom);
},
// 鼠标旋转
"object:rotating": (e) => {
tag.style.display = 'block';
var offsetX = e.e.offsetX;
var offsetY = e.e.offsetY;
tag.style.left = offsetX + 30 + 'px'; // 离鼠标太近,可能会出现抖动,闪现
tag.style.top = offsetY + 30 + 'px';
},
"object:rotated": (e) => {
tag.style.display = 'none';
},
})
// 离开页面,保存当前的画布信息
function unload() {
// 导出当前画布信息
const currState = canvas.toJSON();
console.log(currState);
sessionStorage.setItem('img', JSON.stringify(currState));
sessionStorage.setItem('zoomObj', JSON.stringify({
zm: zoom,
zmpoint: zoomPoint
}));
}
let draw = false
function cancelDraw() {
draw = !draw
canvas.isDrawingMode = draw
}
function huatu() {
drawing = !drawing
if (path) {
removePoint()
}
}
function fillColor() {
var e = canvas.getActiveObject()
console.log(e)
fabric.util.loadImage('./DIY配色封面图-男.jpg', function (img) {
e.set('fill', new fabric.Pattern({
source: img,
opacity: 1
}));
canvas.renderAll();
});
e.bringForward()
// e.setBackgroundImage = './鞋3.jpg'
canvas.renderAll()
}
var selectd = null
// 刷新,恢复之前的画布信息
var sessionImg = sessionStorage.getItem('img');
var lastState = sessionImg ? JSON.parse(sessionImg) : '';
var zoomObj = sessionStorage.getItem('zoomObj') ? JSON.parse(sessionStorage.getItem('zoomObj')) : '';
// 加载画布信息
canvas.loadFromJSON(lastState, () => {
// 设置缩放点
zoomPoint = zoomObj.zmpoint;
zoom = zoomObj.zm;
canvas.zoomToPoint({
x: zoomPoint.x,
y: zoomPoint.y
}, zoom);
// 给每一个图层设置边框圆角样式 刷新后重绘,需要重新设置之前的一些样式
var objects = canvas._objects;
if (objects.length > 0) {
objects.map(item => {
item.set({
transparentCorners: false,
cornerColor: 'black',
cornerStrokeColor: 'black',
borderColor: '#686666',
cornerSize: 12,
padding: 10,
cornerStyle: 'circle',
borderDashArray: [3, 3],
})
})
}
// 重绘
canvas.renderAll();
});
// 删除某个图层
var deleteBtn = document.getElementById('deleteBtn');
function addDeleteBtn(x, y) {
console.log(x, y)
deleteBtn.style.display = 'none';
deleteBtn.style.left = x + 'px';
deleteBtn.style.top = y + 'px';
deleteBtn.style.display = 'block';
}
var path, str; // path是绘制的对象,str是其路径
var pointPosition = [] // 点的位置坐标信息
var pointArray = [] // 点的对象组合
var drawing = false // 控制是否开始自由绘画
var mouseMoving = false // 用于判断鼠标是否在移动
var isMoving = false // 用于判断是否进入移动鼠标事件
var mousePoint1; // 鼠标拖动的点1
var mousePoint2; // 鼠标拖动的点2
var mouseLine1; // 鼠标拖动的线1
var mouseLine2; // 鼠标拖动的线2
var besair = '' // 贝塞尔曲线的坐标
// x , y 是线段1的镜象线的坐标值,也就是线段2的末尾值
var x;
var y;
var besairObj; // 贝塞尔曲线的对象
// 增加点,和路径,开始绘画
function addPoint(e) {
var id = new Date().getTime() + Math.random()
var circle = new fabric.Circle({
radius: 3,
fill: '#1a80ff',
left: e.absolutePointer.x,
top: e.absolutePointer.y,
selectable: false,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
id: id
})
canvas.add(circle)
pointPosition.push({
x: e.absolutePointer.x,
y: e.absolutePointer.y
})
if (pointPosition.length == 1) {
str = 'M' + pointPosition[0].x + ' ' + pointPosition[0].y
path = new fabric.Path(str)
path.set({
fill: 'red',
// opacity: 0.5,
selectable: false,
hasBorders: false,
hasControls: false,
evented: false
})
canvas.add(path);
pointArray.push(circle)
}
pointArray.push(circle)
// 增加第二个点时再执行下面的逻辑
if (pointPosition.length <= 1) return
var length = pointPosition.length - 1
canvas.remove(path)
// str = str + 'L' + pointPosition[length].x + ' ' + pointPosition[length].y + 'z'
str = str + 'L' + pointPosition[length].x + ' ' + pointPosition[length].y
path = new fabric.Path(str)
path.set({
fill: '#7c4529',
// opacity: 0.5,
strokeWidth: 1.5,
stroke: '#1a80ff',
selectable: true,
hasBorders: false,
hasControls: false,
evented: false
})
canvas.add(path);
canvas.renderAll()
}
// 整个图形成型,包括圆点
function finishDrawing(e) {
canvas.remove(path)
// 最后一个点的坐标就是第一个点的坐标
pointPosition.push({
x: pointPosition[0].x,
y: pointPosition[0].y
})
str = str + 'z'
path = new fabric.Path(str)
path.set({
fill: '#7c4529',
// opacity: 0.5,
strokeWidth: 1.5,
stroke: '#1a80ff',
// selectable: true,
// hasBorders: true,
// hasControls: true,
// evented: true
})
canvas.add(path);
canvas.renderAll()
}
// 移除点
function removePoint() {
for (let item of pointArray) {
canvas.remove(item)
}
console.log(222222)
// 结束绘画时,如果贝塞尔曲线的对象存在,则将其移除,并置空
if (besairObj) {
canvas.remove(besairObj)
besairObj = null
if (path) canvas.remove(path)
path = new fabric.Path(str)
path.set({
fill: '#7c4529',
strokeWidth: 1.5,
stroke: '#1a80ff',
})
canvas.add(path);
}
str = ''
pointArray = []
pointPosition = []
path.set({
stroke: null,
})
canvas.renderAll()
}
// 跟随鼠标拖动的点和线
function dragMousePoint(e) {
var length = pointPosition.length - 1
mouseMoveLine1(e, length)
mouseMoveLine2(e, length)
mouseMovePoint(e)
BeSaiEr(e, length)
// throttle(BeSaiEr(e, length),1000)
canvas.renderAll()
}
// 跟随鼠标走动的两个点
function mouseMovePoint(e) {
// var x2 = (y / Math.sqrt(Math.pow(x,2) + Math.pow(y,2)) ) * 1.5
// var y2 = (x / Math.sqrt(Math.pow(x,2) + Math.pow(y,2)) ) * 1.5
if (!mousePoint1) {
mousePoint1 = new fabric.Circle({
radius: 4,
fill: 'white',
left: e.absolutePointer.x,
top: e.absolutePointer.y,
selectable: false,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
strokeWidth: 0.8,
stroke: '#1a80ff',
// id: id
})
mousePoint2 = new fabric.Circle({
radius: 4,
fill: 'white',
left: x,
top: y,
selectable: false,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
strokeWidth: 0.8,
stroke: '#1a80ff',
// id: id
})
canvas.add(mousePoint2)
canvas.add(mousePoint1)
}
mousePoint1.set({
left: e.absolutePointer.x,
top: e.absolutePointer.y
})
mousePoint2.set({
left: x,
top: y
})
}
// 跟随鼠标走动的线1
function mouseMoveLine1(e, length) {
if (mouseLine1) canvas.remove(mouseLine1)
canvas.selection = false
// new fabric.Line(x1,y1,x2,y2) x1,y1是起始点,x2,y2是结束点
mouseLine1 = new fabric.Line([pointPosition[length].x, pointPosition[length].y,
e.absolutePointer.x, e.absolutePointer.y
], {
strokeWidth: 2,
fill: '#999999',
stroke: '#999999',
class: 'line',
originX: 'center',
originY: 'center',
selectable: false,
hasBorders: false,
hasControls: false,
evented: false
});
canvas.add(mouseLine1)
}
// 跟随鼠标走动的线2
function mouseMoveLine2(e, length) {
// 鼠标移动点,也是线段的结束点
var moveX = e.absolutePointer.x
var moveY = e.absolutePointer.y
// 线段的起始点
var originX = pointPosition[length].x
var originY = pointPosition[length].y
if (mouseLine2) canvas.remove(mouseLine2)
// x , y 是线段1的镜象线的坐标值,也就是线段2的末尾值 至于为什么x的值为x = 2 * originX - moveX,画个坐标出来就清楚了
x = 2 * originX - moveX
y = 2 * originY - moveY
canvas.selection = false
mouseLine2 = new fabric.Line([originX, originY,
x, y
], {
strokeWidth: 2,
fill: '#999999',
stroke: '#999999',
class: 'line',
originX: 'center',
originY: 'center',
selectable: false,
hasBorders: false,
hasControls: false,
evented: false
});
canvas.add(mouseLine2)
}
// 跟随鼠标改动的贝塞尔曲线
function BeSaiEr(e, length) {
// pointPosition[length].x 鼠标移动事件的起始点
besair = 'M' + pointPosition[length - 1].x + ' ' + pointPosition[length - 1].y + ' Q ' + x + ' ' + y + ' ' + pointPosition[length ].x + ' ' + pointPosition[
length ].y
// besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length-1].x + ' ' + pointPosition[length-1].y
// besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length].x + ' ' + pointPosition[length].y
if (besairObj) {
canvas.remove(besairObj)
}
besairObj = new fabric.Path(besair)
besairObj.set({
fill: '#7c4529',
// opacity: 0.5,
strokeWidth: 1.5,
stroke: '#1a80ff',
selectable: true,
hasBorders: false,
hasControls: false,
evented: false
})
canvas.add(besairObj)
besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length].x + ' ' + pointPosition[length].y
}
canvas.on('selection:created', function (e) {
selectd = e
console.log(canvas.getZoom())
// 第一次触发了,但是不变
// setTimeout(()=>{addDeleteBtn(e.target.oCoords.tr.x , e.target.oCoords.tr.y )},0);
// addDeleteBtn(e.target.lineCoords.tr.x, e.target.lineCoords.tr.y);
});
canvas.on('selection:updated', function (e) {
// console.log(e);
// addDeleteBtn(e.target.lineCoords.tr.x, e.target.lineCoords.tr.y);
});
canvas.on('mouse:down', function (e) {
if (!canvas.getActiveObject()) {
deleteBtn.style.display = 'none';
}
// 如果str包含z,也就是说路径里面有z,说明图形已经绘画完毕了,将之前的所有点和点的位置信息全部清空
if (str && str.indexOf('z') !== -1) {
removePoint()
addPoint(e)
} else if (e.target && pointArray[0] && e.target.id == pointArray[0].id) { // 目标点和第一个点id相等时,说明绘画动作已经完成,此时开始最后一步绘画
finishDrawing(e)
} else if (drawing) {
if (canvas.getActiveObject()) return
addPoint(e)
}
// 只有开启绘画,才进行鼠标拖动
if (!drawing) return
canvas.on('mouse:move', startDrawing);
});
var startDrawing = function (e) {
dragMousePoint(e)
}
canvas.on('mouse:up', function (e) {
canvas.selection = true
if (besair) {
// 在移动事件中,每次都将最后一个L的数据排除掉,避免bug
if (str.lastIndexOf('L') !== -1) {
str = str.substring(0, str.lastIndexOf('L'))
}
// 如果有z这说明本次绘图已经结束,不去掉z会造成图形失真,有bug
if (str.indexOf('z') !== -1) {
str = str.substring(0, str.indexOf('z'))
besair = besair + 'z'
}
// 鼠标弹起时,如果besair有值,则与str合并,并将其置为空字符串
str = str + besair
console.log(str)
besair = ''
}
// 鼠标弹起时,取消监听移动事件
canvas.off('mouse:move', startDrawing)
});
canvas.on('object:modified', function (e) {
// addDeleteBtn(e.target.oCoords.tr.x, e.target.oCoords.tr.y);
});
canvas.on('object:scaling', function (e) {
// $(".deleteBtn").remove();
deleteBtn.style.display = 'none';
});
canvas.on('object:moving', function (e) {
console.log(e.target.aCoords);
// $(".deleteBtn").remove();
deleteBtn.style.display = 'none';
});
canvas.on('object:rotating', function (e) {
// $(".deleteBtn").remove();
deleteBtn.style.display = 'none';
// addDeleteBtn(e.target.oCoords.tr.x, e.target.oCoords.tr.y);
});
canvas.on('mouse:wheel', function (e) {
console.log(e)
console.log(canvas.getZoom())
deleteBtn.style.display = 'none';
// addDeleteBtn(e.target.oCoords.tr.x, e.target.oCoords.tr.y);
})
$(document).on('click', "#deleteBtn", function () {
if (canvas.getActiveObject()) {
canvas.remove(canvas.getActiveObject());
// $(".deleteBtn").remove();
deleteBtn.style.display = 'none';
}
});
</script>
</body>
</html>
说一下这个代码怎么跑起来:
1.启动http-server服务器,否则会报图片跨域的错误
2.将代码中的图片地址换成你自己的
3.点击绘画按钮,即可开始绘画,进入到钢笔工具