实现画刷相关fabric-canvas笔记-参考官网-demo3

画刷相关-实现

  • 实现一个画布功能-可以擦除,绘制,保存画布
    在这里插入图片描述
  • 实现可以自由绘画的画布在这里插入图片描述

案例

    <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默认构建的部分,需要重新下载

> http://fabricjs.com/erasing

但是如果只下载在这里插入图片描述
这几个单独的部分,再去引用,会报错。
因此直接将所有的都下载下来,一块引用
在这里插入图片描述
在这里插入图片描述
不要选中下面的,不认会少了相关的函数

笔记

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,
  });
}
})()

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一夕ξ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值