LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

vue+fabric.js實(shí)現(xiàn)簡易的圖文編輯器

freeflydom
2024年10月25日 10:40 本文熱度 858

前言

通過vue2和fabric.js實(shí)現(xiàn)一個簡易的圖文編輯器,可以在畫布上添加文字,圖片,設(shè)置背景圖,對文字,圖片的屬性進(jìn)行修改。最后生成圖片。至于畫布上對選中的對象進(jìn)行拖動,縮放,旋轉(zhuǎn),這些能力fabric本身已經(jīng)支持。

1 創(chuàng)建一個vue項(xiàng)目

2 安裝fabric.js

建議使用4或5版本,最新版學(xué)習(xí)成本較高,相關(guān)經(jīng)驗(yàn)文檔少。

npm install fabric@4.6.0

3核心代碼

頁面基本結(jié)構(gòu)

頁面左側(cè)為添加元素區(qū)域,可添加文字,圖片等元素。中間為畫布。右側(cè)對選中的元素的屬性進(jìn)行修改。

具體代碼可參考源碼。項(xiàng)目中我用的node版本是18.17.1。

<div class="editor">

    <div class="sidebar left">

      <button @click="addText">添加文本</button>

      <button @click="addImage">添加圖片</button>

      <button @click="setBackgroundImage">添加背景圖</button>

    </div>

    <canvas id="c" width="600" height="600" class="canvas"></canvas>

    <div class="sidebar right">

      <!-- 右側(cè)屬性面板 -->

       <div v-if="selectedObject">

        屬性修改...

       </div>

    </div>

</div>

 

初始化畫布

首先確保 頁面中已經(jīng)有canvas標(biāo)簽。

data中定義需要用到到參數(shù)

data() {

    return {

      canvas: null,

      selectedObject: null, // 當(dāng)前選中的元素對象

      canvasWidth: 800, // 初始畫布寬度  

      canvasHeight: 600, // 初始畫布高度

      canvasBackgroundColor: '#FFF', // 初始畫布背景色

    };

},

在mounted鉤子函數(shù)中創(chuàng)建了Fabric.js畫布并監(jiān)聽鼠標(biāo)點(diǎn)擊

創(chuàng)建fabric畫布,并指定背景色,大小。

監(jiān)聽mouse:up事件,點(diǎn)擊畫布上的元素時(shí),更新selectedObject,selectedObject對象表示當(dāng)前選中元素的屬性。

mounted() {

    this.$nextTick(() => {

      this.initCanvas();

    })

},

initCanvas() {

  // 創(chuàng)建畫布

  this.canvas = new fabric.Canvas('c',{

    backgroundColor: this.canvasBackgroundColor,

    width: 800,

    height: 576,

  });

  

  // 監(jiān)聽點(diǎn)擊

  this.canvas.on('mouse:up', (e) => {

    if (e.target) {

      this.selectedObject = e.target;

    } else {

      this.selectedObject = null

    }

  });

},

添加文本

addText() {

  const text = new fabric.IText('點(diǎn)擊編輯', {

    left: 100,

    top: 100,

    fontSize: 30,

    fontFamily: 'arial', // 字體

    fill: '#333', // 顏色

    originX: 'left',

    originY: 'top',

    

  });

  this.canvas.add(text);

},

以上只添加了文本的基本屬性,除此之外還有一些常用屬性:

editable:是否可編輯,值為布爾值;

lockUniScaling:控制四個正方向縮放,值為布爾值;

lockScalingX: 禁止橫向縮放,值為布爾值;

lockScalingY: 禁止縱向縮放,值為布爾值;

同時(shí)還可以添加自定義的屬性,例如我在添加文本元素時(shí),自定義了屬性system_name。

addText() {

  const text = new fabric.IText('當(dāng)前日期', {

    ......

    system_name: 'current_date',

  });

  this.canvas.add(text);

},

添加圖片

addImage() {

  fabric.Image.fromURL('圖片url', (img) => {

    img.set({

      left: 100,

      top: 100,

      angle: 0,  // 你可以根據(jù)需要調(diào)整圖片的旋轉(zhuǎn)角度  

    });

    // 將圖片添加到畫布  

    this.canvas.add(img);

    // 重新渲染畫布以顯示新添加的圖片  

    this.canvas.renderAll();

  }, { crossOrigin: 'anonymous' });

},

fabric.Image.fromURL('圖片url', callback, options) 方法用于從指定的 URL 加載圖片。這個方法接受三個參數(shù)。

'圖片url':圖片的 URL 地址。

callback(img):一個回調(diào)函數(shù),當(dāng)圖片加載完成后執(zhí)行。img 參數(shù)是加載后的 Fabric.js 圖片對象。

options:一個對象,包含加載圖片時(shí)的選項(xiàng)。在這個例子中,設(shè)置了 { crossOrigin: 'anonymous' },這允許跨域加載圖片,避免在加載跨域圖片時(shí)出現(xiàn) CORS(跨源資源共享)錯誤。

如果我希望添加的圖片不超出畫布,同時(shí)居中。上述代碼可以這樣改進(jìn):

計(jì)算縮放因子scale并應(yīng)用,保證圖片最長的邊不會超出畫布。 

addImage() {

  fabric.Image.fromURL('圖片url', (img) => {

    // 獲取畫布的寬高

    const canvasWidth = this.canvas.getWidth();

    const canvasHeight = this.canvas.getHeight();

    const maxWidth = canvasWidth * 0.6; // 計(jì)算圖片允許的最大寬度  

    const maxHeight = canvasHeight * 0.3; // 計(jì)算圖片允許的最大高度 

    // 計(jì)算縮放比例   

    let scale = Math.min(maxWidth / img.width, maxHeight / img.height);   

    // 應(yīng)用縮放比例  

    img.scale(scale).set({

      left: (canvasWidth - img.width * scale) / 2, // 居中圖片  

      top: (canvasHeight - img.height * scale) / 2,

    });

    // 將圖片添加到畫布  

    this.canvas.add(img);

    // 重新渲染畫布以顯示新添加的圖片  

    this.canvas.renderAll();

  }, { crossOrigin: 'anonymous' });

},

設(shè)置背景圖

設(shè)置背景圖有兩種實(shí)現(xiàn)方式,

1 通過canvas.setBackgroundImage()設(shè)置;

2 添加一個圖片,寬高與畫布大小一致,將其放在所有其他對象的底層,并禁止選中、觸發(fā)事件。

我這里使用的是第二種方法。

setBackgroundImage(imageUrl) {

  fabric.Image.fromURL(imageUrl, (img) => {

    // 獲取畫布的寬度和高度  

    const canvasWidth = this.canvas.getWidth();  

    const canvasHeight = this.canvas.getHeight();  

    // 直接設(shè)置圖像的寬度和高度為畫布的寬度和高度  

    img.set({

      left: 0, 

      top: 0,

      scaleX: canvasWidth / img.width,

      scaleY: canvasHeight / img.height,  

      selectable: false, // 讓背景圖不可選  

      evented: false,     // 讓背景圖不觸發(fā)事件  

      is_background: true // 自定義屬性,背景圖標(biāo)識 區(qū)別于普通圖片元素  

    }); 

    // 將背景圖添加到畫布上  

    this.canvas.add(img);

    // 將背景圖放在所有其他對象的底層  

    this.canvas.sendToBack(img);

    // 重新渲染畫布  

    this.canvas.renderAll();

  }, { crossOrigin: 'anonymous' });

},

上述方法可以實(shí)現(xiàn)設(shè)置背景圖,但如果我已經(jīng)設(shè)置了背景圖,現(xiàn)在又想替換其他背景圖時(shí)。由于新添加的背景圖被放到了最底層,舊的背景圖還沒刪除掉,舊圖覆蓋在新的背景圖上,所以上述代碼需要優(yōu)化,

解決方法:

因?yàn)槲以谔砑颖尘皥D時(shí)自定義了屬性is_background,所以每次添加背景圖先前遍歷畫布上的元素,如果is_background屬性為true則刪除它。然后再添加背景圖。完整邏輯如下

setBackgroundImage(imageUrl) {

  fabric.Image.fromURL(imageUrl, (img) => {

    // 獲取畫布的寬度和高度  

    const canvasWidth = this.canvas.getWidth();  

    const canvasHeight = this.canvas.getHeight();  

    // 直接設(shè)置圖像的寬度和高度為畫布的寬度和高度  

    img.set({

      left: 0, 

      top: 0,

      scaleX: canvasWidth / img.width,

      scaleY: canvasHeight / img.height,  

      selectable: false, // 讓背景圖不可選  

      evented: false,     // 讓背景圖不觸發(fā)事件  

      is_background: true // 背景圖標(biāo)識  

    }); 

    // 遍歷畫布上的所有對象,查找并刪除已存在的背景圖  

    this.canvas.getObjects().forEach((obj) => {  

      if (obj.is_background) {  

        this.canvas.remove(obj);  

      }  

    });

    // 將背景圖添加到畫布上  

    this.canvas.add(img);

    // 將背景圖放在所有其他對象的底層  

    this.canvas.sendToBack(img);

    // 重新渲染畫布  

    this.canvas.renderAll();

  }, { crossOrigin: 'anonymous' });

},

修改屬性

選中元素時(shí),selectedObject表示選中的對象,此時(shí)右側(cè)顯示相應(yīng)的屬性值修改框。

通過selectedObject.type區(qū)分文本或圖片。i-text為文本,image為圖片

文字常見屬性修改:

字體顏色:fill;

字體大小:fontSize;

字體粗細(xì):fontWeight,常規(guī)為normal,加粗bold;

字體風(fēng)格:fontStyle,常規(guī)為normal,斜體italic;

下劃線:underline,布爾值;

刪除線:linethrough,布爾值;

 

<!-- 字體屬性修改 -->

<div v-if="selectedObject.type === 'i-text'">

    <div class="style-title">顏色:</div>

    <el-color-picker style="width: 100%;" v-model="selectedObject.fill" @change="updateColor"></el-color-picker>

    <div class="style-title">字體大小:</div>

    <el-input-number  size="small" v-model="selectedObject.fontSize" controls-position="right" @change="canvasRender" :min="1"></el-input-number>

    <div class="style-title">常用屬性:</div>

    <!-- 加粗,斜體,下劃線,刪除線 -->

    <div class="font-style">

      <i class="fa fa-bold" @click="updateTextProps('bold')"></i>

      <i class="fa fa-italic" @click="updateTextProps('italic')"></i>

      <i class="fa fa-underline" @click="updateTextProps('underline')"></i>

      <i class="fa fa-strikethrough" @click="updateTextProps('linethrough')"></i>

    </div>

</div>

......

methods: {

    // 修改顏色

    updateColor(newColor) {

      this.selectedObject.fill = newColor

      this.selectedObject.dirty = true;

      this.canvas.renderAll();

    },

    

    // 渲染畫布

    canvasRender(e) {

      this.canvas.renderAll();

    },

    

    // 修改文字屬性

    updateTextProps(type) {

      if(type == 'bold') {

      // 加粗

        this.selectedObject.fontWeight = this.selectedObject.fontWeight == 'normal' ? 'bold' : 'normal'

      }

      if(type == 'italic') {

      // 斜體

        this.selectedObject.fontStyle = this.selectedObject.fontStyle == 'normal' ? 'italic' : 'normal'

      }

      if(type == 'linethrough') {

      // 刪除線

        this.selectedObject.linethrough = !this.selectedObject.linethrough

      }

      if(type == 'underline') {

      // 下劃線

        this.selectedObject.underline = !this.selectedObject.underline

      }

      this.selectedObject.dirty = true;

      this.canvas.renderAll();

    },

}

圖片常見屬性修改

圖片我主要做了尺寸的修改。

 

直接修改width,height并不能改變圖片的大小。這是圖片本身的物理大小,不能修改的。畫布上展現(xiàn)的圖片實(shí)際大小為物理大小*縮放比,例如寬默認(rèn)為:width*scaleX,高為height*scaleY。修改圖片尺寸,實(shí)際上就是修改縮放比scale。因此,在添加圖片時(shí),我需要為圖片添加兩個自定義屬性,來表示圖片在畫布上的實(shí)際大小。添加自定義屬性scaleWidth,scaleHeight,表示縮放后圖片的實(shí)際大小。上述添加圖片的方法可做以下優(yōu)化:

addImage() {

  fabric.Image.fromURL('圖片url', (img) => {

    ......

    img.scale(scale).set({

      ......

      scaleWidth: img.width * scale,

      scaleHeight: img.height * scale

    });

    ......

  }, { crossOrigin: 'anonymous' });

},

接下來,在右側(cè)屬性編輯區(qū)修改圖片尺寸,實(shí)際上要根據(jù)修改后的尺寸scaleWidth去計(jì)算新的縮放比scaleX,height同理。然后更新圖片屬性的scaleX,scaleY屬性即可。直接用鼠標(biāo)拖拽圖片的邊去進(jìn)行縮放也是在修改scaleX,scaleY。

<!-- 圖片屬性修改 -->

<div v-if="selectedObject.type === 'image'">

    <div class="style-title">尺寸:</div>  

    <div class="place-line">

      <el-input-number v-model="selectedObject.scaleWidth" size="small" placeholder="寬度" controls-position="right" @change="updateImgScale" :min="1" :max="1000" :precision="0" /> 

      <el-input-number v-model="selectedObject.scaleHeight" size="small" placeholder="高度" controls-position="right" @change="updateImgScale" :min="1" :max="1000" :precision="0" /> 

    </div>

</div>

methods: {

    // 縮放圖片

    updateImgScale() { 

      const newScaleX = this.selectedObject.scaleWidth / this.selectedObject.width; 

      const newScaleY = this.selectedObject.scaleHeight / this.selectedObject.height; 

      this.selectedObject.set({

        scaleX: newScaleX,

        scaleY: newScaleY,

      })

      this.canvas.renderAll();

    },

}

刪除

將當(dāng)前選中的對象或?qū)ο蟮募蟿h除。我這里用到的是getActiveObjects,獲取所有選中的對象,遍歷并通過canvas.remove()全部刪除,如果想獲取單個對象,可以用this.canvas.getActiveObject()。

<i class="el-icon-delete" style="color: red;" @click="deleteSelectedObjects"> 

刪除</i>

......

// 刪除元素

deleteSelectedObjects() {

  const selectedObjects = this.canvas.getActiveObjects();

  if (selectedObjects.length > 0) {

    selectedObjects.forEach(obj => {

      this.canvas.remove(obj);

    });

    this.canvas.renderAll();

    // 清除 selectedObject 引用,如果你需要在其他地方使用它  

    this.selectedObject = null;

  }

},

生成JSON

 

將畫布上的所有內(nèi)容生成JSON文件。通過canvas.toJSON將畫布上的圖形對象轉(zhuǎn)為JSON對象,生成的JSON對象默認(rèn)情況下是包含對象的所有屬性,但自定義屬性我們需要手動指定。this.canvas.toJSON(['屬性A', '屬性B',...])

<el-button type="primary" size="small" @click="exportCanvasAsJSON">生成JSON</el-button>

......

exportCanvasAsJSON() {

  // 獲取畫布上所有對象的JSON表示  

  const jsonData = this.canvas.toJSON(['selectable', 'evented', 'is_background', 'scaleX', 'scaleY', 'scaleWidth', 'scaleHeight']); // 你可以根據(jù)需要包含或排除屬性

  // 將JSON對象轉(zhuǎn)換為字符串  

  const jsonString = JSON.stringify(jsonData, null, 2);

    

  // 以下是下載的邏輯=====================

  // 創(chuàng)建一個Blob對象  

  const blob = new Blob([jsonString], { type: 'text/json' });

  // 創(chuàng)建一個指向blob的URL  

  const url = window.URL.createObjectURL(blob);

  // 創(chuàng)建一個臨時(shí)的a標(biāo)簽用于下載  

  const a = document.createElement('a');

  a.href = url;

  a.download = 'canvas_data.json'; // 指定下載的文件名  

  document.body.appendChild(a);

  a.click(); // 模擬點(diǎn)擊以觸發(fā)下載  

  // 清理  

  document.body.removeChild(a);

  window.URL.revokeObjectURL(url);

},

生成圖片

通過canvas.toDataURL將畫布內(nèi)容導(dǎo)出為數(shù)據(jù) URL,并指定為圖像格式(如 PNG 或 JPEG)。

<el-button type="primary" size="small" @click="exportCanvasAsImage">下載圖片</el-button>

......

exportCanvasAsImage() {

  // 設(shè)置圖片的質(zhì)量和格式,這里以PNG格式為例,質(zhì)量為0.8  

  const imageUrl = this.canvas.toDataURL({  

      format: 'png',  

      quality: 0.8  

  });

  

  // 以下是下載的邏輯=====================

  // 創(chuàng)建一個指向該DataURL的a標(biāo)簽用于下載

  const a = document.createElement('a');

  a.href = imageUrl;

  a.download = 'canvas_image.png'; // 指定下載的文件名

  document.body.appendChild(a);

  a.click(); // 模擬點(diǎn)擊以觸發(fā)下載

  document.body.removeChild(a);

  window.URL.revokeObjectURL(imageUrl);

}

渲染JSON為圖像

前面生成的JSON文件,我們通常會保存到本地或傳給后端。一般二次編輯時(shí),是需要回顯畫布的。回顯畫布可通過canvas.loadFromJSON(json, [callback])來實(shí)現(xiàn)。

json (String): 描述畫布狀態(tài)的 JSON 字符串。

callback (Function, 可選): 當(dāng) JSON 數(shù)據(jù)加載完成并渲染到畫布上后調(diào)用的函數(shù)

mounted() {

    this.initData()

    // 如果是編輯時(shí)

    if(isEdit) {

      // 請求接口,或讀取本地的JSON文件...

      // jsonData為需要渲染的JSON

      this.canvas.loadFromJSON(jsonData, this.canvas.renderAll.bind(this.canvas))

    }

},

?轉(zhuǎn)自https://juejin.cn/post/7427513979639496741



該文章在 2024/10/25 10:41:56 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
日本有码中文字幕第一页在线播放 | 在线视频国产1024 | 在国产线视频a在线视频 | 午夜性色福利免费视频在线观看 | 羞羞视频在线观看网页 | 伊人亚洲综合中文字幕 |