本文簡介
點贊 + 關(guān)注 + 收藏 = 學(xué)會了
學(xué)習(xí) Fabric.js,我的建議是看文檔不如看 demo。
本文實現(xiàn)的功能:將元素拖進到畫布中并生成對應(yīng)的圖形或圖片。
效果如下圖所示:

思路
要實現(xiàn)以上效果,需要考慮以下幾點:
- 元素有拖拽功能。
- 能在畫布中生成對應(yīng)的元素。
- 畫布有可能縮放。
- 畫布有可能移動。
- 畫布的位置可能在頁面的某處。
- 在3和4情況下還能在準確的位置生成元素。
基于以上幾點,我得出以下解法。
- 解1:要讓
HTML元素具備拖拽功能,只要將draggable屬性設(shè)置為true即可。 - 解2:
Fabric.js創(chuàng)建元素可看 《Fabric.js 從入門到膨脹》的基礎(chǔ)圖形篇,要創(chuàng)建圖片可以看 圖片篇。 - 解3:縮放畫布我在 《Fabric.js 縮放畫布》 里講解過。
- 解4:移動畫布我在 《Fabric.js 拖拽平移畫布》 里講解過。
- 解5:畫布的左上角不一定在body的左上角,也就是鼠標當前位置可能和畫布對應(yīng)的坐標不一樣,需要通過加減法計算一下。
- 解6:
Fabric.js提供了一個方法可以將鼠標當前坐標轉(zhuǎn)換為畫布對應(yīng)的真實坐標,這個方法叫restorePointerVpt。
動手
我分幾個步驟慢慢實現(xiàn)上述功能。我知道你很急,但你先別急
創(chuàng)建畫布及元素

<div class="box">
<div class="data_list">
<div class="data_item rect" draggable="true"></div>
<div class="data_item circle" draggable="true"></div>
<div class="data_item img" draggable="true"></div>
</div>
<canvas id="c" style="border: 1px solid #ccc;"></canvas>
</div>
<script src="https://unpkg.com/fabric@5.2.1/dist/fabric.min.js"></script>
<script>
let canvas = null
// 初始化畫布
function initCanvas() {
// 創(chuàng)建畫布
canvas = new fabric.Canvas('c', {
width: 800,
height: 600
})
// 矩形
const rect = new fabric.Rect({
top: 30,
left: 30,
width: 60,
height: 60,
fill: 'pink'
})
// 將矩形添加到畫布中
canvas.add(rect)
// 接下來3個事件監(jiān)聽的主要功能是移動畫布,在按住 alt 后鼠標可以拖拽畫布
// 按下鼠標事件
canvas.on('mouse:down', function (opt) {
var evt = opt.e;
if (evt.altKey === true) {
this.isDragging = true
this.lastPosX = evt.clientX
this.lastPosY = evt.clientY
}
})
// 移動鼠標事件
canvas.on('mouse:move', function (opt) {
if (this.isDragging) {
var e = opt.e;
var vpt = this.viewportTransform;
vpt[4] += e.clientX - this.lastPosX
vpt[5] += e.clientY - this.lastPosY
this.requestRenderAll()
this.lastPosX = e.clientX
this.lastPosY = e.clientY
}
})
// 松開鼠標事件
canvas.on('mouse:up', function (opt) {
this.setViewportTransform(this.viewportTransform)
this.isDragging = false
})
// 監(jiān)聽鼠標滾輪縮放事件,可以縮放畫布
canvas.on('mouse:wheel', opt => {
const delta = opt.e.deltaY // 滾輪,向上滾一下是 -100,向下滾一下是 100
let zoom = canvas.getZoom() // 獲取畫布當前縮放值
zoom *= 0.999 ** delta
if (zoom > 20) zoom = 20 // 限制最大縮放級別
if (zoom < 0.01) zoom = 0.01 // 限制最小縮放級別
// 以鼠標所在位置為原點縮放
canvas.zoomToPoint(
{ // 關(guān)鍵點
x: opt.e.offsetX,
y: opt.e.offsetY
},
zoom // 傳入修改后的縮放級別
)
})
}
initCanvas()
</script>
上面的代碼使用了 Fabric.js 綁定了頁面上的畫布,并創(chuàng)造了一個粉紅色的矩形。
按住 alt 后,使用鼠標在畫布上可以拖拽畫布。
在畫布上滾動鼠標滾輪可以縮放畫布。
左側(cè)的元素列表也將 draggable 屬性設(shè)置為 true,元素具備拖拽功能了。
監(jiān)聽元素放進畫布
我們還需要使用一個變量來記錄當前拖拽的是什么元素。
<!-- 省略部分代碼 -->
<div class="data_list">
<div class="data_item rect" draggable="true" ondragstart="onDragstart('rect')"></div>
<div class="data_item circle" draggable="true" ondragstart="onDragstart('circle')"></div>
<div class="data_item img" draggable="true" ondragstart="onDragstart('img')"></div>
</div>
<script>
let currentElType = null // 當前要創(chuàng)建的元素類型
// 拖拽開始時就記錄當前打算創(chuàng)建的元素類型
function onDragstart(type) {
currentType = type
}
</script>
前面的代碼已經(jīng)知道拖拽時需要生成什么類型的元素了,現(xiàn)在還需要知道生成到畫布的哪個地方(x和y坐標)
松開鼠標時,需要計算鼠標在畫布的坐標。這里的坐標是指畫布在頁面中的位置轉(zhuǎn)換出來的坐標,而且還要計算畫布拖拽和縮放過的情況。
我的做法是通過 canvas 元素的 getBoundingClientRect() 方法返回的對象中獲取到 top 和 left 兩個數(shù)據(jù)。這兩個數(shù)據(jù)就是 canvas 元素距離頁面頂部和左側(cè)的距離。
然后通過鼠標當前坐標減去 canvas 距離頁面頂部或左側(cè)的距離,計算出鼠標點擊畫布的真實坐標。
但畫布有可能拖拽和縮放,所以需要通過 Fabric.js 提供的 restorePointerVpt() 方法將坐標轉(zhuǎn)換一下。
于是有了下面的代碼。
// 省略部分代碼......
canvas.on('drop', function(opt) {
// 畫布元素距離瀏覽器左側(cè)和頂部的距離
let offset = {
left: canvas.getSelectionElement().getBoundingClientRect().left,
top: canvas.getSelectionElement().getBoundingClientRect().top
}
// 鼠標坐標轉(zhuǎn)換成畫布的坐標(未經(jīng)過縮放和平移的坐標)
let point = {
x: opt.e.x - offset.left,
y: opt.e.y - offset.top,
}
// 轉(zhuǎn)換后的坐標,restorePointerVpt 不受視窗變換的影響
let pointerVpt = canvas.restorePointerVpt(point)
})
生成對應(yīng)的元素
上面的代碼最后得出的 pointerVpt 就是轉(zhuǎn)換后最終的坐標,我們在這個坐標上生成元素即可。
// 省略部分代碼......
canvas.on('drop', function(opt) {
// 省略部分代碼......
switch (currentType) {
case 'rect':
createRect(pointerVpt.y, pointerVpt.x)
break
case 'circle':
createCircle(pointerVpt.y, pointerVpt.x)
break
case 'img':
createImg(pointerVpt.y, pointerVpt.x)
break
}
// 創(chuàng)建完元素,把當前操作的元素類型設(shè)置回 null
currentElType = null
})
// 創(chuàng)建矩形
function createRect(top, left) {
canvas.add(new fabric.Rect({
top,
left,
width: 60,
height: 60,
fill: 'pink'
}))
}
// 創(chuàng)建圓形
function createCircle(top, left) {
canvas.add(new fabric.Circle({
top,
left,
radius: 30,
fill: 'pink'
}))
}
// 創(chuàng)建圖片元素
function createImg(top, left) {
fabric.Image.fromURL('./picture.jpg', oImg => {
oImg.top = top
oImg.left = left
canvas.add(oImg)
})
}
代碼倉庫
前面都是碎碎念,代碼一段一段的。如果需要完整版代碼可以打開鏈接自取。
推薦閱讀
??《Fabric.js 橡皮擦的用法(包含恢復(fù)功能)》
??《Fabric.js 監(jiān)聽元素相交(重疊)》
??《Fabric.js 自定義子類,創(chuàng)建屬于自己的圖形》
點贊 + 關(guān)注 + 收藏 = 學(xué)會了
代碼倉庫