🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

案例+图解帶你遨遊 Canvas 2D繪圖 Fabric.js🔥🔥(5W+字)

Fabric.js 簡介

Fabric.js 是一個功能強大且操作簡單的 Javascript HTML5 canvas 工具庫。

00.png

『Fabric.js 官網首頁』

『Fabric.js Demos』

本文主要講解 Fabric.js 有基礎也有實戰,包括:

  • 畫布的基本操作
  • 基礎圖形繪製方法(矩形、圓形、三角形、橢圓、多邊形、線段等)
  • 自訂圖形
  • 圖片的使用
  • 文字和文本框
  • 圖形和文字的基礎樣式
  • 漸變
  • 選中狀態
  • 分組和取消分組
  • 動畫
  • 設定和獲取圖形層級
  • 基礎事件
  • 禁止水平、垂直移動
  • 縮放和平移畫布
  • 視口座標和畫布座標轉換
  • 序列化和反序列化
  • ……

起步

1. 新建頁面並引入 Fabric.js

import { fabric } from 'fabric'

2. 創建 canvas 容器

HTML 中創建 <canvas>,並設定容器的 id寬高,width/height

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

這裡創建了一個 canvas 容器,邊框為灰色,id="c"。指定長寬都為 400px

003.png

3. 使用 fabric 接管容器,並畫一個矩形

JS 中實例化 fabric ,之後就可以使用 fabricapi 管理 canvas 了。

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric' // 引入 fabric

function init() {
  const canvas = new fabric.Canvas('c') // 這裡傳入的是canvas的id

  // 創建一個長方形
  const rect = new fabric.Rect({
    top: 30, // 距離容器頂部 30px
    left: 30, // 距離容器左側 30px
    width: 100, // 寬 100px
    height: 60, // 高 60px
    fill: 'red' // 填充 紅色
  })

  // 在canvas畫布中加入矩形(rect)。
  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

004.png

畫布

Fabric.js 的畫布操作性是非常強的。

基礎版(可互動)

005.gif
基礎版就是“起步”章節所說的那個例子。

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas') // 這裡傳入的是canvas元素的id

  // 創建一個長方形
  const rect = new fabric.Rect({
    top: 100, // 距離容器頂部 100px
    left: 100, // 距離容器左側 100px
    width: 30, // 矩形寬度 30px
    height: 30, // 矩形高度 30px
    fill: 'red' // 填充 紅色
  })

  canvas.add(rect) // 將矩形添加到 canvas 畫布裡
}

onMounted(() => {
  init()
})
</script>

不可互動

006.gif

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  // 使用 StaticCanvas 創建一個不可操作的畫布
  const canvas = new fabric.StaticCanvas('canvas') // 這裡傳入的是canvas元素的id

  // 創建一個長方形
  const rect = new fabric.Rect({
    top: 100, // 距離容器頂部 100px
    left: 100, // 距離容器左側 100px
    width: 30, // 矩形寬度 30px
    height: 30, // 矩形高度 30px
    fill: 'red' // 填充 紅色
  })

  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

創建不可互動的畫布,其實只需把 new fabric.Canvas 改成 new fabric.StaticCanvas 即可。

在js設定畫布參數

007.png

const canvas = new fabric.Canvas('canvas', {
    width: 300, // 畫布寬度
    height: 300, // 畫布高度
    backgroundColor: '#eee' // 畫布背景色
})

使用背景圖

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  // 設定背景圖
  // 參數1:背景圖資源(可以引入本地,也可以使用網路圖)
  // 參數2:設定完背景圖執行以下重新渲染canvas的操作,這樣背景圖就會展示出來了
  canvas.setBackgroundImage(
    '圖片url',
    canvas.renderAll.bind(canvas)
  )
}

onMounted(() => {
  init()
})
</script>

拉伸背景圖

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  // fabric.Image.fromURL:加載圖片的api
  // 第一個參數:圖片地址(可以是本地的,也可以是網路圖)
  // 第二個參數:圖片加載的回調函數
  fabric.Image.fromURL(
    '圖片url',
    (img) => {
      // 設定背景圖
      canvas.setBackgroundImage(
        img,
        canvas.renderAll.bind(canvas),
        {
          scaleX: canvas.width / img.width, // 計算出圖片要拉伸的寬度
          scaleY: canvas.height / img.height // 計算出圖片要拉伸的高度
        }
      )
    }
  )
}

onMounted(() => {
  init()
})
</script>

這個例子使用了 fabric.Image.fromURL 這個 api 來加載圖片,第一个參數是圖片地址,第二个參數是回調函數。

拿到圖片的參數和畫布的寬高進行計算,從而使圖片充滿全屏。

基礎圖形

矩形

015.png

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const rect = new fabric.Rect({
    top: 100, // 距離容器頂部 100px
    left: 100, // 距離容器左側 100px
    fill: 'orange', // 填充 橙色
    width: 100, // 寬度 100px
    height: 100 // 高度 100px
  })

  // 將矩形添加到畫布中
  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Rect 創建 矩形

圓角矩形

016.png

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const rect = new fabric.Rect({
    top: 100, // 距離容器頂部 100px
    left: 100, // 距離容器左側 100px
    fill: 'orange', // 填充 橙色
    width: 100, // 寬度 100px
    height: 100, // 高度 100px
    rx: 20, // x軸的半徑
    ry: 20 // y軸的半徑
  })

  // 將矩形添加到畫布中
  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

畫圓角矩形,需要添加 rxry

圓形

017.png

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const circle = new fabric.Circle({
    top: 100,
    left: 100,
    radius: 50, // 圓的半徑 50
    fill: 'green'
  })

  canvas.add(circle)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Circle 創建圓形。圓形需要使用 radius 設置半徑大小。

橢圓形

018.png

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const ellipse = new fabric.Ellipse({
    top: 20,
    left: 20,
    rx: 70,
    ry: 30,
    fill: 'hotpink'
  })

  canvas.add(ellipse)
}

onMounted(() => {
  init()
})
</script>

需要使用 new fabric.Ellipse 創建 橢圓。和圓形不同,橢圓不需要設置 radius ,但要設置 rxry

  • rx > ry :橢圓是橫著的
  • rx < ry:橢圓是豎著的
  • rx = ry: 看上去就是個圓形

三角形

019.png

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const triangle = new fabric.Triangle({
    top: 100,
    left: 100,
    width: 80, // 底邊長度
    height: 100, // 底邊到對角的距離
    fill: 'blue'
  })

  canvas.add(triangle)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Triangle 創建三角形,三角形是需要給定 “底和高” 的。

線段

020.png

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const line = new fabric.Line(
    [10, 10, 200, 300], // 起始點坐標
    {
      stroke: 'red', // 笔触顏色
    }
  )

  canvas.add(line)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Line 創建線段。new fabric.Line 需要傳入2個參數。

  • 第一個參數是 數組 ,數組需要傳4個值,前2個值是起始坐標的x和y,後2個值是結束坐標的x和y
  • 第二個參數是 線段的樣式,要設定線段的顏色,需要使用 stroke

折線

021.png

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const polyline = new fabric.Polyline([
    { x: 30, y: 30 },
    { x: 150, y: 140 },
    { x: 240, y: 150 },
    { x: 100, y: 30 }
  ], {
    fill: 'transparent', // 如果畫折線,需要填充透明
    stroke: '#6639a6', // 線段顏色:紫色
    strokeWidth: 5 // 線段粗細 5
  })

  canvas.add(polyline)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Polyline 創建線段new fabric.Polyline 需要傳入2個參數。

  • 第一個參數是數組,描述線段的每一個點
  • 第二個參數用來描述線段樣式

需要注意的是, fill 設定成透明才會顯示成線段,如果不設置,會默認填充黑色,如下圖所示:
022.png

你也可以填充自己喜歡的顏色,new fabric.Polyline 是不會自動把 起始點結束點 自動閉合起來的。

多邊形

023.png

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const polygon = new fabric.Polygon([
    { x: 30, y: 30 },
    { x: 150, y: 140 },
    { x: 240, y: 150 },
    { x: 100, y: 30 }
  ], {
    fill: '#ffd3b6', // 填充色
    stroke: '#6639a6', // 線段顏色:紫色
    strokeWidth: 5 // 線段粗細 5
  })

  canvas.add(polygon)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Polygon 繪製多邊形,用法和 new fabric.Polyline 差不多,但最大的不同點是 new fabric.Polygon 會自動把 起始點結束點 連接起來。

自訂圖形

在Fabric.js中,幾乎所有的2d圖形直接或間接繼承自 Object 類,那麼如果我們不用其自帶的2d圖形,而是自建圖形,要怎麼應用 Fabric.js 中的方法呢?

Fabric.js 提供了 fabric.util.createClass 方法解決這個問題

一個自訂子類的結構:

// 創建一個自訂子類
const customClass = fabric.util.createClass(fabric.Object, {
  type: "customClass",
  initialize: function (options) {
    options || (options = {});
    this.callSuper("initialize", options);
    // 自訂屬性
  },

  toObject: function () {
    return fabric.util.object.extend(this.callSuper("toObject"), {
      // 將自訂屬性添加到序列化物件中
    });
  },

  _render: function (ctx) {
    this.callSuper("_render", ctx);
    // 自訂渲染邏輯
  },
});

一個簡單的自訂類主要要修改3個地方,分別是:

  1. initialize : 添加的自訂屬性方法放這
  2. toObject: 將自訂屬性添加到序列化物件中,方便canvas記錄
  3. _render: 處理自訂渲染邏輯

此處舉一個簡單的例子,寫一個自訂表格圖形:
新增繪製網格圖的方法 initMap:

// 繪製表格圖形
function initTable(options, ctx) {
  const { gridNumX, gridNumY, width, height, fill, left, top } = options;
  ctx.save();
  ctx.translate(-width / 2, -height / 2)
  // 開始路徑並繪製線條
  ctx.beginPath();
  // 設定線條樣式
  ctx.lineWidth = 1;
  ctx.strokeStyle = fill;
  // 開始繪製橫線
  for (let i = 0; i < gridNumY + 1; i++) {
    // 注意要算線的寬度,也就是後面那個+i
    ctx.moveTo(0, height / gridNumY * i);
    ctx.lineTo(width, height / gridNumY * i);
    ctx.stroke();
  }
  // 開始繪製縱線
  for (let i = 0; i < gridNumX + 1; i++) {
    ctx.moveTo(width / gridNumX * i, 0);
    ctx.lineTo(width / gridNumX * i, height);
    ctx.stroke();
  }
  ctx.restore();
}

創建 Table 子類:

// 創建一個自訂子類
const Map = fabric.util.createClass(fabric.Object, {
  type: "Table",
  initialize: function (options) {
    options || (options = {});
    this.callSuper("initialize", options);
    this.set("gridNumX", options.gridNumX || "");
    this.set("gridNumY", options.gridNumY || "");
  },

  toObject: function () {
    return fabric.util.object.extend(this.callSuper("toObject"), {
      gridNumX: this.get("gridNumX"),
      gridNumY: this.get("gridNumY"),
    });
  },

  _render: function (ctx) {
    this.callSuper("_render", ctx);
    initTable({
      ...this
    }, ctx)
  },
});

新建 Table 實例並添加到canvas:

const table = new Table({
  left: 100,
  top: 100,
  label: "test",
  fill: "#faa",
  width: 100,
  height: 100,
  gridNumX: 4,
  gridNumY: 3,
});

const table2 = new Table({
  left: 300,
  top: 100,
  label: "test",
  fill: "green",
  width: 200,
  height: 300,
  gridNumX: 2,
  gridNumY: 5,
});
// 將所有圖形添加到 canvas 中
canvas.add(table, table2);

如圖所示,成功創建了可重用的自訂圖形,而且能夠使用 Object 類的功能。

image.png

文字

Fabric.js 有3類跟文字相關的 api

  • 普通文字
  • 可編輯文字
  • 文本框

普通文字 Text

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const text = new fabric.Text('hello')
  canvas.add(text)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Text 創建文本,傳入第一個參數就是文本內容。

new fabric.Text 還支持第二個參數,可以設置文本樣式。

可編輯文字 IText

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const itext = new fabric.IText('hello')
  canvas.add(itext)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.IText 可以創建可編輯文字,用法和 new fabric.Text 一樣。

文本框 Textbox

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const textbox = new fabric.Textbox('What are you doing?', {
    width: 250
  })
  canvas.add(textbox)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Textbox 可以創建文本框。
new fabric.Textbox 第二個參數是對象,使用 width 可以設定了文本框的寬度,文本內容超過設定的寬度會自動換行。
new fabric.Textbox 的內容同樣是可編輯的。

基礎樣式

圖形常用樣式

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

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const circle = new fabric.Circle({
    top: 100,
    left: 100,
    radius: 50, // 半徑:50px
    backgroundColor: 'green', // 背景色:綠色
    fill: 'orange', // 填充色:橙色
    stroke: '#f6416c', // 邊框顏色:粉色
    strokeWidth: 5, // 邊框粗細:5px
    strokeDashArray: [20, 5, 14], // 邊框虛線規則:填充20px 空5px 填充14px 空20px 填充5px ……
    shadow: '10px 20px 6px rgba(10, 20, 30, 0.4)', // 投影:向右偏移10px,向下偏移20px,羽化6px,投影顏色及透明度
    transparentCorners: false, // 選中時,角是被填充了。true 空心;false 實心
    borderColor: '#16f1fc', // 選中時,邊框顏色:天藍
    borderScaleFactor: 5, // 選中時,邊的粗細:5px
    borderDashArray: [20, 5, 10, 7], // 選中時,虛線邊的規則
    cornerColor: "#a1de93", // 選中時,角的顏色是 青色
    cornerStrokeColor: 'pink', // 選中時,角的邊框的顏色是 粉色
    cornerStyle: 'circle', // 選中時,叫的屬性。默認rect 矩形;circle 圓形
    cornerSize: 20, // 選中時,角的大小為20
    cornerDashArray: [10, 2, 6], // 選中時,虛線角的規則
    selectionBackgroundColor: '#7f1300', // 選中時,選框的背景色:朱紅
    padding: 40, // 選中時,選擇框離元素的內邊距:40px
    borderOpacityWhenMoving: 0.6, // 當對象活動和移動時,對象控制邊界的不透明度  
  })

  canvas.add(circle)
}

onMounted(() => {
  init()
})
</script>

原文出處:https://juejin.cn/post/7595351120668672026


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝7   💬7   ❤️2
187
🥈
我愛JS
💬1  
6
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付