Fabric.js 將本地圖像上傳到畫(huà)布背景

本文介紹

我使用 Fabric.js 的版本是 4.6.0。

這次要實(shí)現(xiàn)的效果是:在本地上傳一張圖片,然后渲染到 canvas 里(當(dāng)做背景圖)。

我會(huì)用 原生 的方法實(shí)現(xiàn)一次,然后再在 Vue3 + Element-plus 環(huán)境下實(shí)現(xiàn)一次。

最后聊聊我在真實(shí)項(xiàng)目中的做法。


需求:

  1. 通過(guò)點(diǎn)擊上傳按鈕上傳圖片
  2. 拿到圖片,放到畫(huà)布上渲染

需要注意的是,本文主要實(shí)現(xiàn) 上傳圖片并渲染到畫(huà)布 的邏輯,所以沒(méi)有做上傳文件類型的限制,也沒(méi)做文件大小限制。如果你的業(yè)務(wù)中需要限制文件類型,只需在本案例基礎(chǔ)上添加限制的方法就行了。


本文所有代碼都在文末給出的倉(cāng)庫(kù)里。

如果本文內(nèi)容對(duì)你有所幫助,也請(qǐng)你幫我點(diǎn)個(gè)贊唄~



原生操作

通過(guò) <input type="file" /> 獲取圖片路徑,會(huì)受到瀏覽器安全策略影響,所以需要處理一下。

實(shí)現(xiàn)邏輯:

  • 定義好 上傳按鈕畫(huà)布(HTML部分);
  • 初始化畫(huà)布;
  • 點(diǎn)擊上傳按鈕 獲取圖片地址(這里需要處理一下安全策略的問(wèn)題);
  • 拿到圖片路徑,使用 canvas.setBackgroundImage 將圖片設(shè)置成畫(huà)布背景;
  • canvas.setBackgroundImage 的回調(diào)函數(shù)里刷新一下畫(huà)布;


<div>
  <input type="file" name="file" id="upload" onchange="handleUpload()" />
  <button onclick="saveCanvas()">保存</button>
</div>

<canvas id="canvas" width="600" height="600" style="border: 1px solid #ccc;"></canvas>

<!-- 引入fabric.js -->
<script src="https://cdn.bootcdn.net/ajax/libs/fabric.js/460/fabric.js"></script>
<script>

// 上傳文件的DOM元素
const uploadEl = document.getElementById("upload")

// 畫(huà)布
let canvas = null

// 初始化畫(huà)布
function initCanvas() {
  canvas = new fabric.Canvas('canvas')
}

// 上傳文件事件
function handleUpload() {
  // 上傳文件列表的第一個(gè)文件
  const file = uploadEl.files[0]

  // 圖片文件的地址
  let imgPath = null

  // 獲取圖片文件真實(shí)路徑
  // 由于瀏覽器安全策略,現(xiàn)在需要這么做了
  // 這段代碼是網(wǎng)上復(fù)制下來(lái)的,想深入理解的可以百度搜搜 “C:\fakepath\”
  if (window.createObjcectURL != undefined) {
    imgPath = window.createOjcectURL(file); 
  } else if (window.URL != undefined) {
    imgPath = window.URL.createObjectURL(file); 
  } else if (window.webkitURL != undefined) {
    imgPath = window.webkitURL.createObjectURL(file);
  }

  // 設(shè)置畫(huà)布背景,并刷新畫(huà)布
  canvas.setBackgroundImage(
    imgPath,
    canvas.renderAll.bind(canvas)
  )
}

// 保存畫(huà)布
function saveCanvas() {
  let data = canvas.toJSON()
  console.log(data)
}

window.onload = function() {
  initCanvas()
}
</script>

上面的實(shí)現(xiàn)方式,如果是在純前端的環(huán)境下,保存時(shí)背景圖是地址是本地地址( "blob:http://127.0.0.1:5500/383e7860-3fa5-43b9-92d9-e7165760e60b" )。

這樣其實(shí)不是很好,如果在別的電腦想通過(guò) 反序列化 渲染出來(lái)的時(shí)候,可能會(huì)出現(xiàn)一點(diǎn)問(wèn)題。


如果純前端實(shí)現(xiàn)的方式,可以將圖片轉(zhuǎn)成 base64 再生成背景圖。

fabric.Image.fromURL(
  imgPath, // 真實(shí)圖片地址
  img => {
    // 將圖片設(shè)置再畫(huà)布上,然后重新渲染畫(huà)布,圖片就出來(lái)了。
    canvas.setBackgroundImage(
      img, // 要設(shè)置的圖片
      canvas.renderAll.bind(canvas) // 重新渲染畫(huà)布
    )
  }
)



在 element-plus 里的操作

我使用了 vue3 + element-plus 。

實(shí)現(xiàn)邏輯和原生方法一樣。
唯一不同的是本例用了 el-upload 這個(gè)組件。
我將圖片文件轉(zhuǎn)成 base64 再放進(jìn)畫(huà)布。

<template>
  <div>
    <div class="btn__x">
      <!-- 上傳組件 -->
      <el-upload
        action="https://jsonplaceholder.typicode.com/posts/"
        :multiple="false"
        :show-file-list="false"
        :limit="1"
        accept=".jpg,.png"
        :before-upload="onProgress"
      >
        <el-button type="primary">上傳</el-button>
      </el-upload>

      <!-- 保存按鈕(序列化) -->
      <el-button @click="saveCanvas">保存:打開(kāi)控制臺(tái)查看</el-button>
    </div>

    <!-- 畫(huà)布 -->
    <canvas id="canvas" width="600" height="600" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted, ref } from 'vue'
import { useStore } from 'vuex'
import { fabric } from 'fabric'

const store = useStore()

// 畫(huà)布
let canvas = null

// 上傳
function onProgress(file) {
  // 拿圖片文件
  const reader = new FileReader()
  reader.readAsDataURL(file)

  // 圖片文件完全拿到后執(zhí)行
  reader.onload = () => {
    // 轉(zhuǎn)換成base64格式
    const base64Img = reader.result

    // 將base64圖片設(shè)置成背景
    canvas.setBackgroundImage(
      base64Img,
      canvas.renderAll.bind(canvas) // 刷新畫(huà)布
    )
  }
  return false
}

// 初始化畫(huà)布
function init() {
  canvas = new fabric.Canvas('canvas')
}

// 保存
function saveCanvas() {
  console.log(canvas.toJSON())
}

// 頁(yè)面加載完成后,初始化畫(huà)布
onMounted(() => {
  init()
})
</script>

<style lang="scss" scoped>
.btn__x {
  display: flex;

  .el-button {
    margin-right: 20px;
  }
}
</style>



在正式開(kāi)發(fā)中

在正式的項(xiàng)目開(kāi)發(fā)中,上面兩種情況出現(xiàn)的概率應(yīng)該不多(除非你的后端伙伴是個(gè)懶人)

先說(shuō)說(shuō)上面兩種情況存在的問(wèn)題:

  1. 圖片路徑是本地地址,保存到服務(wù)器是沒(méi)意義的。
  2. 轉(zhuǎn)成 base64 來(lái)保存,字段可能會(huì)很大。


這種情況放到服務(wù)器可能沒(méi)什么用的。 127.0.0.1 是你本機(jī)的,你上傳的圖片在別人的電腦可能無(wú)法查看。


這種情況雖然問(wèn)題不大,但 backgroundImage.src 的值有點(diǎn)大。


我在項(xiàng)目中的做法:

  1. 前端上傳圖片給后端
  2. 后端把圖片存到服務(wù)器,然后返回一個(gè)圖片url給前端
  3. 前端拿到圖片url,再放到 fabric 里渲染出來(lái)

這樣做的好處是 backgroundImage.src 的值變短了。


在正式項(xiàng)目中,你可能還要考慮到背景圖的大小和畫(huà)布大小不匹配問(wèn)題。
你可以參考 《Fabric.js 從入門(mén)到膨脹》“拉伸背景圖” 這小節(jié)。



代碼倉(cāng)庫(kù)

原生方式實(shí)現(xiàn)

在 Vue3+Element-plus 中實(shí)現(xiàn)



推薦閱讀

《Fabric.js 從入門(mén)到膨脹》


《Fabric.js 漸變效果(包括徑向漸變)》


《Fabric.js 3個(gè)api設(shè)置畫(huà)布寬高》


《Fabric.js 自定義右鍵菜單》


《Fabric.js 更換圖片的3種方法(包括更換分組內(nèi)的圖片,以及存在緩存的情況)》

如果本文內(nèi)容對(duì)你有所幫助,也請(qǐng)你幫我點(diǎn)個(gè)贊唄~
點(diǎn)贊 + 關(guān)注 + 收藏 = 學(xué)會(huì)了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容