index.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. import fabric from '../../js/fabric-adapter'
  2. import del from './images/sc.svg'
  3. import rotate from './images/xz.svg'
  4. import symmetry from './images/jx.svg'
  5. import jia from './images/jia.svg'
  6. import jian from './images/jian.svg'
  7. import pingpu from './images/pingpu.svg'
  8. import { uploadBaseImg } from '@/apis/other'
  9. export default {
  10. data() {
  11. return {
  12. action: 'select',
  13. drawWidth: 20,
  14. eraseOpacity:1, //橡皮擦 透明度
  15. min: 2,
  16. max: 80,
  17. color: '#ffffff',
  18. //撤销
  19. canvasState: [],//操作记录
  20. stateIndex: -1,
  21. canUndoState:false,
  22. //画笔在层上操作
  23. group:null,
  24. arr:[],
  25. }
  26. },
  27. props: {
  28. toolConfig: {
  29. type: Array,
  30. default: () => ['select','color','draw','eraser','empty','undo']
  31. },
  32. controlToolConfig: {
  33. type: Array,
  34. default: () => ['del','rotate','symmetry']
  35. },
  36. defaultColor:{ //画笔默认颜色
  37. type: String,
  38. default:'#ffffff'
  39. }
  40. },
  41. computed: {
  42. //是否可以撤销
  43. canUndo() {
  44. return this.stateIndex >= 0;
  45. },
  46. },
  47. watch: {
  48. action(val,old){
  49. if(old==='color' && this.showStraw){
  50. this.hideColorXg()
  51. }
  52. },
  53. drawWidth() {
  54. if (this.fcanvas){
  55. this.fcanvas.freeDrawingBrush.width = this.drawWidth
  56. this.setCursor()
  57. }
  58. },
  59. color() {
  60. if (this.fcanvas) {
  61. this.fcanvas.freeDrawingBrush.color = this.color
  62. this.toDraw()
  63. }
  64. },
  65. eraseOpacity(){
  66. if (this.fcanvas) {
  67. this.fcanvas.freeDrawingBrush.opacity = this.eraseOpacity
  68. this.setCursor()
  69. }
  70. }
  71. },
  72. created() {
  73. this.color = this.defaultColor
  74. },
  75. activated() {
  76. //设置控件 添加镜像删除等icon
  77. this.setControlTool();
  78. },
  79. mounted() {
  80. //设置控件 添加镜像删除等icon
  81. this.setControlTool();
  82. },
  83. methods: {
  84. actionInit(){
  85. //监听撤销
  86. if(this.toolConfig.includes('undo')){
  87. this.setUndo()
  88. }
  89. //修改画笔 每次在此图层上操作
  90. if(this.toolConfig.includes('draw')){
  91. this.fcanvas.on('path:created', (opt)=>{
  92. if(this.action === 'erase') {
  93. this.selected.map(item=>{
  94. this.updateLayerCover(item)
  95. })
  96. this.getLayers()
  97. return;
  98. }
  99. if(this.action !== 'draw') return;
  100. let object = this.fcanvas.getObjects()
  101. let path = object[object.length-1]
  102. let selected = []
  103. let name = 'pencli'
  104. let sort = null
  105. let id = null
  106. if(this.selected.length === 1){
  107. name = this.selected[0].name
  108. sort = this.selected[0].sort
  109. id = this.selected[0].id
  110. selected = this.selected
  111. }else{
  112. id = this.getNextLayersId()
  113. sort = this.getNextLayersSort()
  114. }
  115. selected.push(path)
  116. var sel = new fabric.ActiveSelection(selected, {
  117. canvas: this.fcanvas,
  118. });
  119. this.fcanvas.setActiveObject(sel);
  120. // this.canUndoState = true;
  121. const group = this.fcanvas.getActiveObject().toGroup();
  122. group.setControlsVisibility({
  123. 'mtr':false,
  124. })
  125. group.set({
  126. name,
  127. sort,
  128. id,
  129. erasable:false,
  130. })
  131. group.moveTo(sort)
  132. this.fcanvas.discardActiveObject()
  133. this.selected = [group]
  134. // this.updateCanvasState();
  135. this.getLayers()
  136. })
  137. }
  138. this.fcanvas.on('mouse:down', (opt)=>{
  139. if(this.action !== 'erase') return;
  140. this.toEraseInLayer();
  141. })
  142. //设置默认笔画
  143. setTimeout(() => {
  144. this.fcanvas.freeDrawingBrush.width = this.drawWidth
  145. this.fcanvas.freeDrawingBrush.color = this.color
  146. }, 100)
  147. },
  148. // 新增图片图层
  149. async addMaps(url, options = {
  150. left:50,
  151. bottom:50,
  152. }, ) {
  153. return new Promise((resolve, reject) => {
  154. if(!this.fcanvas.add){
  155. reject(false);
  156. }
  157. fabric.Image.fromURL(url, (img) => {
  158. let sort = options.sort || this.getNextLayersSort()
  159. let erasable = options.erasable === false ? false : true
  160. let selectable = options.selectable === false ? false : true
  161. let id = this.getNextLayersId()
  162. this.fcanvas.discardActiveObject()
  163. img.setControlsVisibility({
  164. 'mtr':false,
  165. })
  166. this.fcanvas.add(img.set({
  167. name:"image",
  168. ...options,
  169. sort,
  170. id,
  171. erasable:erasable,
  172. }))
  173. img.moveTo(sort)
  174. if(img.getScaledWidth() > options.maxWidth){
  175. let zoom = this.fcanvas.getZoom()
  176. img.scaleToWidth(options.maxWidth * this.fcanvas.getZoom())
  177. }
  178. if(img.getScaledHeight() > options.maxHeight){
  179. img.scaleToHeight(options.maxHeight * this.fcanvas.getZoom())
  180. }
  181. if(selectable) this.fcanvas.setActiveObject(img)
  182. this.toSelect()
  183. this.getLayers()
  184. if(selectable) this.select(img)
  185. this.fcanvas.renderAll();
  186. console.log(img)
  187. resolve(img)
  188. }, {
  189. crossOrigin: 'anonymous',
  190. })
  191. })
  192. },
  193. //新增 svg
  194. addSvg(url, options = {
  195. left:50,
  196. top:50,
  197. width:200,
  198. height:200
  199. },){
  200. if(!this.fcanvas.add){
  201. return;
  202. }
  203. fabric.loadSVGFromURL(url, (objects,options) => {
  204. const svg = fabric.util.groupSVGElements(objects, options);
  205. let sort = options.sort || this.getNextLayersSort()
  206. let erasable = options.erasable || false
  207. let id = this.getNextLayersId()
  208. this.fcanvas.discardActiveObject()
  209. svg.setControlsVisibility({
  210. 'mtr':false,
  211. })
  212. this.fcanvas.add(svg.set({
  213. ...options,
  214. name:"svg",
  215. sort,
  216. id,
  217. erasable:erasable,
  218. }))
  219. svg.moveTo(sort)
  220. this.fcanvas.setActiveObject(svg)
  221. this.toSelect()
  222. this.getLayers()
  223. this.select(svg)
  224. this.fcanvas.renderAll();
  225. })
  226. },
  227. //新增文字图层
  228. addText( options = {
  229. left:50,
  230. top:50,
  231. },){
  232. if(!this.fcanvas.add){
  233. return;
  234. }
  235. const textbox = new fabric.Textbox('', {
  236. left: 50,
  237. top: 50,
  238. width: 150,
  239. fontSize: 20
  240. });
  241. let sort = options.sort || this.getNextLayersSort()
  242. let erasable = options.erasable || false
  243. let id = this.getNextLayersId()
  244. this.fcanvas.discardActiveObject()
  245. this.fcanvas.add(textbox.set({
  246. ...options,
  247. name:"text",
  248. sort,
  249. id,
  250. erasable:erasable,
  251. }))
  252. textbox.moveTo(sort)
  253. this.fcanvas.setActiveObject(textbox)
  254. this.toSelect()
  255. this.getLayers()
  256. this.select(textbox)
  257. textbox.enterEditing();
  258. this.fcanvas.renderAll();
  259. },
  260. // 选择
  261. toSelect() {
  262. this.action = 'select'
  263. this.fcanvas.isDrawingMode = false
  264. },
  265. async setCursor(){
  266. if(!['draw','erase'].includes(this.action)) return
  267. let fcanvasZoom = this.fcanvas.getZoom()
  268. let deviceZoom = detectZoom.device()
  269. let zoom = fcanvasZoom/deviceZoom;
  270. const canvas = document.createElement('canvas')
  271. const fcanvas = new fabric.Canvas(canvas, {
  272. containerClass:"fcanvas",
  273. // 元素对象被选中时保持在当前z轴,不会跳到最顶层
  274. preserveObjectStacking:true,
  275. width: this.drawWidth*zoom,
  276. height: this.drawWidth*zoom,
  277. })
  278. const color = this.action === 'draw' ? this.color : '#ffffff';
  279. var rect = new fabric.Circle({
  280. radius: (this.drawWidth*zoom)/2,
  281. fill:color,
  282. left: 0,
  283. opacity: 1,
  284. strokeWidth: 0,
  285. strokeUniform: true
  286. });
  287. fcanvas.add(rect);
  288. const base64 = await fcanvas.toDataURL({
  289. format: 'png',
  290. enableRetinaScaling: true
  291. })
  292. this.fcanvas.freeDrawingCursor = `url( ${base64} ) ${(this.drawWidth*fcanvasZoom)/2} ${(this.drawWidth*fcanvasZoom)/2}, auto`;
  293. },
  294. // 画笔
  295. toDraw() {
  296. this.action = 'draw'
  297. this.fcanvas.freeDrawingBrush = new fabric.PencilBrush(this.fcanvas)
  298. this.fcanvas.freeDrawingBrush.canvas.selectable = false;
  299. this.setFreeDrawingBrushStyle()
  300. this.setCursor()
  301. this.fcanvas.renderAll();
  302. },
  303. // 橡皮擦
  304. toErase() {
  305. this.action = 'erase'
  306. console.log(this.fcanvas.getObjects())
  307. this.fcanvas.freeDrawingBrush = new fabric.EraserBrush(this.fcanvas)
  308. this.toEraseInLayer();
  309. this.setFreeDrawingBrushStyle()
  310. this.setCursor()
  311. },
  312. //拖拽
  313. toDrag(){
  314. this.action = 'drag'
  315. this.fcanvas.isDrawingMode = false
  316. this.fcanvas.hoverCursor = 'grab'
  317. },
  318. //设置橡皮擦作用图层
  319. toEraseInLayer(){
  320. this.fcanvas.getObjects().map(item=>{
  321. if(this.selectedIds.includes(item.id)){
  322. item.set({
  323. erasable:true,
  324. })
  325. }else{
  326. item.set({
  327. erasable:false,
  328. })
  329. }
  330. })
  331. },
  332. // 清空
  333. setEmpty() {
  334. this.fcanvas.remove.apply(this.fcanvas, this.fcanvas.getObjects())
  335. this.getLayers()
  336. this.undoAfterSelectLayers();
  337. },
  338. //合并组
  339. toGroup(){
  340. this.fcanvas.getActiveObject().toGroup();
  341. this.fcanvas.requestRenderAll();
  342. },
  343. // 设置画笔样式
  344. setFreeDrawingBrushStyle() {
  345. if (!this.fcanvas) return
  346. this.fcanvas.freeDrawingBrush.width = this.drawWidth
  347. this.fcanvas.freeDrawingBrush.color = this.color
  348. this.fcanvas.freeDrawingBrush.opacity = this.eraseOpacity
  349. this.fcanvas.isDrawingMode = true
  350. },
  351. //设置控件
  352. setControlTool(){
  353. let self = this;
  354. //修改 controls 默认样式
  355. fabric.Object.prototype.transparentCorners = false;
  356. fabric.Object.prototype.cornerColor = '#333333';
  357. fabric.Object.prototype.cornerStrokeColor = '#333333';
  358. fabric.Object.prototype.borderColor = '#000000';
  359. fabric.Object.prototype.cornerStyle = 'circle';
  360. fabric.Textbox.prototype.transparentCorners = false;
  361. fabric.Textbox.prototype.cornerColor = '#333333';
  362. fabric.Textbox.prototype.cornerStrokeColor = '#333333';
  363. fabric.Textbox.prototype.borderColor = '#000000';
  364. fabric.Textbox.prototype.cornerStyle = 'circle';
  365. //删除控件, 每次 重新计算
  366. let controlToolConfigFun = {
  367. del:'deleteControl',
  368. rotate:'rotateControl',
  369. tile:"tileControl",
  370. tileIconjian:"tileIconjianControl",
  371. symmetry:'symmetryControl',
  372. }
  373. Object.keys(controlToolConfigFun).map(item=>{
  374. delete fabric.Object.prototype.controls[controlToolConfigFun[item]]
  375. delete fabric.Textbox.prototype.controls[controlToolConfigFun[item]]
  376. })
  377. delete fabric.Object.prototype.controls.tileIconppControl
  378. delete fabric.Textbox.prototype.controls.tileIconppControl
  379. if(this.controlToolConfig.includes('del')) setToolDel()
  380. if(this.controlToolConfig.includes('rotate')) setToolRotate()
  381. if(this.controlToolConfig.includes('symmetry')) setToolSymmetry()
  382. if(this.controlToolConfig.includes('tile')) setTooTile()
  383. // 增加图标
  384. function renderIcon(icon) {
  385. return function renderIcon(ctx, left, top, styleOverride, fabricObject) {
  386. var size = this.cornerSize
  387. ctx.save()
  388. ctx.translate(left, top)
  389. ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
  390. ctx.drawImage(icon, -size / 2, -size / 2, size, size)
  391. ctx.restore()
  392. }
  393. }
  394. // 删除
  395. function setToolDel() {
  396. var icon = document.createElement('img')
  397. icon.src = del
  398. icon.title = '删除'
  399. fabric.Object.prototype.controls.deleteControl = new fabric.Control({
  400. x: -0.5,
  401. y: -0.5,
  402. offsetY: -25,
  403. cursorStyle:"Pointer",
  404. mouseUpHandler: deleteObject,
  405. render: renderIcon(icon),
  406. cornerSize: 20
  407. })
  408. fabric.Textbox.prototype.controls.deleteControl = fabric.Object.prototype.controls.deleteControl
  409. function deleteObject(eventData, transform) {
  410. try {
  411. var target = transform.target
  412. var canvas = target.canvas
  413. if(target.delable === false){
  414. self.$message.warning('该元素无法被删除')
  415. return;
  416. }
  417. canvas.remove(target)
  418. if(target.hasOwnProperty('_objects') && target.name !== 'svg'){
  419. target._objects.map(item=>{
  420. if(item.delable !== false){
  421. canvas.remove(item)
  422. }
  423. })
  424. canvas.discardActiveObject()
  425. }
  426. canvas.requestRenderAll()
  427. self.getLayers()
  428. self.undoAfterSelectLayers();
  429. }catch (e) {
  430. console.log(e)
  431. }
  432. }
  433. }
  434. // 旋转
  435. function setToolRotate() {
  436. var icon = document.createElement('img')
  437. icon.src = rotate
  438. icon.title = '旋转'
  439. fabric.Object.prototype.controls.rotateControl = new fabric.Control({
  440. x: 0,
  441. y: -0.5,
  442. actionHandler: fabric.controlsUtils.rotationWithSnapping,
  443. cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler,
  444. offsetY: -40,
  445. withConnection: true,
  446. actionName: 'rotate',
  447. // 渲染图标
  448. cursorStyle:"Pointer",
  449. render: renderIcon(icon),
  450. // 设置控制点大小
  451. cornerSize: 20
  452. })
  453. fabric.Textbox.prototype.controls.rotateControl = fabric.Object.prototype.controls.rotateControl
  454. }
  455. // 镜像
  456. function setToolSymmetry() {
  457. let icon = document.createElement('img')
  458. icon.src = symmetry
  459. icon.title = '镜像'
  460. fabric.Object.prototype.controls.symmetryControl = new fabric.Control({
  461. x: 0.5,
  462. y: -0.5,
  463. offsetY: -25,
  464. offsetX: 0,
  465. cursorStyle:"Pointer",
  466. mouseUpHandler: deleteObject,
  467. render: renderIcon(icon),
  468. cornerSize: 20
  469. })
  470. fabric.Textbox.prototype.controls.symmetryControl = fabric.Object.prototype.controls.symmetryControl
  471. function deleteObject(eventData, transform) {
  472. var target = transform.target
  473. target.set({
  474. flipX:!target.flipX,
  475. })
  476. var canvas = target.canvas
  477. self.updateCanvasState()
  478. canvas.requestRenderAll()
  479. }
  480. }
  481. // 平铺
  482. function setTooTile() {
  483. let iconpp = document.createElement('img')
  484. iconpp.src = pingpu
  485. iconpp.title = '平铺'
  486. fabric.Object.prototype.controls.tileIconppControl = new fabric.Control({
  487. x: -0.5,
  488. y: -0.5,
  489. offsetX: 30,
  490. offsetY: -25,
  491. cursorStyle:"default",
  492. render: renderIcon(iconpp),
  493. cornerSize: 60
  494. })
  495. let icon = document.createElement('img')
  496. icon.src = jia
  497. icon.title = '平铺加'
  498. fabric.Object.prototype.controls.tileControl = new fabric.Control({
  499. x: -0.5,
  500. y: -0.5,
  501. offsetX: 60,
  502. offsetY: -25,
  503. cursorStyle:"Pointer",
  504. mouseUpHandler: tileObject,
  505. render: renderIcon(icon),
  506. cornerSize: 20
  507. })
  508. let iconjian = document.createElement('img')
  509. iconjian.src = jian
  510. iconjian.title = '平铺减'
  511. fabric.Object.prototype.controls.tileIconjianControl = new fabric.Control({
  512. x: -0.5,
  513. y: -0.5,
  514. offsetX: 0,
  515. offsetY: -25,
  516. cursorStyle:"Pointer",
  517. mouseUpHandler: tileObject,
  518. render: renderIcon(iconjian),
  519. cornerSize: 20
  520. })
  521. async function tileObject(eventData, transform) {
  522. var target = transform.target
  523. if(target.name !== 'image' || !target.tile ) return;
  524. if(target.tile.src){
  525. let multiple = target.tile.multiple + 1
  526. if(transform.corner === 'tileIconjianControl'){
  527. if(target.tile.multiple === 1){
  528. self.$message.error('无法在放大/缩小了')
  529. return
  530. }
  531. multiple = Math.max(target.tile.multiple - 1,1)
  532. }else{
  533. if(target.tile.multiple === 20){
  534. self.$message.error('无法在放大/缩小了')
  535. return
  536. }
  537. }
  538. let thisOssOptios = {
  539. ...target.tile.ossOptios,
  540. w:parseInt((target.tile.ossOptios.w)/multiple),
  541. h:parseInt((target.tile.ossOptios.h)/multiple)
  542. }
  543. let multipleImgSrc = self.$appfun.ossResize(target.tile.src,thisOssOptios)
  544. const image = await self.$appfun.loadingImg(multipleImgSrc)
  545. const canvas = document.createElement('canvas')
  546. canvas.width = target.width
  547. canvas.height = target.height
  548. const ctx = canvas.getContext('2d')
  549. var pat = ctx.createPattern(image,"repeat");
  550. ctx.rect(0,0,canvas.width,canvas.height);
  551. ctx.fillStyle = pat;
  552. ctx.fill();
  553. const url = canvas.toDataURL('image/png')
  554. target.setSrc(url,()=>{
  555. target.tile.multiple = multiple
  556. self.updateCanvasState()
  557. target.canvas.requestRenderAll()
  558. })
  559. }
  560. }
  561. }
  562. },
  563. //撤销
  564. historyState(index){
  565. if (this.canUndo) {
  566. this.canUndoState = true;
  567. this.fcanvas.loadFromJSON(this.canvasState[index], () => {
  568. this.fcanvas.renderAll();
  569. this.stateIndex = index;
  570. this.canvasState.splice(this.stateIndex+1);
  571. this.getLayers()
  572. this.undoAfterSelectLayers();
  573. this.canUndoState = false;
  574. });
  575. }
  576. },
  577. setUndo(){
  578. //监听撤销
  579. this.fcanvas.on("object:modified", ()=>{
  580. this.updateCanvasState()
  581. });
  582. },
  583. updateCanvasState(e) {
  584. if (!this.canUndoState) {
  585. const canvasAsJson = JSON.stringify(this.fcanvas.toJSON(['name','sort','mtr','id','selectable','erasable']));
  586. this.canvasState.splice(this.stateIndex+1);
  587. this.canvasState.push(canvasAsJson);
  588. this.stateIndex = this.canvasState.length - 1;
  589. // if(this.viewStatus) this.updateMiniMap();
  590. } else {
  591. this.canUndoState = false;
  592. }
  593. },
  594. }
  595. }