Canvas API 為 Web 開發者提供了強大的 2D 繪圖能力,但在處理複雜的互動式圖形應用時,原生 Canvas 可能顯得繁瑣。Fabric.js 作為一個強大的 Canvas 庫,在 Canvas 之上提供了更高層次的抽象操作,更適合這類的操作。
如果你還不是很理解 canvas 本身怎麼運作,這裡兩句話解釋完畢:
Canvas 開發的本質其實很簡單,想像下面這種少兒畫板:
Canvas 的渲染過程就是不斷的在畫板(Canvas)上面擦了畫,畫了擦。
from -- Fabric.js 原理與源碼解析
你如果想要畫板上畫的車車(物件)往前進,你要先把他的上個位置的車車擦掉,往他行進的方向再畫一個新的,以此類推,持續動作,這車車看起來才會是在前進
這樣說起來 fabric.js 比較像小孩在地毯上玩車車,
地毯是畫板,但在上面的車車(物件)我們可以隨心所欲地移動、控制他們,且可以選擇要控制哪個(只要你有手)
jQuery 簡化了 DOM 操作和跨瀏覽器兼容性,Fabric.js 同樣簡化了 Canvas 操作,提供了更高級的抽象和更易用的 API。
講起來很抽象,我們來進例子:
以下的例子共同使用 canvas html 元素:
<canvas id="myCanvas" width="500" height="500"></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 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);
new fabric.Rect
來新增物件,往後可以直接呼叫已創建好的 redRect
做後續想執行的行為// 繪製矩形函數
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();
// 將矩形添加到 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.addEventListener('click', function(e) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if(這邊需要計算點擊位置是否在所畫的 rect 範圍以內,以下省略大段計算) {
console.log('物件被點擊了!');
}
});
canvas.on('mouse:down', function(options) {
if (options.target) {
console.log('物件被點擊了!');
}
});
canvas.on
來監聽內建的事件系統 'mouse:down'
這類的事件,並可自訂觸發的function內容,大大簡化了交互處理。原生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);
const group = new fabric.Group([rect, new fabric.Circle({ radius: 50 })]);
canvas.add(group);
原生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);
}
}
以檔案來說比較像把 word 存檔,之後開起來還是可以編輯,物件都還保有原本的屬性
const json = JSON.stringify(canvas);
const loadedCanvas = new fabric.Canvas('anotherCanvas');
loadedCanvas.loadFromJSON(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();
}
});
原生Canvas不直接支持SVG。
可以直接把 SVG 導入變成物件
fabric.loadSVGFromURL('path/to/svg', function(objects, options) {
const shape = fabric.util.groupSVGElements(objects, options);
canvas.add(shape);
});
Fabric.js在以下方面顯著優於原生Canvas:
然而,原生Canvas在以下情況可能更合適:
總的來說,Fabric.js通過提供豐富的功能和簡化的API,大大提高了開發複雜Canvas應用的效率。對於大多數交互式圖形項目,Fabric.js是一個更好的選擇。
然而,了解原生Canvas API仍然很重要,因為它可以幫助更深入地理解Fabric.js的工作原理,並在需要時進行底層優化。