iT邦幫忙

2024 iThome 鐵人賽

DAY 2
0
Modern Web

一起來玩圖像編輯器:Fabric.js 的實戰修煉系列 第 2

Day2- Fabric.js 與原生 Canvas 的關係?!

  • 分享至 

  • xImage
  •  

Canvas API 為 Web 開發者提供了強大的 2D 繪圖能力,但在處理複雜的互動式圖形應用時,原生 Canvas 可能顯得繁瑣。Fabric.js 作為一個強大的 Canvas 庫,在 Canvas 之上提供了更高層次的抽象操作,更適合這類的操作。

canvas 是什麼?怎麼運作

如果你還不是很理解 canvas 本身怎麼運作,這裡兩句話解釋完畢:

Canvas 開發的本質其實很簡單,想像下面這種少兒畫板:
畫板
Canvas 的渲染過程就是不斷的在畫板(Canvas)上面擦了畫,畫了擦。
from -- Fabric.js 原理與源碼解析

你如果想要畫板上畫的車車(物件)往前進,你要先把他的上個位置的車車擦掉,往他行進的方向再畫一個新的,以此類推,持續動作,這車車看起來才會是在前進

fabric.js 怎麼運作

這樣說起來 fabric.js 比較像小孩在地毯上玩車車,
地毯是畫板,但在上面的車車(物件)我們可以隨心所欲地移動、控制他們,且可以選擇要控制哪個(只要你有手)
playing children

Fabric.js 與 Canvas 的關係類似於 jQuery 之於 JavaScript

jQuery 簡化了 DOM 操作和跨瀏覽器兼容性,Fabric.js 同樣簡化了 Canvas 操作,提供了更高級的抽象和更易用的 API。

講起來很抽象,我們來進例子:


Fabric.js 與 Canvas 大PK

以下的例子共同使用 canvas html 元素:

<canvas id="myCanvas" width="500" height="500"></canvas>

基本繪圖

原生Canvas:

// 先選取要作用的 Dom
const canvas = document.getElementById('myCanvas');

// 獲取 HTML5 canvas 元素的 2D 渲染
const ctx = canvas.getContext('2d');

// 使用這個渲染的 ctx 繪製一個紅色矩形
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 100);

Fabric.js:

// 創建 Fabric.js canvas 對象
const canvas = new fabric.Canvas('myCanvas');

// 創建一個紅色矩形
const redRect = new fabric.Rect({
    left: 10,
    top: 10,
    fill: 'red',
    width: 100,
    height: 100
});

// 將矩形添加到 canvas
canvas.add(redRect);
  • 原生Canvas需要直接操作繪圖上下文(Context)。
  • Fabric.js提供了物件導向的API,更直觀且易於管理。
    使用像 new fabric.Rect 來新增物件,往後可以直接呼叫已創建好的 redRect 做後續想執行的行為
    => 單純創建元素這件事以目前畫一個紅色矩形看起來兩者的複雜程度差不多,但後面要做更多事的時候就會感覺到差別了😈

物件操作

原生 Canvas 中操作:

// 繪製矩形函數
function drawRect() {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除 canvas
    ctx.fillStyle = rect.fill;
    ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
}

// 初始繪製矩形
drawRect();

// 例子:移動矩形
rect.x = 50;
drawRect();

// 例子:改變矩形顏色
rect.fill = 'blue';
drawRect();

// 例子:縮放矩形
rect.width = 150;
rect.height = 150;
drawRect();

Fabric.js中:

// 將矩形添加到 canvas
canvas.add(rect);

// 例子:移動矩形
rect.set('left', 50);
canvas.renderAll();

// 例子:改變矩形顏色
rect.set('fill', 'blue');
canvas.renderAll();

// 例子:縮放矩形
rect.scaleToWidth(150);
rect.scaleToHeight(150);
canvas.renderAll();
  • 原生Canvas 需要手動清除和重繪
  • Fabric.js 允許直接操作物件,大大簡化了動態交互

事件處理

原生Canvas:

canvas.addEventListener('click', function(e) {
  const rect = canvas.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  if(這邊需要計算點擊位置是否在所畫的 rect 範圍以內,以下省略大段計算) {
  console.log('物件被點擊了!');
  }
});

Fabric.js:

canvas.on('mouse:down', function(options) {
  if (options.target) {
    console.log('物件被點擊了!');
  }
});
  • 原生 Canvas 需要手動實現事件檢測
  • Fabric.js 可以直接用 canvas.on 來監聽內建的事件系統 'mouse:down'這類的事件,並可自訂觸發的function內容,大大簡化了交互處理。

群組和圖層

原生Canvas

原生Canvas中沒有內建的群組概念,需要自行管理。
硬要寫出來的話長這樣:

        // 定義矩形對象
        const rect = {
            x: 10,
            y: 10,
            width: 100,
            height: 100,
            fill: 'red'
        };

        // 定義圓形對象
        const circle = {
            x: 200,
            y: 200,
            radius: 50,
            fill: 'blue'
        };

        // 定義群組對象
        const group = {
            items: [rect, circle],
            x: 0,
            y: 0
        };

        // 繪製矩形函數
        function drawRect(rect) {
            ctx.fillStyle = rect.fill;
            ctx.fillRect(rect.x + group.x, rect.y + group.y, rect.width, rect.height);
        }

        // 繪製圓形函數
        function drawCircle(circle) {
            ctx.beginPath();
            ctx.arc(circle.x + group.x, circle.y + group.y, circle.radius, 0, Math.PI * 2);
            ctx.fillStyle = circle.fill;
            ctx.fill();
        }

        // 繪製群組函數
        function drawGroup(group) {
            // 清除 canvas
            ctx.clearRect(0, 0, canvas.width, canvas.height); 
            group.items.forEach(item => {
                if (item.width) {
                    drawRect(item);
                } else if (item.radius) {
                    drawCircle(item);
                }
            });
        }

        // 繪製群組
        drawGroup(group);

Fabric.js:

const group = new fabric.Group([rect, new fabric.Circle({ radius: 50 })]);
canvas.add(group);
  • Fabric.js提供了強大的群組功能,方便管理複雜的圖形結構。

序列化和反序列化(儲存畫面的方式)

原生Canvas需要手動實現序列化邏輯(每做一個步驟之後,進行快照紀錄)
比較把文件影印保存,上面的物件屬性已不同,後續不能直接編輯單一物件

原生canvas:

// 保存當前步驟
function saveStep() {
    const step = canvas.toDataURL();
    steps.push(step);
    console.log('Step saved:', step);
}

// 加載步驟
function loadStep() {
    if (steps.length > 0) {
        const step = steps.pop();
        const img = new Image();
        img.src = step;
        img.onload = () => {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(img, 0, 0);
        };
        console.log('Step loaded:', step);
    }
}

Fabric.js:

以檔案來說比較像把 word 存檔,之後開起來還是可以編輯,物件都還保有原本的屬性

const json = JSON.stringify(canvas);
const loadedCanvas = new fabric.Canvas('anotherCanvas');
loadedCanvas.loadFromJSON(json);
  • Fabric.js支持將整個畫布狀態序列化為JSON,便於保存和恢復。

動畫

原生Canvas:

let x = 1
function animate() {
  //畫下一個之前要先清空畫布
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  //矩形的水平位置會向右移動
  ctx.fillRect(x++, 10, 100, 100);
  
  // 以動畫頻率更新畫面
  requestAnimationFrame(animate);
}

Fabric.js:

fabric.util.animate({
  startValue: 0,
  endValue: 100,
  onChange: function(value) {
    rect.set('left', value);
    canvas.renderAll();
  }
});
  • Fabric.js提供了更高級的動畫API,簡化了動畫邏輯。

SVG導入/導出

原生Canvas不直接支持SVG。

Fabric.js:

可以直接把 SVG 導入變成物件

fabric.loadSVGFromURL('path/to/svg', function(objects, options) {
  const shape = fabric.util.groupSVGElements(objects, options);
  canvas.add(shape);
});
  • Fabric.js原生支持SVG導入和導出,大大擴展了應用範圍。

性能考量

  • 對於簡單的靜態繪圖,原生Canvas可能略快。
  • 對於複雜的交互式應用,Fabric.js的物件模型和優化可能提供更好的性能。

結論

Fabric.js在以下方面顯著優於原生Canvas:

  • 物件導向的API
  • 內建的事件處理
  • 強大的群組和圖層管理
  • 序列化支持
  • 高級動畫和濾鏡效果
  • SVG支持

然而,原生Canvas在以下情況可能更合適:

  • 極簡單的繪圖需求
  • 需要最大化性能的特定場景
  • 不需要複雜交互的靜態圖形(ex:用向量畫一幅最後的晚餐)

總的來說,Fabric.js通過提供豐富的功能和簡化的API,大大提高了開發複雜Canvas應用的效率。對於大多數交互式圖形項目,Fabric.js是一個更好的選擇。
然而,了解原生Canvas API仍然很重要,因為它可以幫助更深入地理解Fabric.js的工作原理,並在需要時進行底層優化。


上一篇
Day1- Fabric.js 是什麼?他可以做些什麼
下一篇
Day3- 來概念解構 Fabric.js 吧 (1) - 核心概念
系列文
一起來玩圖像編輯器:Fabric.js 的實戰修煉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言