iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0
Modern Web

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

Day16-fabric.js 進階組合技!自定義控件開發 (control) 實例

  • 分享至 

  • xImage
  •  

如果我想要每個物件的控制,旁邊都可以有刪除或是複製功能的 icon 可以點擊,並且可以個別操作,該怎麼做呢?

登登,可以使用 fabric.Control 來新增這樣的控制項在物件上!以達成以下的效果

control-demo

並且,當我們希望所有的物件在生成時都有這樣的控制項,我們可以不要那麼忙的每新增一個物件,就把這屬性加到新的物件上,我們可以直接來修改 prototype!

修改 prototype 的概念可以回顧一下 day4 的物件的擴展與繼承
day4 的物件的擴展與繼承

來上例子:

conat canvas = new fabric.Canvas("canvas");

// 添加刪除控制項
// 在這邊自己命名 新的控制項按鈕名為:deleteControl,並直接把他新增在 fabric.Object.prototype.controls 上
fabric.Object.prototype.controls.deleteControl = new fabric.Control({
  x: 0.5,
  y: -0.5,
  offsetY: -16,
  cursorStyle: "pointer",
  mouseUpHandler: function (eventData, transform, x, y) {
    const target = transform.target;
    const canvas = target.canvas;
    canvas.remove(target);
    canvas.requestRenderAll();
    return true;
  },
  render: function (ctx, left, top, styleOverride, fabricObject) {
    const size = this.sizeX || this.sizeY || 12;
    ctx.save();
    ctx.translate(left, top);
    ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
    ctx.fillStyle = "red";
    ctx.beginPath();
    ctx.arc(0, 0, size / 2, 0, 2 * Math.PI);
    ctx.fill();
    ctx.fillStyle = "white";
    ctx.fillRect(-size / 4, -size / 8, size / 2, size / 4);
    ctx.restore();
  }
});

var circle = new fabric.Circle({
  top: 100,
  left: 60,
  radius: 30,
  stroke: "blue",
  fill: "transparent"
});

canvas.add(circle);
canvas.setActiveObject(circle);
canvas.renderAll();

議題:是否要改掉原始 prototype?

這兩種方式在修改 fabric.Object.prototype.controls 時有不同的用途和效果:

1.使用淺拷貝來延伸

fabric.Object.prototype.controls = fabric.util.object.extend({}, fabric.Object.prototype.controls); 這種方式創建了 fabric.Object.prototype.controls 的一個淺拷貝。它的特點是:
- 創建了一個新的對象,這個對象包含了原始 controls 對象的所有屬性。
- 新對象與原始對象是分離的,修改新對象不會直接影響原始對象。
- 適用於當你想要重新定義整個 controls 對象,或者在不影響原始對象的情況下添加多個新控制項,適合大規模的修改或重構。
- 這種方法會保留所有現有的控制項,除非你明確地覆蓋或刪除它們。
- 可能在創建新對象時稍微消耗更多資源。

2.直接修改原有的 prototype controls 對象

fabric.Object.prototype.controls.deleteControl = new fabric.Control({...}); 這種方式直接在原有的 controls 對象上添加或修改特定的控制項。它的特點是:
- 直接修改原始的 controls 對象。
- 只影響你明確指定的控制項(在這個例子中是 deleteControl)。
- 其他已存在的控制項保持不變,適合小範圍的修改或添加功能。
- 適用於當你只想添加或修改個別控制項,而不想改變整個 controls 對象結構的情況。
- 直接修改,可能略微更高效。

fabric.Control

是 Fabric.js 中用於創建自定義控制項的類。它有許多可以使用的屬性和方法。以下是一些主要的屬性及其用途:

每個屬性的具體用途:

  • x, y,
    • (number) 控制項相對於對象中心的水平和垂直位置 ⇛ 你想要這個控制項出現在物件相對的哪個角落
    • 範圍通常在 -0.5 到 0.5 之間。
    • 值的範圍和含義:
      • -0.5: 代表對象的左邊(x軸)或頂部(y軸)
      • 0: 代表對象的中心
      • 0.5: 代表對象的右邊(x軸)或底部(y軸)
    • 位置對照圖
      https://ithelp.ithome.com.tw/upload/images/20240819/20168354W5otLDugZt.png
      如果需要更精確的定位,可以結合使用 offsetXoffsetY 屬性來進行像素級的微調。
  • offsetX, offsetY
    • (number) 控制項的額外偏移量。
  • cursorStyle
    • (string) 增強用戶體驗,提示可能的操作。
  • actionHandler
    • (function) 定義控制項的主要行為。
  • mouseDownHandler, mouseUpHandler
    • 可以用於實現更複雜的交互。

    • mouseDownHandler
      -(function) 鼠標按下控制項時執行的函數。

    • mouseUpHandler

      • (function) 鼠標在控制項上釋放時執行的函數。
  • render
    • (function) 自定義控制項的外觀,用來渲染自定義控制項的函式。
  • cornerSize
    • (number) 控制控制項的尺寸。
  • sizeXsizeY
    • (number) 控制項的寬度和高度。
  • touchSizeX, touchSizeY
    • (number) 觸控設備上控制項的寬度和高度。
  • visible
    • (boolean) 控制控制項的顯示與隱藏。
  • actionName:(string) 與控制項相關聯的動作名稱。用於識別控制項的作用,便於事件處理。

使用這些屬性,你可以創建功能豐富、外觀獨特的自定義控制項。

用比較白話來說,你可以:

  1. 創建特定位置的控制項,讓按鈕 icon 在你想要的位置(使用 x, y)。
  2. 定義控制項的交互行為,設定按下按鈕之後要執行的 function(使用 actionHandler, mouseDownHandler 等)。
  3. 自定義控制項的視覺外觀,用來渲染外觀(使用 render)。
  4. 根據設備類型調整控制項大小(使用 sizeX, sizeY, touchSizeX, touchSizeY)。
  5. 動態顯示或隱藏控制項(使用 visible)。

這些屬性和方法提供了極大的靈活性,使你能夠創建適合特定需求的自定義控制項。


render 函數

方式1: 直接用canvas 的方式畫控制項的圖

function renderGreanIcon(ctx, left, top, styleOverride, fabricObject) {
    const size = this.sizeX || this.sizeY || 12; 
    //設置控制項的大小。如果 `this.sizeX` 或 `this.sizeY` 被定義,就使用它們;否則默認為 12。
    ctx.save(); //保存當前的繪圖上下文狀態,這樣後面的變換不會影響到其他繪圖操作。
    ctx.translate(left, top); //將繪圖原點移動到控制項的位置
    
    // 根據對象的旋轉角度旋轉繪圖上下文,確保控制項的方向與對象一致。
    ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
    
	// 繪製一個綠色的圓形作為控制項的背景
    ctx.fillStyle = "green";
    ctx.beginPath(); 
    ctx.arc(0, 0, size / 2, 0, 2 * Math.PI);
    ctx.fill();

	// 在綠色圓形上繪製一個白色的小正方形
    ctx.fillStyle = "white";
    ctx.fillRect(-size / 4, -size / 4, size / 2, size / 2);
    ctx.restore(); //恢復之前保存的繪圖上下文狀態,清除所有的變換。
  }

方式2: 用其他圖片渲染的方式畫控制項的圖

import DelateIcon from '@/public/images/delete.js'; // 我之前是出來的方式是引用 js 檔裡的 svg 作為圖片來源

// --- 物件控制選項的圖片渲染 ---
const renderDeleteIcon = (ctx, left, top) => {
	const imgDelete = new Image();
	imgDelete.src = DelateIcon;
	const size = 24;
	ctx.save();
	ctx.translate(left, top);
	ctx.drawImage(imgDelete, -size / 2, -size / 2, size, size);
	ctx.restore();
};


const onDelete = function (eventData, transform) { const canvas = transform.target.canvas; canvas.remove(canvas.getActiveObject()); canvas.renderAll(); };


fabric.Object.prototype.controls.clone = new fabric.Control({
  x: -0.5,
  y: -0.5,
  // ... 其餘設定
  mouseUpHandler: onDelete, // 這樣把 function 內容抽出來在外面定義也可以喔
  render:renderDeleteIcon 
  })

今日🌰:fabricjs 控制項-codepen


上一篇
Day15-Fabric.js 中的動畫效果
下一篇
Day17-fabric.js 進階組合技!想要在畫布範圍之外的控制點也可以被看到
系列文
一起來玩圖像編輯器:Fabric.js 的實戰修煉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言