画刷相关-实现
- 实现一个画布功能-可以擦除,绘制,保存画布
- 实现可以自由绘画的画布
案例
<style>
button.active {
background: limegreen;
font-weight: bold
}
</style>
</head>
<body>
<button id="select" type="button" onclick="changeAction(this)">select</button>
<button id="erase" type="button" onclick="changeAction(this)">erase</button>
<button id="undo" type="button" onclick="changeAction(this)">undo erasing</button>
<button id="draw" type="button" onclick="changeAction(this)">draw</button>
<button id="spray" type="button" onclick="changeAction(this)">spray</button>
</div>
<div>
<div>
<label for="a">
background image <code>erasable</code>
</label>
<input id="a" type="checkbox" onchange="setBgImageErasableProp(this)">
</div>
<div>
<label for="b">
remove erased objects on <code>erasing:end</code>
</label>
<input id="b" type="checkbox" onchange="setErasingRemovesErasedObjects(this)">
</div>
</div>
<div>
<button type="button" onclick="toJSON()">toJSON</button>
<button type="button" onclick="downloadImage()">to Image</button>
<button type="button" onclick="downloadSVG()">toSVG</button>
</div>
<div style="display:flex;flex-direction:row;">
<div>
<canvas id="c" width="500" height="620"></canvas>
</div>
<div style="margin:0 1rem;">
<code>erasing:end</code><br>
<code id="output">N/A</code>
</div>
</div>
<!-- <script src="https://unpkg.com/fabric@4.6.0/dist/fabric.min.js"></script> -->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/6.0.0-rc.1/fabric.js"></script> -->
<script src="./fabric (1).js"></script>
<script>
let erasingRemovesErasedObjects = false;
function changeAction(target) {
['select','erase','undo','draw','spray'].forEach(action => {
const t = document.getElementById(action);
t.classList.remove('active');
});
if(typeof target==='string') target = document.getElementById(target);
target.classList.add('active');
switch (target.id) {
case "select":
canvas.isDrawingMode = false;
break;
case "erase":
console.log(fabric.EraserBrush);
canvas.freeDrawingBrush = new fabric.EraserBrush(canvas);
canvas.freeDrawingBrush.width = 10;
canvas.isDrawingMode = true;
break;
case "undo":
canvas.freeDrawingBrush = new fabric.EraserBrush(canvas);
canvas.freeDrawingBrush.width = 10;
canvas.freeDrawingBrush.inverted = true;
canvas.isDrawingMode = true;
break;
case "draw":
canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);
canvas.freeDrawingBrush.width = 35;
canvas.isDrawingMode = true;
break;
case "spray":
canvas.freeDrawingBrush = new fabric.SprayBrush(canvas);
canvas.freeDrawingBrush.width = 35;
canvas.isDrawingMode = true;
break;
default:
break;
}
}
function init() {
canvas.setOverlayColor("rgba(0,0,255,0.4)",undefined,{erasable:false});
const t = new fabric.Triangle({
top: 300,
left: 210,
width: 100,
height: 100,
fill: "blue",
erasable: false
});
canvas.add(
new fabric.Rect({
top: 50,
left: 100,
width: 50,
height: 50,
fill: "#f55",
opacity: 0.8
}),
new fabric.Rect({
top: 50,
left: 150,
width: 50,
height: 50,
fill: "#f55",
opacity: 0.8
}),
new fabric.Group([
t,
new fabric.Circle({ top: 140, left: 230, radius: 75, fill: "green" })
], { erasable: 'deep' })
);
fabric.Image.fromURL('https://ts4.cn.mm.bing.net/th?id=OIP-C.kB-Ovasi0GW67-rmwnAcwAHaEo&w=316&h=197&c=8&rs=1&qlt=90&o=6&dpr=1.3&pid=3.1&rm=2',
function (img) {
// img.set("erasable", false);
img.scaleToWidth(480);
img.clone((img) => {
canvas.add(
img
.set({
left: 400,
top: 350,
clipPath: new fabric.Circle({
radius: 200,
originX: "center",
originY: "center"
}),
angle: 30
})
.scale(0.25)
);
canvas.renderAll();
});
img.set({ opacity: 0.7 });
function animate() {
img.animate("opacity", img.get("opacity") === 0.7 ? 0.4 : 0.7, {
duration: 1000,
onChange: canvas.renderAll.bind(canvas),
onComplete: animate
});
}
animate();
canvas.setBackgroundImage(img);
img.set({ erasable:false });
canvas.on("erasing:end", ({ targets, drawables }) => {
var output = document.getElementById("output");
output.innerHTML = JSON.stringify({
objects: targets.map((t) => t.type),
drawables: Object.keys(drawables)
}, null, '\t');
if(erasingRemovesErasedObjects) {
targets.forEach(obj => obj.group?.removeWithUpdate(obj) || canvas.remove(obj));
}
})
canvas.renderAll();
},
{ crossOrigin: "anonymous" }
);
function animate() {
try {
canvas
.item(0)
.animate("top", canvas.item(0).get("top") === 500 ? "100" : "500", {
duration: 1000,
onChange: canvas.renderAll.bind(canvas),
onComplete: animate
});
} catch (error) {
setTimeout(animate, 500);
}
}
animate();
}
const setDrawableErasableProp = (drawable, value) => {
canvas.get(drawable)?.set({ erasable: value });
changeAction('erase');
};
const setBgImageErasableProp = (input) =>
setDrawableErasableProp("backgroundImage", input.checked);
const setErasingRemovesErasedObjects = (input) =>
(erasingRemovesErasedObjects = input.checked);
const downloadImage = () => {
const ext = "png";
const base64 = canvas.toDataURL({
format: ext,
enableRetinaScaling: true
});
const link = document.createElement("a");
link.href = base64;
link.download = `eraser_example.${ext}`;
link.click();
};
const downloadSVG = () => {
const svg = canvas.toSVG();
const a = document.createElement("a");
const blob = new Blob([svg], { type: "image/svg+xml" });
const blobURL = URL.createObjectURL(blob);
a.href = blobURL;
a.download = "eraser_example.svg";
a.click();
URL.revokeObjectURL(blobURL);
};
const toJSON = async () => {
const json = canvas.toDatalessJSON(["clipPath", "eraser"]);
const out = JSON.stringify(json, null, "\t");
const blob = new Blob([out], { type: "text/plain" });
const clipboardItemData = { [blob.type]: blob };
try {
navigator.clipboard &&
(await navigator.clipboard.write([
new ClipboardItem(clipboardItemData)
]));
} catch (error) {
console.log(error);
}
const blobURL = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = blobURL;
a.download = "eraser_example.json";
a.click();
URL.revokeObjectURL(blobURL);
};
const canvas = this.__canvas = new fabric.Canvas('c');
init();
changeAction('erase');
</script>
</body>
问题
?. 在vscode中总是会自动的加空格
在setting.json中添加
"%PDF%",
],
"editor.fontLigatures": false,
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.semi": false
},
"files.autoSave": "afterDelay",
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"vetur.format.options.tabSize": 4,
"vetur.format.defaultFormatter.js": "vscode-typescript",
"vetur.format.defaultFormatterOptions": {
"js-beautify-html": {
"wrap_attributes": "force-expand-multiline"
},
"prettyhtml": {
"printWidth": 100,
"singleQuote": false,
"wrapAttributes": false,
"sortAttributes": false
}
},
"breadcrumbs.showFunctions": false,
"outline.showConstructors": false,
"editor.formatOnType": false,
"editor.formatOnSave": false,
"editor.insertSpaces": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.codeActionsOnSave.rules": null,
或者
fabric.EraserBrush显示未undefined
其并不是fabric默认构建的部分,需要重新下载
但是如果只下载
这几个单独的部分,再去引用,会报错。
因此直接将所有的都下载下来,一块引用
不要选中下面的,不认会少了相关的函数
笔记
isDrawingMode
开启绘画模式,在普通的框选模式下是不支持绘画的
const canvas = new fabric.Canvas('c', {
isDrawingMode: true // 开启绘画模式
})
如果在初始化的时候不开启,也可以在后面使用`canvas.isDrawingMode = true* 的方式来设置
canvas.freeDrawingBrush
可以通过修改width来调节画刷的宽度
canvas.freeDrawingBrush.width = 20 // 画笔宽度
设置颜色
canvas.freeDrawingBrush.color = 'pink' // 画笔颜色
设置虚线,默认是实线进行绘画
canvas.freeDrawingBrush.strokeDashArray = [20, 50] // 行程破折号数组
设置画刷绘制的投影
// 画笔投影
canvas.freeDrawingBrush.shadow = new fabric.Shadow({
blur: 10, // 羽化程度
offsetX: 10, // x轴偏移量
offsetY: 10, // y轴偏移量
color: '#30e3ca' // 投影颜色
})
自由绘制的时候不能超出画布区域
canvas.freeDrawingBrush.limitedToCanvasSize = true // 当“ true”时,自由绘制被限制为画布大小。
canvas.setOverlayColor和overlayColor
overlayColor
在初始化画布的时候使用
用于设置覆盖色或者前景色
使用这个属性可以在画布顶层覆盖一层颜色,可以覆盖画布内所有的背景和元素
let canvas = new fabric.Canvas('canvasBox', {
overlayColor: 'pink', // 覆盖颜色
backgroundColor: 'red' // 背景色
})
显示的是pink色
bringToFront()方法可以将元素的层级放到最顶层,但依然是不能越过overlayColor的
let canvas = new fabric.Canvas('canvasBox', {
overlayColor: 'pink' // 覆盖颜色
})
let rect = new fabric.Rect({
width: 80,
height: 80,
})
rect.bringToFront()
canvas.add(rect)
可以通过定时器,在一定的时间后,将最顶层的覆盖色取消
// 1秒后移除覆盖层
setTimeout(() => {
canvas.overlayColor = null //移除覆盖色,使用transparent或者空的字符串也是可行的
canvas.renderAll()
}, 1000)
setOverlayColor
可以在某个时刻设置画布遮罩的颜色
setOverlayColor(overlayColor,callback)
overlayColor :设置前景色或者图案
callback: 回调函数(设置完要刷新画布)必须传入
canvas.setOverlayColor(
'yellowgreen', // 设置颜色
canvas.renderAll.bind(canvas) // 刷新画布//一定要加bind去绑定
)
设置图像为遮罩,此时会重复渲染图像,铺满整个画布
canvas.setOverlayColor(
{ source: '../../images/bg4.png' },
canvas.renderAll.bind(canvas)
)
canvas.setOverlayColor(
{
source: '../../images/bg4.png',
repeat: 'no-repeat', // 不重复
offsetX: 200, // x轴方向的偏移
offsetY: 100 // y轴方向的偏移
},
canvas.renderAll.bind(canvas)
)
但需要注意,overlayColor是在画布的顶部设置一层覆盖色,但是画布的操作并没有限制
fabric.Group()
可以同时操作多个对象
const t=new fabric.Triangle({
top:300,
left:210,
height:100,
width:100,
fill:'red',
})
const circle=new fabric.Circle({
top:100,left:220,radius:75,fill:'green'
})
const group=new fabric.Group([t,circle])
canvas.add(group,rect,rect1)
可以对其中的对象集进行修改
group.item(0).set('fill', 'red');
group.item(1).set({
text: 'trololo',
fill: 'white'
});
所有对canvas的修改,包括在其中添加删除对象,以及对象参数的修改,都需要调用渲染方法才能显示出来
canvas.renderAll();
fabric.Image.fromURL
由于图像文件的加载是异步的,所以对Image的后续操作,都要在回调函数中完成
fabric.Image.fromURL(imgUrl,function(img) {
canvas.add(img).renderAll()
},{crossOrigin: 'anonymous'})
fabric.Image
先创建一个img标签的DOM元素,之后,在添加到fabric中
var imgElement = document.getElementById('dog');
var imgInstance = new fabric.Image(imgElement, {
left: 50,
top: 50,
angle: 10,
opacity: 0.85
});
canvas.add(imgInstance);
scaleToWidth
scaleToWidth(value: number), scaleToHeight(value: number)
用于指定高度或者宽度等比例缩放图片,一般使用一个就可以,另外一个会自动按照比例缩放
使用场景:渲染图片时,由于图片尺寸不能确定,可能远远大于画布尺寸,所以需要缩放成一定的大小来显示
fabric.Image.fromURL('https://ts4.cn.mm.bing.net/th?id=OIP-C.kB-Ovasi0GW67-rmwnAcwAHaEo&w=316&h=197&c=8&rs=1&qlt=90&o=6&dpr=1.3&pid=3.1&rm=2',function(img){
img.scaleToWidth(380)
canvas.add(img).setActiveObject(img)
canvas.renderAll()
})
set
使用set可以设置元素的属性,get可以获取元素的属性
使用img.set来添加自定义形状的图片
fabric.Image.fromURL(
'http://fabricjs.com/assets/mononoke.jpg',
function(img) { canvas.add(img.set({
left: 400,
top: 350,
clipPath: new fabric.Circle({
radius: 200,
originX: 'center',
originY: 'center'
}),
angle: 30
})
.scale(0.25)
);
canvas.renderAll()
}, { crossOrigin: 'anonymous' });
clipPath
剪切
const rect4=new fabric.Rect({
width:200,height:100,fill:'red',clipPath:new fabric.Circle({
radius:40, originX: "center",originY: "center"
})
})
canvas.add(rect4)
注意:clipPath从对象的中心开始定位,对象originX 和originY不起任何作用,而clipPath 的originX 和originY起作用。定位逻辑和fabric.Group相同
animate动画效果
图片的动画效果
改变图片的透明度
img.animate("opacity",img.get("opacity")===0.7?0.4:0.7,{
duration:1000,
onChange:canvas.renderAll.bind(canvas),//是动画的回调函数,可以绑定事件,就是当canvas渲染完成时自动发生动画
onComplete:animate
})
animate接受三个参数
第一个参数是动画的属性,可以是任何用set( )方法设定的值;
第二个参数是发生变化的结束值(例如正方形从0度旋转到360度);
第三个参数是设置动画的细节属性,包括动画时间,回调函数,缓动效果,等等。
改变大小:
function animate1() {
img.animate("scaleY", img.get("scaleY") === 0.8 ? 0.2 : 0.8, {
duration: 1000,
onChange: canvas.renderAll.bind(canvas),
onComplete: animate1
})
}
animate1()
元素的动画效果
改变高度
function animate(){
try{
canvas.item(2).animate("top",canvas.item(2).get("top")===500?"100":"500",{//除了top之外,任意的可变属性都可以用来做动画效果
duration:1000,
onChange:canvas.renderAll.bind(canvas),
onComplete:animate
})
}catch(error){
setTimeout(animate,500)
}
}
animate()
改变宽度
function animate1() {
try {
canvas
.item(0)
.animate("width", canvas.item(0).get("width") == 50 ? "0" : "50", {
duration: 1000,
onChange: canvas.renderAll.bind(canvas),
onComplete: animate1
});
} catch (error) {
setTimeout(animate1, 500);
}
}
animate1()
将fabric画布内容与图片的相互转化
画布转化为图片
1、现在的浏览器都支持右键另存为图片的方法将canvas画布内容转化为图片
2、在代码中可以通过toDataURL()来操作:可以将画布里面的图案转化为base64编码格式的png,然后返回Data URL数据
HTMLCanvasElement.toDataURL() 返回一个包含图片展示的dataURL,默认为png格式
canvas.toDataURL(type, encoderOptions);
type 可选,默认为image/png
encoderOptions 在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。
渲染到html页面上
<button type="button" onclick="downloadImage()">to Image</button>
const downloadImage=function(){
const img=document.createElement('img') //新建一个dom元素
console.log(img);
img.src=canvas.toDataURL("image/png") //犯规的是遗传base64编码的url
document.body.appendChild(img)
}
下载到本地
a标签法
1、将cavas元素的数据通过toDataURL转化成base64编码的图片格式
2、利用a标签设置download属性可以将href连接元素下载,将该属性值设置为上一步获得的base64格式字符串
3、构造一个点击时间,并触发a标签的click时间完成下载
<canvas id="canvas" ></canvas>
<button type="button" onclick="downloadImage()">to Image</button>
const canvas=this._canvas=new fabric.Canvas('canvas',{
width:600,
height:600,
// overlayColor:'pink'
})
const downloadImage=function(){
const el=document.createElement('a')//创建一个a标签,并设置里href和download属性
el.href=canvas.toDataURL("image/png")//canvas是之前获取的画布元素 设置hred为图片经过base64编码后的字符串,默认为png格式
el.download='canvas.png'//保存的文件名称
//创建一个点击时间并对a标签进行触发
const event=new MouseEvent('click')
el.dispatchEvent(event)
}
或者
const downloadImage = () => {
const ext = "png";
const base64 = canvas.toDataURL({
format: ext,
enableRetinaScaling: true
});
const link = document.createElement("a");
link.href = base64;
link.download = `eraser_example.${ext}`;
link.click();
};
缺点:无法被异步代码包裹,也就是包含网络请求的情况下代码不生效
对于分辨率过高的canvas,生成的dataURL过长,超过浏览器限制,可能会导致无法顺利下载
saveAs方法
需要用到第三方库fileSaver.js
1、通过canvas的toBlob API将数据转化为二进制格式
2、通过FileSaver.js提供的daveAs方法对该二进制进行下载
实现不了(?)
已知url的图片转化为canvas
直接借助canvas的标签来完成
本地图片转化canvas
当base64字符串作为img标签的src数学时,如果是有效的,则可以预览
思路
需要input标签支持type=file,需要使用filereader对象,有点是选完文件之后可以自动转换
1、当type=file的input标签触发onchange事件的时候,开始执行以下步骤
2、创建一个新的FileReader对象
3、使用其的readAsDataURl这个API,读取文件内容
4、当读取成功时,触发回调函数,可以通过回调函数的参数e,利用数学e.target.result获取base64字符串
5、该base64字符串即为所需要的内容
首先使用input标签去导入本地图片
<canvas id="canvas"></canvas>
<input type="file" accept="image/png, image/jpeg, image/gif, image/jpg" id="input">
<script src='./fabricdemo4.js'></script>
操作canvas
let canvas=new fabric.Canvas('canvas',{
width:400,
height:300
})
将图片导入到canvas中
//将图片添加到canvas中
let input=document.querySelector('#input')
input.addEventListener('input',function(e){
new Promise((resolve,reject)=>{
let reader=new FileReader()
reader.onloadend=function(e){
resolve(e.target.result)
}
reader.readAsDataURL(e.target.files[0])
}).then(resolve=>{//是一个base64格式的字符串 拿到刚刚上传的图片的base64格式的字符串 有时候从后端获取过来的图片数据就是一个base64格式的字符串
// let Base64Prefix='data:image/png;base64,'
//let data=atob(resolve.startsWith(Base64Prefix)?resolve.substring(Base64Prefix.length):resolve)//对base64格式的字符串进行解码
fabric.Image.fromURL(resolve,img=>{//通过url方法添加图片
canvas.add(img).renderAll()
},{crossOrigin:'anonymous'})
//const img=new Image()
// img.src=resolve
// const img1=document.createElement('img') //通过创建DOM元素的方法添加
// img1.src=resolve
//canvas.add(new fabric.Image(img1))
// const myctx=canvas.getContext('2d')
// img.οnlοad=()=>{
// myctx.drawImage(img,0,0,300,150)//直接在canvas中绘制图片
// }
})
})
说明
Base64应用场景
1、项目中,把一些比较小的图片保存在数据中,用一个字段进行保存,但是前端页面有时需要展示这些图片到页面上,但是直接返回数据库中保存的数据会出现问题,因为网络不能传输不可打印字符。因此后端把文件数据返回给前端的时候,需要将数据转换为base64格式编码的字符串。
2、URL编码
发http请求的时候,在解析参数是根据?和=去解析的,但是当key value中包含=号时,参数解析就会出错。以及当url出现汉字的时候,会自动编码
画布转化为SVG并保存到本地
URL.createObjectURL
URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。
可以在浏览器对该url地址进行访问
URL.revokeObjectURL()
在每次调用 createObjectURL() 方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放。window.URL.revokeObjectURL(objectURL);
const URL1 = URL.createObjectURL(file)
console.log(URL1)
document.querySelector('#img1').src = URL1
URL.revokeObjectURL(URL1)
为什么会少了一个toSVG方法
这是因为之前在下载fabric.js的时候,将SVG相关的代码给移除掉了
解决
<button onclick="downloadSVG()">toSVG</button>
//创建canvas画布
const canvas=this._canvas=new fabric.Canvas('canvas',{
width:600,
height:600,
// overlayColor:'pink'
})
const downloadSVG=function(){
console.log(this._canvas.toSVG,this._canvas);
const svg = canvas.toSVG()
const blob=new Blob([svg],{type:"image/svg+xmml"})
const blobURL=URL.createObjectURL(blob)
const a=document.createElement('a')
a.href=blobURL
a.download='canvas.svg'
a.click()
URL.revokeObjectURL(blobURL)
}
序列化
序列化和反序列化的作用
序列化主要用于持久存储,反序列化主要用于将持久存储的静态内容转换为canvas中可以操作的2d元素,从而可以实现将某个时刻画布上的状态进行还原,如果存储够用,可以将整个在Canvas上的绘制过程进行录制或者回放
转化对象时保存自定义属性
toJSON 方法(定义什么值将被序列化)可以将画布导出为JSON数据,但是不能保存自定义属性,比如元素的id属性等
如果希望在序列化的时候能够保存自定义属性,需要在方法里面传入一个数组,这个数组表明要输出的自定义属性的key
const a = canvas.toJSON(['myFabricType1', 'myFabricType2'])
const b = canvas.toDatalessJSON(['myFabricType1', 'myFabricType2'])
const c = canvas.toDatalessObject(['myFabricType1', 'myFabricType2'])
const d = canvas.toObject(['myFabricType1', 'myFabricType2'])
JSON.stringify()
将js对象转化为JSON字符串,如果指定了replacer函数,则可以选择性的替换值
JSON.stringify(value[, replacer[, space]])
value:必需, 要转换的 JavaScript 值(通常为对象或数组)。
replacer:可选。用于转换结果的函数或数组。
space:可选,文本添加缩进、空格和换行符,
console.log(JSON.stringify([new Number(3), new String('false'), new Boolean(false)]));
// expected output: "[3,"false",false]"
const toJSON=function(){
const json=canvas.toDatalessJSON()
console.log(json)
}
const json=canvas.toDatalessJSON()
let out=JSON.stringify(json,null,'/t')
console.log(json,out)
clipboard
Clipboard所有的操作都是异步的,返回promise对象,不会造成页面的卡顿,可以将任意内容放在剪切板中
剪贴板相关的有两个权限:clipboard-write(写权限)和clipboard-read(读权限)。"写权限"自动授予脚本,而"读权限"必须用户明确同意给予。也就是说,写入剪贴板,脚本可以自动完成,但是读取剪贴板时,浏览器会弹出一个对话框,询问用户是否同意读取。
Clipboard.readText()
用于赋值剪切板里面的文本数据
如果是第一次调用浏览器这时会跳出一个对话框,询问用户是否同意脚本读取剪贴板。
如果用户不同意,脚本就会报错这时,可以使用try…catch结构,处理报错。
async function getClipboardContents() {
try {
const text = await navigator.clipboard.readText();
console.log('Pasted content: ', text);
} catch (err) {
console.error('Failed to read clipboard contents: ', err);
}
}
Clipboard.read()
用于复制剪切板里面的数据
该方法返回一个 Promise 对象。一旦该对象的状态变为 resolved,就可以获得一个数组,每个数组成员都是 ClipboardItem 对象的实例。
async function getClipboardContents() {
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
for (const type of clipboardItem.types) {
const blob = await clipboardItem.getType(type);
console.log(URL.createObjectURL(blob));
}
}
} catch (err) {
console.error(err.name, err.message);
}
}
Clipboard.writeText()
将文本内推写入剪切板
document.body.addEventListener(
'click',
async (e) => {
await navigator.clipboard.writeText('Yo')
}
)
这个方法不需要用于的许可,但是最好也放在try…catch里面防止报错
async function copyPageUrl() {
try {
await navigator.clipboard.writeText(location.href);
console.log('Page URL copied to clipboard');
} catch (err) {
console.error('Failed to copy: ', err);
}
}
navigator.clipboard.write
将任意数据写入剪切板中,可以是文本数据,也可以是二进制数据
async function copyImage() {
const fetchImage = ()=>{
const input = document.getElementById("file");
return Promise.resolve(input.files[0]);
};
const clipboardItem = new ClipboardItem({
["image/png"]:fetchImage()
});
const reponse = await navigator.clipboard.write([clipboardItem]);
console.log(reponse)
}
ClipboardItem()
构造函数创建了一个新的ClipboardItem对象,该对象表示将通过剪贴板API存储或检索的数据,即分别是Clipboard .write()和Clipboard .read()。
new ClipboardItem(data)
new ClipboardItem(data, options)
try/catch
当你第一次使用async/await, 你可能尝试使用try/catch将每一个 async 操作包围起来。如果你await一个被 reject 的 Promise,JavaScript 会抛出一个可以被捕获的错误。
run();
async function run() {
try {
await Promise.reject(new Error("Oops!"));
} catch (error) {
error.message; // "Oops!"
}
}
内容
<button onclick="toJSON()">toJSON</button>
const canvas=this._canvas=new fabric.Canvas('canvas',{
width:600,
height:600,
// overlayColor:'pink'
})
const toJSON=function(){
const json=canvas.toDatalessJSON()//转化成对象时保存自定义属性
let out=JSON.stringify(json,null,'/t')
const blob=new Blob([out],{type:"text/plain"})
const a=document.createElement('a')
const blobURL=URL.createObjectURL(blob)
a.herf=blobURL
a.download='canvas.svg'
a.click()
URL.revokeObjectURL(blobURL)
}
js中onclick事件中的this用法
要想获取onclick所在的标签内容,必须加入参数,而且实参为this,形参为非关键字就行了。
<div onclick="func(this)" name="name">点击事件</div>
function func(e) {
conesole.log(e); // 点击后直接获取本点击对象
console.log(e.innerHTML);
}
classList 属性
给div添加class
classList属性返回元素的类型,该属性用于在元素中添加,移除以及切换css类。classList属性是只读的,但是可以使用add()和remove()方法修改它
document.getElementById("myDIV").classList.add("mystyle");
样式显示
选中某个元素的时候,添加样式
先全部取消样式,再单独针对选中的元素来添加样式
.active{
background-color: red;
font-weight:bold
}
<div>
<button id="select" onclick="changeAction(this)">select</button>
<button id="erase" onclick="changeAction(this)">erase</button>
<button id="undo" onclick="changeAction(this)">undo erasings</button>
<button id="draw" onclick="changeAction(this)">draw</button>
<button id="spray" onclick="changeAction(this)">spray</button>
</div>
function changeAction(target){
['select','erase','undo','draw','spray'].forEach(action=>{
const t=document.getElementById(action)
t.classList.remove('active')//所有的元素移除active属性
})
//为单独点击的元素添加active属性
target.classList.add('active')
}
画刷功能
fabric.js的基础包并没有包含橡皮擦的模块,在使用的时候,安装之前的方法,下载包含Erase功能的fabric
canvas.isDrawingMode = false // 不允许绘画(返回普通框选模式) 默认情况下是不允许绘画的
要使用橡皮擦的话,需要现将isDrawingMode 设为 true
其次canvas.freeDrawingBrush=new fabric.EraserBrush(canvas)//自由绘画模式 的画笔类型设置为橡皮擦对象,函数里面需要传入画布本身。就是在初始化画布时的那个对象
canvas.freeDrawingBrush.inverted=true// 恢复被擦拭的地方
erasable属性
用于确定对象是否可以擦除
const obj = new fabric.Rect({ erasable: false });
obj.set('erasable', true);//对象可以擦除
fabric.Object.prototype.erasable = false;//让所有的对象都不可以擦除
备注
.box-cont{
display: flex;
}
#canvas{
border:1px solid black
}
.cont-code{
margin-left:10px;
}
.active{
background-color: red;
font-weight:bold
}
</style>
</head>
<body>
<div>
<button id="select" onclick="changeAction(this)">select</button>
<button id="erase" onclick="changeAction(this)">erase</button>
<button id="undo" onclick="changeAction(this)">undo erasings</button>
<button id="draw" onclick="changeAction(this)">draw</button>
<button id="spray" onclick="changeAction(this)">spray</button>
</div>
<div>
<label>background image <code>erasable</code><input type="checkbox" onchange="setErasingbgc(this)"></label>
</div>
<div>
<label>remove erased objects on <code>erasing:end</code><input type="checkbox" onclick="setErasingRemovesErasedObjects(this)"></label>
</div>
<div>
<button onclick="toJSON()">toJSON</button>
<button type="button" onclick="downloadImage()">to Image</button>
<button onclick="downloadSVG()">toSVG</button>
</div>
<div class="box-cont">
<div>
<canvas id="canvas" ></canvas>
</div>
<div class="cont-code">
<code>erasing:end</code></br>
<code id="output">N/A</code>
</div>
</div>
<script src="./fabric (2).js"></script>
<script>
let erasingRemoveEraseObjects=false //创建canvas画布
const canvas=this._canvas=new fabric.Canvas('canvas',{
width:400,
height:600,
// overlayColor:'pink'
})
//初始化canvas 为canvas添加一些必要的元素和设置
function _init(){
canvas.setOverlayColor('rgba(0,0,255,0.2)',canvas.renderAll.bind(canvas))//设置前景图
const t=new fabric.Triangle({
top:300,
left:210,
height:100,
width:100,
fill:'red',
})
const rect=new fabric.Rect({
top:100,
left:100,
width:50,
height:50,
fill:'grey',
// erasable: false //直接在初始化元素的时候,修改可擦除属性
})
const rect1=new fabric.Rect({
top:100,
left:30,
width:50,
height:50,
fill:'grey',
erasable: false
})
const circle=new fabric.Circle({
top:100,left:220,radius:75,fill:'green'
})
const group=new fabric.Group([t,circle])
canvas.add(group,rect,rect1)
//引入图片 以及相关的设置
fabric.Image.fromURL('https://ts4.cn.mm.bing.net/th?id=OIP-C.kB-Ovasi0GW67-rmwnAcwAHaEo&w=316&h=197&c=8&rs=1&qlt=90&o=6&dpr=1.3&pid=3.1&rm=2',function(img){
img.scaleToWidth(380)//图片缩放比例
//canvas.add(img).setActiveObject(img) //添加图片,并且设置为活动对象。
canvas.renderAll()
img.clone((img)=>{//再拷贝一份相同的图片
canvas.add(img.set({
left:100,
top:250,
clipPath:new fabric.Circle({
radius:200,
originX:"center",
oringinY:"center"
}),
angle:20
}).scale(0.25)
)
canvas.renderAll()
})
img.set({opacity:0.7})
function animate(){//设置图片的动画效果
img.animate("opacity",img.get("opacity")===0.7?0.4:0.7,{
duration:1000,
onChange:canvas.renderAll.bind(canvas),//是动画的回调函数,可以绑定事件,就是当canvas渲染完成时自动发生动画
onComplete:animate
})
}
animate()
//设置背景图
canvas.setBackgroundImage(img,canvas.renderAll.bind(canvas),{
scaleX: canvas.width / img.width,
scaleY: canvas.height / img.height,
})
img.set({ erasable:false});//默认背景图是不可以擦除的
canvas.renderAll()
},{crossOrigin:'anonymous'})//允许跨域
function animate(){//小方块的高度动画效果
try{//注意canvas.item(2) 这样来取元素的,在后面有删除操作后,这个元素是会发生变化的
canvas.item(2).animate("top",canvas.item(2).get("top")===500?"100":"500",{
duration:1000,
onChange:canvas.renderAll.bind(canvas),
onComplete:animate
})
}catch(error){
setTimeout(animate,500)
}
}
animate()
}
_init()
const downloadImage=function(){
const el=document.createElement('a')//创建一个a标签,并设置里href和download属性
el.href=canvas.toDataURL("image/png")//canvas是之前获取的画布元素 设置hred为图片经过base64编码后的字符串,默认为png格式
el.download='canvas.png'//保存的文件名称
//创建一个点击时间并对a标签进行触发
const event=new MouseEvent('click')
el.dispatchEvent(event)
}
const downloadSVG=function(){
console.log(this._canvas.toSVG,this._canvas);
const svg = canvas.toSVG()
const blob=new Blob([svg],{type:"image/svg+xmml"})
const blobURL=URL.createObjectURL(blob)
const a=document.createElement('a')
a.href=blobURL
a.download='canvas.svg'
a.click()
URL.revokeObjectURL(blobURL)
}
const toJSON=function(){
const json=canvas.toDatalessJSON()
let out=JSON.stringify(json,null,'/t')
const blob=new Blob([out],{type:"text/plain"})
const clipboardItemData={[blob.type]:blob}//{text/plain: Blob}
const a=document.createElement('a')
const blobURL=URL.createObjectURL(blob)
a.herf=blobURL
a.download='canvas.svg'
a.click()
URL.revokeObjectURL(blobURL)
}
//改变样式以及设置画刷
function changeAction(target){
['select','erase','undo','draw','spray'].forEach(action=>{
const t=document.getElementById(action)
t.classList.remove('active')//所有的元素移除active属性
})
//为单独点击的元素添加active属性
if(typeof target==='string') target=document.getElementById(target) //在点击bgcerasable选框的时候,传入的是一个字符串
target.classList.add('active')
//设置功能画刷
switch (target.id){
case "select":
canvas.isDrawingMode=false // 不允许绘画(返回普通框选模式)
break;
case "erase"://启动画刷
canvas.freeDrawingBrush=new fabric.EraserBrush(canvas)//自由绘画模式 的画笔类型设置为橡皮擦对象 里面记得传入canvas这个对象
canvas.freeDrawingBrush.width=10//设置画笔的宽度
canvas.isDrawingMode=true//开启绘画模式 因为普通的框选模式是不支持绘画的
break;
case "undo":
canvas.freeDrawingBrush=new fabric.EraserBrush(canvas)
canvas.freeDrawingBrush.width=10
canvas.freeDrawingBrush.inverted=true// 恢复被擦拭的地方
canvas.isDrawingMode=true
break;
case "draw":
canvas.freeDrawingBrush=new fabric.PencilBrush(canvas)//设置自由绘制模式的画笔类型为铅笔类型
canvas.freeDrawingBrush.width=10
canvas.isDrawingMode=true
break;
case "spray":
canvas.isDrawingMode=true
canvas.freeDrawingBrush=10
canvas.freeDrawingBrush=new fabric.SprayBrush(canvas)
break;
}
}
//修改背景图可以被擦除
function setErasingbgc(target){
setDrawableErasableProp('backgroundImage',target.checked)
}
function setDrawableErasableProp(drawable,value){
console.log(drawable,value);
canvas.get(drawable)?.set({erasable:value})//使用可选链更加好 canvas.get(drawable) 获取元素 设置可擦除的属性
changeAction('erase')//同时自动修改当前的活动元素为erase
}
function setErasingRemovesErasedObjects(target){
erasingRemoveEraseObjects=target.checked
console.log(erasingRemoveEraseObjects);
}
// 修改右侧文字的显示 只对可以擦除的属性有用
canvas.on("erasing:end",function({targets,drawables}){//监听绘制完成事件 { targets, drawables } 进行了解构赋值 只有erasable为true的元素,才有type属性
var output=document.getElementById('output')
output.innerHTML = JSON.stringify({
objects: targets.map((t) => t.type),
drawables: Object.keys(drawables)
}, null, '\t');
//勾选erasing:end选框
if(erasingRemoveEraseObjects){
targets.forEach(obj=>canvas.remove(obj))
}
})
可选链操作符?.
允许读取位于连接对象链深处的属性的值,而不用明确验证链中的每个引用是否有效
有可能obj.first.second是没有定义的
let nestedProp = obj.first && obj.first.second;
为了避免报错,在访问obj.first.second之前,要保证 obj.first 的值既不是 null,也不是 undefined。如果只是直接访问 obj.first.second,而不对 obj.first 进行校验,则有可能抛出错误。
有了可选链运算符(?.),在访问 obj.first.second 之前,不再需要明确地校验 obj.first 的状态,再并用短路计算获取最终结果:
let nestedProp = obj.first?.second;
通过使用 ?. 运算符取代 . 运算符,JavaScript 会在尝试访问 obj.first.second 之前,先隐式地检查并确定 obj.first 既不是 null 也不是 undefined。如果obj.first 是 null 或者 undefined,表达式将会短路计算直接返回 undefined。
自由画布的实现
fabric.PatternBrush
自定义画刷
<style>
#canvas{
border:1px solid black
}
.box{
width:900px;
height:auto;
border:2px solid red;
display: flex;
justify-content: space-around
}
.box-cont{
margin-left:50px;
width:400px;
display: flex;
flex-direction: column;
}
</style>
</head>
<body>
<div class="box">
<canvas id="canvas"></canvas>
<div class="box-cont">
<div><button id="drawing-mode">Cancel drawing mode</button></br>
<button id="clear-canvas">Clear</button></div>
<div id="drawing-mode-options">
<table>
<tr>
<td>Mode</td>
<td><select id="drawing-mode-selector"><option>Pencil</option><option>Circle</option><option>Spray</option><option>pattern</option>
<option>hline</option><option>vline</option><option>square</option><option>diamond</option><option>textture</option>
</select></td>
</tr>
<tr>
<td>Line Width</td>
<td><span>2</span><input type="range" min="0" max="10" value="0" id="drawing-line-width"></td>
</tr>
<tr><td>Line color</td><td><input type="color" id="drawing-color"></td></tr>
<tr><td>Shadow color</td><td><input type="color" id="drawing-shadow-color"></td></tr>
<tr><td>Shadow width</td><td><span>2</span><input type="range" min="0" max="10" value="0" id="drawing-shadow-width"></td></tr>
<tr><td>Shadow offset</td><td><span>2</span><input type="range" min="0" max="10" value="0" id="drawing-shadow-offset"></td></tr>
</table>
</div>
</div>
</div>
<script src="./fabric (2).js"></script>
<script>
// let canvas=this._canvas=new fabric.Canvas('canvas',{
// width:500,
// height:400,
// isDrawingMode: true
// })
// fabric.Object.prototype.transparentCorners=false
// // 封装了一个取DOm元素的函数
// var $=function(id){return document.getElementById(id)}
// var clearEl=$('clear-canvas'),
// drawingModeEl=$('drawing-mode'),
// drawingOptionsEl=$('drawing-mode-options')
// // 画布的删除
// clearEl.οnclick=function(){canvas.clear()}
// //是否开启绘制功能
// drawingModeEl.οnclick=function(){
// canvas.isDrawingMode=!canvas.isDrawingMode
// if(canvas.isDrawingMode){
// drawingModeEl.innerHTML='Cancel drawing mode'
// drawingOptionsEl.style.display='block'
// }
// else{
// drawingModeEl.innerHTML='Enter drawing mode'
// drawingOptionsEl.style.display='none'
// }
// }
// //修改画刷的样式
// $('drawing-mode-selector').οnchange=function(){
// if(this.value==='vline'){
// var vLinePatternBrush = new fabric.PatternBrush(canvas);
// vLinePatternBrush.getPatternSrc = function() {
// var patternCanvas = fabric.document.createElement('canvas');
// patternCanvas.width = patternCanvas.height = 10;
// var ctx = patternCanvas.getContext('2d');
// ctx.strokeStyle = this.color;
// ctx.lineWidth = 5;
// ctx.beginPath();
// ctx.moveTo(0, 5);
// ctx.lineTo(10, 5);
// ctx.closePath();
// ctx.stroke();
// return patternCanvas;
// };
// canvas.freeDrawingBrush = vLinePatternBrush;
// }
// // 自由绘制文本
// if(this.value==='textture'){
// var img=new Image()//创建一个image对象
// img.οnlοad=function(){
// var textturePatterBrush=new fabric.PatternBrush(canvas)
// textturePatterBrush.source=img
// textturePatterBrush.width = 50;
// textturePatterBrush.height = 50;
// canvas.freeDrawingBrush=textturePatterBrush
// }
// img.src='./蜂巢.png'//定义image对象的src 相当于给浏览器缓存了一张图片 一定要放到onload后面
// }
// if(this.value==='square'){
// canvas.freeDrawingBrush = squareBrush
// }
// }
// const squareBrush = new fabric.PatternBrush(canvas)
// // getPatternSrc 取得要重複繪製的圖形 Canvas
// squareBrush.getPatternSrc= function() {
// const squareWidth = 30
// const squareDistance = 2
// // // 創立一個暫存 canvas 來繪製要畫的圖案
// const patternCanvas = fabric.document.createElement('canvas')
// // canvas 總大小為每一格畫筆的大小
// patternCanvas.width = patternCanvas.height = squareWidth + squareDistance
// const ctx = patternCanvas.getContext('2d')
// ctx.fillStyle = this.color
// ctx.fillRect(0, 0, squareWidth, squareWidth)
// // 回傳繪製完畢的 canvas
// return patternCanvas
// }
(function() {
var $ = function(id){return document.getElementById(id)};
var canvas = this.__canvas = new fabric.Canvas('canvas', {
isDrawingMode: true
});
fabric.Object.prototype.transparentCorners = false;
var drawingModeEl = $('drawing-mode'),
drawingOptionsEl = $('drawing-mode-options'),
drawingColorEl = $('drawing-color'),
drawingShadowColorEl = $('drawing-shadow-color'),
drawingLineWidthEl = $('drawing-line-width'),
drawingShadowWidth = $('drawing-shadow-width'),
drawingShadowOffset = $('drawing-shadow-offset'),
clearEl = $('clear-canvas');
clearEl.onclick = function() { canvas.clear() };
drawingModeEl.onclick = function() {
canvas.isDrawingMode = !canvas.isDrawingMode;
if (canvas.isDrawingMode) {
drawingModeEl.innerHTML = 'Cancel drawing mode';
drawingOptionsEl.style.display = '';
}
else {
drawingModeEl.innerHTML = 'Enter drawing mode';
drawingOptionsEl.style.display = 'none';
}
};
if (fabric.PatternBrush) {
var vLinePatternBrush = new fabric.PatternBrush(canvas);
vLinePatternBrush.getPatternSrc = function() {
var patternCanvas = fabric.document.createElement('canvas');
patternCanvas.width = patternCanvas.height = 10;
var ctx = patternCanvas.getContext('2d');
ctx.strokeStyle = this.color;
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(0, 5);
ctx.lineTo(10, 5);
ctx.closePath();
ctx.stroke();
return patternCanvas;
};
var hLinePatternBrush = new fabric.PatternBrush(canvas);
hLinePatternBrush.getPatternSrc = function() {
var patternCanvas = fabric.document.createElement('canvas');
patternCanvas.width = patternCanvas.height = 10;
var ctx = patternCanvas.getContext('2d');
ctx.strokeStyle = this.color;
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(5, 10);
ctx.closePath();
ctx.stroke();
return patternCanvas;
};
var squarePatternBrush = new fabric.PatternBrush(canvas);
squarePatternBrush.getPatternSrc = function() {
var squareWidth = 10, squareDistance = 2;
var patternCanvas = fabric.document.createElement('canvas');
patternCanvas.width = patternCanvas.height = squareWidth + squareDistance;
var ctx = patternCanvas.getContext('2d');
ctx.fillStyle = this.color;
ctx.fillRect(0, 0, squareWidth, squareWidth);
return patternCanvas;
};
var diamondPatternBrush = new fabric.PatternBrush(canvas);
diamondPatternBrush.getPatternSrc = function() {
var squareWidth = 10, squareDistance = 5;
var patternCanvas = fabric.document.createElement('canvas');
var 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;
};
var img = new Image();
// img.οnlοad=function(){
// var texturePatternBrush=new fabric.PatternBrush(canvas)
// texturePatternBrush.source=img
// texturePatternBrush.width = 50;
// texturePatternBrush.height = 50;
// }
img.src='./蜂巢.png'//定义image对象的src 相当于给浏览器缓存了一张图片 一定要放到onload后面
var texturePatternBrush = new fabric.PatternBrush(canvas);
texturePatternBrush.source = img;
}
$('drawing-mode-selector').onchange = function() {
if (this.value === 'hline') {
canvas.freeDrawingBrush = vLinePatternBrush;
}
else if (this.value === 'vline') {
canvas.freeDrawingBrush = hLinePatternBrush;
}
else if (this.value === 'square') {
canvas.freeDrawingBrush = squarePatternBrush;
}
else if (this.value === 'diamond') {
canvas.freeDrawingBrush = diamondPatternBrush;
}
else if (this.value === 'textture') {
canvas.freeDrawingBrush = texturePatternBrush;
}
else {
canvas.freeDrawingBrush = new fabric[this.value + 'Brush'](canvas);
}
if (canvas.freeDrawingBrush) {
var brush = canvas.freeDrawingBrush;
brush.color = drawingColorEl.value;
if (brush.getPatternSrc) {
brush.source = brush.getPatternSrc.call(brush);
}
brush.width = parseInt(drawingLineWidthEl.value, 10) || 1;
brush.shadow = new fabric.Shadow({
blur: parseInt(drawingShadowWidth.value, 10) || 0,
offsetX: 0,
offsetY: 0,
affectStroke: true,
color: drawingShadowColorEl.value,
});
}
};
drawingColorEl.onchange = function() {
var brush = canvas.freeDrawingBrush;
brush.color = this.value;
if (brush.getPatternSrc) {
brush.source = brush.getPatternSrc.call(brush);
}
};
drawingShadowColorEl.onchange = function() {
canvas.freeDrawingBrush.shadow.color = this.value;
};
drawingLineWidthEl.onchange = function() {
canvas.freeDrawingBrush.width = parseInt(this.value, 10) || 1;
this.previousSibling.innerHTML = this.value;
};
drawingShadowWidth.onchange = function() {
canvas.freeDrawingBrush.shadow.blur = parseInt(this.value, 10) || 0;
this.previousSibling.innerHTML = this.value;
};
drawingShadowOffset.onchange = function() {
canvas.freeDrawingBrush.shadow.offsetX = parseInt(this.value, 10) || 0;
canvas.freeDrawingBrush.shadow.offsetY = parseInt(this.value, 10) || 0;
this.previousSibling.innerHTML = this.value;
};
if (canvas.freeDrawingBrush) {
canvas.freeDrawingBrush.color = drawingColorEl.value;
// canvas.freeDrawingBrush.source = canvas.freeDrawingBrush.getPatternSrc.call(this);
canvas.freeDrawingBrush.width = parseInt(drawingLineWidthEl.value, 10) || 1;
canvas.freeDrawingBrush.shadow = new fabric.Shadow({
blur: parseInt(drawingShadowWidth.value, 10) || 0,
offsetX: 0,
offsetY: 0,
affectStroke: true,
color: drawingShadowColorEl.value,
});
}
})()