本文簡介 點贊 + 關註 + 收藏 = 學會了 學習 Fabric.js,我的建議是看文檔不如看 demo。 本文實現的功能:將元素拖進到畫布中並生成對應的圖形或圖片。 效果如下圖所示: 思路 要實現以上效果,需要考慮以下幾點: 元素有拖拽功能。 能在畫布中生成對應的元素。 畫佈有可能縮放。 畫佈有 ...
本文簡介
點贊 + 關註 + 收藏 = 學會了
學習 Fabric.js
,我的建議是看文檔不如看 demo。
本文實現的功能:將元素拖進到畫布中並生成對應的圖形或圖片。
效果如下圖所示:
思路
要實現以上效果,需要考慮以下幾點:
- 元素有拖拽功能。
- 能在畫布中生成對應的元素。
- 畫佈有可能縮放。
- 畫佈有可能移動。
- 畫布的位置可能在頁面的某處。
- 在3和4情況下還能在準確的位置生成元素。
基於以上幾點,我得出以下解法。
- 解1:要讓
HTML
元素具備拖拽功能,只要將draggable
屬性設置為true
即可。 - 解2:
Fabric.js
創建元素可看 《Fabric.js 從入門到膨脹》的基礎圖形篇,要創建圖片可以看 圖片篇。 - 解3:縮放畫布我在 《Fabric.js 縮放畫布》 里講解過。
- 解4:移動畫布我在 《Fabric.js 拖拽平移畫布》 里講解過。
- 解5:畫布的左上角不一定在body的左上角,也就是滑鼠當前位置可能和畫布對應的坐標不一樣,需要通過加減法計算一下。
- 解6:
Fabric.js
提供了一個方法可以將滑鼠當前坐標轉換為畫布對應的真實坐標,這個方法叫restorePointerVpt
。
動手
我分幾個步驟慢慢實現上述功能。我知道你很急,但你先別急
創建畫布及元素
<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/[email protected]/dist/fabric.min.js"></script>
<script>
let canvas = null
// 初始化畫布
function initCanvas() {
// 創建畫布
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個事件監聽的主要功能是移動畫布,在按住 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
})
// 監聽滑鼠滾輪縮放事件,可以縮放畫布
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(
{ // 關鍵點
x: opt.e.offsetX,
y: opt.e.offsetY
},
zoom // 傳入修改後的縮放級別
)
})
}
initCanvas()
</script>
上面的代碼使用了 Fabric.js
綁定了頁面上的畫布,並創造了一個粉紅色的矩形。
按住 alt
後,使用滑鼠在畫布上可以拖拽畫布。
在畫布上滾動滑鼠滾輪可以縮放畫布。
左側的元素列表也將 draggable
屬性設置為 true
,元素具備拖拽功能了。
監聽元素放進畫布
我們還需要使用一個變數來記錄當前拖拽的是什麼元素。
<!-- 省略部分代碼 -->
<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 // 當前要創建的元素類型
// 拖拽開始時就記錄當前打算創建的元素類型
function onDragstart(type) {
currentType = type
}
</script>
前面的代碼已經知道拖拽時需要生成什麼類型的元素了,現在還需要知道生成到畫布的哪個地方(x和y坐標)
鬆開滑鼠時,需要計算滑鼠在畫布的坐標。這裡的坐標是指畫布在頁面中的位置轉換出來的坐標,而且還要計算畫布拖拽和縮放過的情況。
我的做法是通過 canvas
元素的 getBoundingClientRect()
方法返回的對象中獲取到 top
和 left
兩個數據。這兩個數據就是 canvas
元素距離頁面頂部和左側的距離。
然後通過滑鼠當前坐標減去 canvas
距離頁面頂部或左側的距離,計算出滑鼠點擊畫布的真實坐標。
但畫佈有可能拖拽和縮放,所以需要通過 Fabric.js
提供的 restorePointerVpt()
方法將坐標轉換一下。
於是有了下麵的代碼。
// 省略部分代碼......
canvas.on('drop', function(opt) {
// 畫布元素距離瀏覽器左側和頂部的距離
let offset = {
left: canvas.getSelectionElement().getBoundingClientRect().left,
top: canvas.getSelectionElement().getBoundingClientRect().top
}
// 滑鼠坐標轉換成畫布的坐標(未經過縮放和平移的坐標)
let point = {
x: opt.e.x - offset.left,
y: opt.e.y - offset.top,
}
// 轉換後的坐標,restorePointerVpt 不受視窗變換的影響
let pointerVpt = canvas.restorePointerVpt(point)
})
生成對應的元素
上面的代碼最後得出的 pointerVpt
就是轉換後最終的坐標,我們在這個坐標上生成元素即可。
// 省略部分代碼......
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
}
// 創建完元素,把當前操作的元素類型設置回 null
currentElType = null
})
// 創建矩形
function createRect(top, left) {
canvas.add(new fabric.Rect({
top,
left,
width: 60,
height: 60,
fill: 'pink'
}))
}
// 創建圓形
function createCircle(top, left) {
canvas.add(new fabric.Circle({
top,
left,
radius: 30,
fill: 'pink'
}))
}
// 創建圖片元素
function createImg(top, left) {
fabric.Image.fromURL('./picture.jpg', oImg => {
oImg.top = top
oImg.left = left
canvas.add(oImg)
})
}
代碼倉庫
前面都是碎碎念,代碼一段一段的。如果需要完整版代碼可以打開鏈接自取。
推薦閱讀