Fabric.js 拖放元素進畫布

本文簡介

點贊 + 關(guān)注 + 收藏 = 學(xué)會了


學(xué)習(xí) Fabric.js,我的建議是看文檔不如看 demo。

本文實現(xiàn)的功能:將元素拖進到畫布中并生成對應(yīng)的圖形或圖片。

效果如下圖所示:

file



思路

要實現(xiàn)以上效果,需要考慮以下幾點:

  1. 元素有拖拽功能。
  2. 能在畫布中生成對應(yīng)的元素。
  3. 畫布有可能縮放。
  4. 畫布有可能移動。
  5. 畫布的位置可能在頁面的某處。
  6. 在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)建畫布及元素

file
<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() 方法返回的對象中獲取到 topleft 兩個數(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 拖拽創(chuàng)建元素



推薦閱讀

??《Fabric.js 橡皮擦的用法(包含恢復(fù)功能)》

??《Fabric.js 監(jiān)聽元素相交(重疊)》

??《Fabric.js 自定義子類,創(chuàng)建屬于自己的圖形》

??《Fabric.js 自由繪制圓形》

??《Fabric.js 縮放畫布》

??《Fabric.js 變換視窗》

??《Fabric.js 拖拽平移畫布》


點贊 + 關(guān)注 + 收藏 = 學(xué)會了
代碼倉庫

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容