fabric.js使用

  • fabric.js 是 常用的 canvas 插件
    • 1, 在项目中使用
    • 2, 特殊用法
      • ①, 基本设置
      • ②, 画板数据的导入导出
      • ③, 遮罩 Pattern ( 引用官网案例 )
      • ④, 多个对象合并, 并设置为 fabric 背景 ( 适用于变色和更多场景 )
      • ⑤, 把 canvas对象 或者 fabric对象 导出为图片
      • ⑥, 位置的获取
        • 思路一: (计算法, 没算出来)
        • 思路二 (记录位置) 补充: 鼠标位移留痕不规则线条在如下代码中
    • 注意事项

fabric.js 是 常用的 canvas 插件

官方地址:  http://fabricjs.com/
小编用的版本为 4.5.1

1, 在项目中使用

     #myCanvas{width:10000px;height:10000px;}
<canvas id="myCanvas" width="10000" height="10000"></canvas>
<!-- 此处引用jquery ,也可以不引用 -->
<script src="./jquery-2.0.0.min.js"></script>
<script src="./fabric.5.1.0.min.js"></script>
     // 初始化 canvas 为 fabric 对象var canvas = this.__canvas = new fabric.Canvas('myCanvas');// 画一个矩形var rect = new fabric.Rect({width: 1000,height: 1000,left: 0,top: 0,fill: 'rgba(255, 0, 0, 0.4)',});// canvas 添加矩形并渲染canvas.add( rect );canvas.renderAll();

2, 特殊用法

下面是在使用时用到和遇到的场景

①, 基本设置

     //画板元素不能被选中canvas.skipTargetFind = false;//画板不显示选中canvas.selection = false;canvas.freeDrawingBrush.color = "red";canvas.freeDrawingBrush.width = 1;//画板不限制调整时的宽高比// mycanvas.uniScaleTransform = true;// //绑定画板事件canvas.on({// 'mouse:dblclick': this.dblShowRectFrom,//双击区域弹出'mouse:down': this.mouseDownDrawRect, //按下,'mouse:move': this.mouseMoving, //移动中,'mouse:up': this.mouseUpDrawRect, //抬起,'mouse:wheel': this.zoomsHandle, //缩放// 'object:moved': this.objectMoved, //移动完成// // 'object:moving': objectMov,//移动中// 'object:scaled': this.mouseScaleDrawRect,//缩放完成// 'object:scaling': this.mouseScalingDrawRect,//缩放中});

②, 画板数据的导入导出

 // 1, 导出  检测 画板上所有对象 并保存为 字符串let inkTrace = [];canvas.getObjects().forEach((v,i) => {let klass = v.toJSON();if(klass.type == 'image'){// 对其中一些特殊对象进行处理}inkTrace.push(klass)})// 对数据最后加工if(inkTrace.length==0){inkTrace=""}else{inkTrace = JSON.stringify(inkTrace)}
 // 2, 导入 根据字符串处理为 fabric 可识别的对象JSON.parse(inkTrace).forEach((v,i) => {// 不同类型的数据要分别处理 数据中的 type 属性if(v.type=="image"){new fabric.Image.fromObject(v, (e) => {mycanvas.add(e)})}if(v.type=="path"){new fabric.Path.fromObject(v, (e) => {canvas.add(e)})}})canvas.renderAll();

③, 遮罩 Pattern ( 引用官网案例 )

     var text = new fabric.Text('Honey,\nI\'m subtle', {fontSize: 250,left: 0,top: 0,lineHeight: 1,originX: 'left',fontFamily: 'Helvetica',fontWeight: 'bold',statefullCache: true,scaleX: 0.4,scaleY: 0.4});var shape = new fabric.Rect({width: 200,height: 100,left: 10,top: 300,});canvas.add(text, shape);loadPattern('./xxx.png');function loadPattern(url) {fabric.util.loadImage(url, function(img) {console.log(img)text.set('fill', new fabric.Pattern({source: img,repeat: "no-repeat"}));rect.set('fill', new fabric.Pattern({source: img,repeat: "no-repeat"}));canvas.renderAll();});}

④, 多个对象合并, 并设置为 fabric 背景 ( 适用于变色和更多场景 )

         // 创建对象let image = new fabric.Image(image, {left: 10,top: 10,scaleX: 1,scaleY: 1,crossOrigin: 'anonymous',// 跨域})let shape = new fabric.Rect({width: image.width,height: image.height,opacity:0.4,fill: "#FF0000",left: 10,top: 10,scaleX: 1,scaleY: 1,});// 合并let group = new fabric.Group([image, shape], {})// 设置为背景canvas.setBackgroundImage(group, canvas.renderAll.bind(canvas), {});

⑤, 把 canvas对象 或者 fabric对象 导出为图片

     let image = new Image();// 先引入一张图片作为底图, 不引入也没关系image.src = './xxx.png';image.setAttribute('crossOrigin', 'anonymous');image.onload = () => {let fabricImage = new fabric.Image(image, {left: 0,top: 0,width: image.width,height: image.height})let shapeLine = new fabric.Rect({width: 1700,height: 800,fill: "#333",left: 0,top: 0,});let shapeVert = new fabric.Rect({width: 235,height: image.height,fill: "#333",left: 0,top: 0,});// 把这几个对象合并一下let group = new fabric.Group([fabricImage, shapeLine, shapeVert], {})let imgs = group.toDataURL({format: 'png', // jpeg或pngleft: 0,top: 0,width: image.width,height: image.height});// imgs 就是导出后的图片, 放在 img 的src中即可使用}

⑥, 位置的获取

应用场景: 要求背景为一块矩形, 所有的遮罩, 痕迹, 只准出现在背景矩形上, 不准超出

思路一: (计算法, 没算出来)

在有旋转角度的情况下,
     位移: 算出位移之后出现在矩形上的位置(如,右侧超出出现在右侧)
     缩放: 在有旋转角度的情况下, 拉伸已旋转的矩形, 如果超出背景矩形, 则缩放至最大高度(没算出来)
     旋转: 旋转矩形如果超出背景矩形, 计算矩形能在背景矩形旋转的最大角度(没算出来)

mycanvas.on({//'mouse:down': this.mouseDownDrawRect, //按下,//'mouse:move': this.mouseMoving, //移动中,//'mouse:up': this.mouseUpDrawRect, //抬起,//'mouse:wheel': this.zoomsHandle, //缩放'object:moved': this.objectMov, //移动完成//'object:moving': this.objectMov,//移动中'object:scaled': this.mouseScaleDrawRect,//缩放完成//'object:scaling': this.mouseScalingDrawRect,//缩放中'object:rotated': this.objectRotated,// 旋转完成//'object:rotating': this.objectRotated,// 旋转中});// 移动完成计算objectMov = (t) => {const { mainCanvas } = this.state;let mycanvas = mainCanvas,oImgList = mycanvas.getObjects(),oImg = t?oImgList[oImgList.length-1]:mycanvas.getActiveObject();if(!oImg) { return false;}let ep = oImg.aCoords,// 目标对象的四个坐标点angle= Math.floor(( oImg.angle ) / 90),// 目标对象的旋转角度ptl = mycanvas.backgroundImage.aCoords.tl,pbr = mycanvas.backgroundImage.aCoords.br,et = 0,el = 0,eb = 0,er = 0,w = 0,h = 0;switch (angle) {case 0:et = ep.tl.y;el = ep.bl.x;eb = ep.br.y;er = ep.tr.x;el < ptl.x && oImg.set({ left:ptl.x + ep.tl.x - ep.bl.x }); // 左侧超出if(er > pbr.x) { // 右侧超出if((ep.tr.x - ep.bl.x) <= (pbr.x - ptl.x)) { oImg.set({ left:pbr.x - (ep.tr.x - ep.tl.x) })}if((ep.tr.x - ep.bl.x) > (pbr.x - ptl.x)) { oImg.set({ left:ptl.x }) }}et < ptl.y && oImg.set({ top:ptl.y })// 上侧超出if(eb > pbr.y) { // 下侧超出if((ep.br.y - ep.tl.y) <= (pbr.y - ptl.y)) { oImg.set({ top:pbr.y - (ep.br.y - ep.tl.y) })}if((ep.br.y - ep.tl.y) > (pbr.y - ptl.y)) { oImg.set({ top:ptl.y }) }}break;case 1:et = ep.bl.y;el = ep.br.x;eb = ep.tr.y;er = ep.tl.x;er > pbr.x && oImg.set({ left:pbr.x  })// 右侧超出el < ptl.x && oImg.set({ left:ptl.x + ep.tl.x - ep.br.x });// 左侧超出et < ptl.y && oImg.set({ top:ptl.y + (ep.tl.y - ep.bl.y) })// 上侧超出eb > pbr.y && oImg.set({ top:pbr.y - (ep.tr.y - ep.tl.y) })// 下侧超出break;case 2:et = ep.br.y;el = ep.tr.x;eb = ep.tl.y;er = ep.bl.x;el < ptl.x && oImg.set({ left:ptl.x + ep.tl.x - ep.tr.x });// 左侧超出er > pbr.x && oImg.set({ left:pbr.x - (ep.bl.x - ep.tl.x)})// 右侧超出et < ptl.y && oImg.set({ top:ptl.y + (ep.tl.y - ep.br.y) })// 上侧超出eb > pbr.y && oImg.set({ top:pbr.y  })// 下侧超出break;case 3:et = ep.tr.y;el = ep.tl.x;eb = ep.bl.y;er = ep.br.x;el < ptl.x && oImg.set({ left:ptl.x }); // 左侧超出er > pbr.x && oImg.set({ left:pbr.x - (ep.br.x - ep.tl.x)})// 右侧超出et < ptl.y && oImg.set({ top:ptl.y + (ep.tl.y - ep.tr.y) })// 上侧超出eb > pbr.y && oImg.set({ top:pbr.y - (ep.bl.y - ep.tl.y) })// 下侧超出}mycanvas.renderAll()}
获取所有的对象: mycanvas.getObjects()
获取最后一个对象: var objList = mycanvas.getObjects(),obj = objList[objList.length-1]
获取被选中的对象: mycanvas.getActiveObject()

缩放和旋转没算出来, 代码删了留着日后解决,

思路二 (记录位置) 补充: 鼠标位移留痕不规则线条在如下代码中

方法非常简单, mousedown记录选中矩形状态信息, mouseup检测是否超出, 是则回退至原来的位置

// objectMov 位移
mycanvas.on({'mouse:down': this.mouseDownDrawRect, //按下,'mouse:move': this.mouseMoving, //移动中,'mouse:up': this.mouseUpDrawRect, //抬起,//'mouse:wheel': this.zoomsHandle, //缩放//'object:moved': this.objectMov, //移动完成//'object:moving': this.objectMov,//移动中//'object:scaled': this.mouseScaleDrawRect,//缩放完成//'object:scaling': this.mouseScalingDrawRect,//缩放中//'object:rotated': this.objectRotated,// 旋转完成//'object:rotating': this.objectRotated,// 旋转中});mouseDownDrawRect = () => {_this_obj : JSON.parse(JSON.stringify(mycanvas.getActiveObject()))};mouseMoving = (options) => {// canvasPointSave 在 mouseUpDrawRect 中定义// 判断是否可以留痕if( chousedType == 0 && mycanvas.backgroundImage && canvasPointSave.tl){let ptl = canvasPointSave.tl,pbr = canvasPointSave.br,eposition = options.pointer;if ( eposition.x < ptl.x || eposition.x > pbr.x || eposition.y < ptl.y || eposition.y > pbr.y) {mycanvas.isDrawingMode = false;}else{mycanvas.isDrawingMode = true;}}};mouseUpDrawRect = (options) => {// 如果是鼠标画不规则线条, 则重新判断位置mycanvas.isDrawingMode = false;let canvasObjArr = mycanvas.getObjects();if( chousedType == 0 && canvasObjArr.length > 0){this.referDrawRect();}// 如果是位移,缩放,旋转则计算位置if( options.transform ){if( options.transform.action== "drag"){this.objectMov();}if( options.transform.action== "scaleX" ||  options.transform.action== "scaleY" ||  options.transform.action== "scale"){this.mouseScalingDrawRect();this.objectMov();}if( options.transform.action== "rotate"){this.objectRotated();this.objectMov();}}else if(options.target){this.objectMov();}// 记录位置if(mycanvas.backgroundImage) { canvasPointSave = mycanvas.backgroundImage.lineCoords }};objectMov = (t) => {const { mainCanvas } = this.state;let mycanvas = mainCanvas,oImgList = mycanvas.getObjects(),oImg = t?oImgList[oImgList.length-1]:mycanvas.getActiveObject();if(!oImg) { return false;}let ep = oImg.aCoords,// 目标对象的四个坐标点angle= Math.floor(( oImg.angle ) / 90),// 目标对象的旋转角度ptl = mycanvas.backgroundImage.aCoords.tl,pbr = mycanvas.backgroundImage.aCoords.br,et = 0,el = 0,eb = 0,er = 0,w = 0,h = 0;switch (angle) {case 0:et = ep.tl.y;el = ep.bl.x;eb = ep.br.y;er = ep.tr.x;el < ptl.x && oImg.set({ left:ptl.x + ep.tl.x - ep.bl.x }); // 左侧超出if(er > pbr.x) { // 右侧超出if((ep.tr.x - ep.bl.x) <= (pbr.x - ptl.x)) { oImg.set({ left:pbr.x - (ep.tr.x - ep.tl.x) })}if((ep.tr.x - ep.bl.x) > (pbr.x - ptl.x)) { oImg.set({ left:ptl.x }) }}et < ptl.y && oImg.set({ top:ptl.y })// 上侧超出if(eb > pbr.y) { // 下侧超出if((ep.br.y - ep.tl.y) <= (pbr.y - ptl.y)) { oImg.set({ top:pbr.y - (ep.br.y - ep.tl.y) })}if((ep.br.y - ep.tl.y) > (pbr.y - ptl.y)) { oImg.set({ top:ptl.y }) }}break;case 1:et = ep.bl.y;el = ep.br.x;eb = ep.tr.y;er = ep.tl.x;er > pbr.x && oImg.set({ left:pbr.x  })// 右侧超出el < ptl.x && oImg.set({ left:ptl.x + ep.tl.x - ep.br.x });// 左侧超出et < ptl.y && oImg.set({ top:ptl.y + (ep.tl.y - ep.bl.y) })// 上侧超出eb > pbr.y && oImg.set({ top:pbr.y - (ep.tr.y - ep.tl.y) })// 下侧超出break;case 2:et = ep.br.y;el = ep.tr.x;eb = ep.tl.y;er = ep.bl.x;el < ptl.x && oImg.set({ left:ptl.x + ep.tl.x - ep.tr.x });// 左侧超出er > pbr.x && oImg.set({ left:pbr.x - (ep.bl.x - ep.tl.x)})// 右侧超出et < ptl.y && oImg.set({ top:ptl.y + (ep.tl.y - ep.br.y) })// 上侧超出eb > pbr.y && oImg.set({ top:pbr.y  })// 下侧超出break;case 3:et = ep.tr.y;el = ep.tl.x;eb = ep.bl.y;er = ep.br.x;el < ptl.x && oImg.set({ left:ptl.x }); // 左侧超出er > pbr.x && oImg.set({ left:pbr.x - (ep.br.x - ep.tl.x)})// 右侧超出et < ptl.y && oImg.set({ top:ptl.y + (ep.tl.y - ep.tr.y) })// 上侧超出eb > pbr.y && oImg.set({ top:pbr.y - (ep.bl.y - ep.tl.y) })// 下侧超出}mycanvas.renderAll()}mouseScalingDrawRect = () => {const { mainCanvas,_this_obj } = this.state;let mycanvas = mainCanvas,obj = mycanvas.getActiveObject();if(!obj) { return false;}let ep = obj.aCoords,// 目标对象的四个坐标点angle= Math.floor(( obj.angle ) / 90),et = 0,el = 0,eb = 0,er = 0,w = 0,h = 0;// 目标对象的旋转角度switch (angle) {case 0:et = ep.tl.y;el = ep.bl.x;eb = ep.br.y;er = ep.tr.x;w = ep.tr.x - ep.bl.x ;h = ep.br.y - ep.tl.y ;break;case 1:et = ep.bl.y;el = ep.br.x;eb = ep.tr.y;er = ep.tl.x;w = ep.tl.x - ep.br.x ;h = ep.tr.y - ep.bl.y ;break;case 2:et = ep.br.y;el = ep.tr.x;eb = ep.tl.y;er = ep.bl.x;w = ep.bl.x - ep.tr.x ;h = ep.tl.y - ep.br.y ;break;case 3:et = ep.tr.y;el = ep.tl.x;eb = ep.bl.y;er = ep.br.x;w = ep.br.x - ep.tl.x ;h = ep.bl.y - ep.tr.y ;}if(w > mycanvas.backgroundImage.width || h > mycanvas.backgroundImage.height){obj.set({ scaleY: _this_obj.scaleY,scaleX: _this_obj.scaleX,left: _this_obj.left,top: _this_obj.top,}) }mycanvas.renderAll()}referDrawRect = () => {const { mainCanvas } = this.state;let mycanvas = mainCanvas,objList = mycanvas.getObjects(),obj = objList[objList.length-1];if(!obj) { return false;}let scx = mycanvas.backgroundImage.width / obj.width;// -0.05let scy = mycanvas.backgroundImage.height / obj.height;// -0.05if (obj.scaleX > scx) {obj.set({scaleX:scx})};if (obj.scaleY > scy) {obj.set({scaleY:scy})};mycanvas.renderAll()this.objectMov(true)}objectRotated = () => {const { mainCanvas, _this_obj } = this.state;let mycanvas = mainCanvas,oImg = mycanvas.getActiveObject();// 目标对象if(!oImg) { return false;}let ep = oImg.aCoords,// 目标对象的四个坐标点angle= Math.floor(( oImg.angle ) / 90),// 目标对象的旋转角度ptl = mycanvas.backgroundImage.aCoords.tl,pbr = mycanvas.backgroundImage.aCoords.br,et = 0,el = 0,eb = 0,er = 0,w = 0,h = 0,lock = false;switch (angle) {case 0:et = ep.tl.y;el = ep.bl.x;eb = ep.br.y;er = ep.tr.x;w = ep.tr.x - ep.bl.x ;h = ep.br.y - ep.tl.y ;break;case 1:et = ep.bl.y;el = ep.br.x;eb = ep.tr.y;er = ep.tl.x;w = ep.tl.x - ep.br.x ;h = ep.tr.y - ep.bl.y ;break;case 2:et = ep.br.y;el = ep.tr.x;eb = ep.tl.y;er = ep.bl.x;w = ep.bl.x - ep.tr.x ;h = ep.tl.y - ep.br.y ;break;case 3:et = ep.tr.y;el = ep.tl.x;eb = ep.bl.y;er = ep.br.x;w = ep.br.x - ep.tl.x ;h = ep.bl.y - ep.tr.y ;}if(w > mycanvas.backgroundImage.width || h > mycanvas.backgroundImage.height ) {oImg.set({ angle: _this_obj.angle,left: _this_obj.left,top: _this_obj.top,}) }mycanvas.renderAll()}

注意事项

1, canvas 使用和 img 相关操作时 如果 跨域 需要加上 crossOrigin: 'anonymous' 属性;
2, fabric.js 是根据 html 中 canvas标签的id 来初始化的, 如需 初始化多个fabric , 则需 注意 canvas 标签的 id, 以及 window.全局配置的参数(使用vue和react时)
3, fabric 初始化过后的画布, 如果发生 位移 和 缩放 , 再进行放置对象是要 进行计算的

计算规则如下

// 发生位移计算// 右上角let tr = canvas.backgroundImage.lineCoords.tr;// 右下角let br = canvas.backgroundImage.lineCoords.br;// 左上角let tl = canvas.backgroundImage.lineCoords.tl;// 左下角let bl = canvas.backgroundImage.lineCoords.bl;//判断试卷最右侧 是否离开了可视区域if (tr.x <= 50) {var delta = new fabric.Point(50, 0);relativeMouseX += 50;canvas.relativePan(delta);return;}//判断试卷最上侧 是否离开了可视区域if (tr.y > screenHeight - 50) {var delta = new fabric.Point(0, -50);relativeMouseY -= 50;canvas.relativePan(delta);return;}//判断试卷最下侧 是否离开了可视区域if (br.y <= 50) {var delta = new fabric.Point(0, 50);relativeMouseY += 50;canvas.relativePan(delta);return;}//判断试卷最左侧 是否离开了可视区域if (tl.x > screenWidth - 50) {var delta = new fabric.Point(-50, 0);relativeMouseX -= 50;canvas.relativePan(delta);return;}var delta = new fabric.Point(options.e.movementX, options.e.movementY);canvas.relativePan(delta);relativeMouseX += options.e.movementX; //累计每一次移动时候的偏差relativeMouseY += options.e.movementY;// 发生缩放计算//  缩放代码let wheelPost = e?e.e.deltaY:wheel,pointerX = e.pointer.x,pointerY = e.pointer.y,zoomSpeed = 0.03,zoom, zoomPoint, lastzoom, lastzoomPoint={x:0,y:0}, lastmousePoint={x:0,y:0}, relativeMouseX, relativeMouseY;// 通过zoomSpeed 控制缩放速度zoom = (wheelPost > 0 ? -zoomSpeed : zoomSpeed) + canvas.getZoom();zoom = Math.max(0.1, zoom); //最小为原来的1/10zoom = Math.min(3, zoom); //最大是原来的3倍zoomPoint = new fabric.Point(pointerX, pointerY);canvas.zoomToPoint(zoomPoint, zoom);lastzoomPoint.x =lastzoomPoint.x + (zoomPoint.x - lastmousePoint.x - relativeMouseX) / lastzoom;lastzoomPoint.y =lastzoomPoint.y + (zoomPoint.y - lastmousePoint.y - relativeMouseY) / lastzoom;lastmousePoint.x = zoomPoint.x;lastmousePoint.y = zoomPoint.y;lastzoom = zoom;relativeMouseX = 0;relativeMouseY = 0;// 需要获取值时transformMouse = (mouseX, mouseY) => {let { lastzoomPoint,zoomPoint,relativeMouseX,relativeMouseY } = this.state;let {mainCanvas} = this.state;let mycanvas = mainCanvas;let x = lastzoomPoint.x + (mouseX - zoomPoint.x - relativeMouseX) / mycanvas.getZoom();let y = lastzoomPoint.y + (mouseY - zoomPoint.y - relativeMouseY) / mycanvas.getZoom();return { x, y };};

canvas插件 fabric.js 使用相关推荐

  1. html5 烟雾,jQuery烟雾背景发生器(HTML5 Canvas插件waterpipe.js

    插件描述:waterpipe.js是一个创建烟雾弥漫的背景,有多种效果可选择,自定义参数效果还是挺酷的哦! 使用方法 1.包括jQuery和waterpipe.js 2.创建画布元素内包装 Your  ...

  2. Fabric.js 基础画笔的用法 BaseBrush

    theme: smartblue 本文简介 点赞 + 关注 + 收藏 = 学会了 本文介绍 Fabric.js 的基础笔刷用法.如果你还不知道 Fabric.js 是什么,我墙裂建议你阅读一下 < ...

  3. Canvas实用库Fabric.js使用手册

    简介 什么是Fabric.js? Fabric.js是一个可以简化Canvas程序编写的库. Fabric.js为Canvas提供所缺少的对象模型, svg parser, 交互和一整套其他不可或缺的 ...

  4. 强大的Canvas开源库Fabric.js简介与开发指南

    什么是Fabric.js? Fabric.js 是一个强大且简单的Javascript HTML5 Canvas库. 官网地址:http://fabricjs.com/ 为什么要使用Fabric.js ...

  5. HTML5 Canvas JavaScript库 Fabric.js 使用经验

    首先,表明我的态度:采用 Flash 才是最优方案,不建议使用 HTML 5 的 Canvas 做一些生产/工业级的网页应用. Flash的优势一是浏览器支持好,二是代码成熟稳定.而HTML5 的 C ...

  6. HTML5——Canvas图表插件 chart.js的基础使用

    Canvas图表插件 推荐三种比较好用的. chart.js 灵活.可个性化设置图表样式的js图表插件 chartist.js 配置简单,只需要设置数据即可,包是最小的. HighCharts.js ...

  7. html5游戏开发马赛克对比,基于HTML5 Canvas的纯JS图片马赛克效果插件

    这是一款基于HTML5 canvas的图片马赛克js插件.该图片马赛克插件使用简单,可调整马赛克的大小,透明度等属性,适合用于制作一些特殊的图片效果. 基于HTML5 Canvas的纯JS图片马赛克效 ...

  8. canvas火焰动画、管线流动动画-Fabric.js

    官网 http://fabricjs.com/ 一个canvas框架 项目地址 效果 代码 <template><div class="home">< ...

  9. fabric.js和高级画板

    本文介绍fabric.js框架使用,以及使用fabricjs打造一个高级画板程序. 高级画板功能介绍 全局绘制颜色选择 护眼模式.网格模式切换 自由绘制 画箭头 画直线 画虚线 画圆/椭圆/矩形/直角 ...

最新文章

  1. 【elementUI】el-tree搜索时加载子节点对应父节点、父节点对应子节点树
  2. xampp测试php代码,php用xampp测试
  3. sun.jersey使用Jackson转换数据
  4. Google News 中文上线
  5. Intellij IDEA(Android Studio)好用的插件和软件
  6. php pecl memcached,php – 安装PECL Memcached错误
  7. python爬虫-初步使用Scrapy分布式爬虫(爬取mcbbs整合包保存名称及主要mod),大爱MC
  8. (二)先看几个面试题
  9. java concurrent包介绍及使用
  10. Activity的属性taskAffinity
  11. html文件用excel打开乱码,都是兼容惹的祸 用excel打开xls文件出现乱码的解决方法...
  12. 算法精解:C语言描述
  13. 最全面SpringCloud 教程-转自方志朋
  14. 如何在网页浏览器中缩放网页?
  15. 简易智能自动问答机器人
  16. NO.4 项目无法一键打包?自己写个shell脚本吧
  17. mcnpf5输出结果_MCNP使用教程
  18. Java抽象类和接口使用_Java 抽象类和接口
  19. 当Mac电脑遇到ANDROID_NDK_HOME not defined...报错
  20. 【题解】P2324[SCOI2005] 骑士精神

热门文章

  1. 电池pack结构_详解锂电池pack基础知识,18650锂电池pack工艺技巧总结分析
  2. 揭秘:抖音1元秒杀卖货玩法背后的内幕
  3. 小巧精致的“隐形”耳机,让你排除干扰安心入眠,Wedoking 二代 无线睡眠耳机上手
  4. java8 Stream的基本操作
  5. RAID 5数据恢复图解
  6. ios莫名其妙闪退的解决方法
  7. 那些年,我们一起写的设计模式(一)——单例模式(Singleton Pattern)
  8. Spring之魔丸降世
  9. 跟着Leo机器学习:sklearn之 Gaussian Processes
  10. C语言中的 |= 意思