Fabric.js 拖放元素進畫布

来源:https://www.cnblogs.com/k21vin/archive/2023/01/10/17039823.html
-Advertisement-
Play Games

本文簡介 點贊 + 關註 + 收藏 = 學會了 學習 Fabric.js,我的建議是看文檔不如看 demo。 本文實現的功能:將元素拖進到畫布中並生成對應的圖形或圖片。 效果如下圖所示: 思路 要實現以上效果,需要考慮以下幾點: 元素有拖拽功能。 能在畫布中生成對應的元素。 畫佈有可能縮放。 畫佈有 ...


本文簡介

點贊 + 關註 + 收藏 = 學會了


學習 Fabric.js,我的建議是看文檔不如看 demo。

本文實現的功能:將元素拖進到畫布中並生成對應的圖形或圖片。

效果如下圖所示:

file



思路

要實現以上效果,需要考慮以下幾點:

  1. 元素有拖拽功能。
  2. 能在畫布中生成對應的元素。
  3. 畫佈有可能縮放。
  4. 畫佈有可能移動。
  5. 畫布的位置可能在頁面的某處。
  6. 在3和4情況下還能在準確的位置生成元素。

基於以上幾點,我得出以下解法。

  • 解1:要讓 HTML 元素具備拖拽功能,只要將 draggable 屬性設置為 true 即可。
  • 解2:Fabric.js 創建元素可看 《Fabric.js 從入門到膨脹》的基礎圖形篇,要創建圖片可以看 圖片篇
  • 解3:縮放畫布我在 《Fabric.js 縮放畫布》 里講解過。
  • 解4:移動畫布我在 《Fabric.js 拖拽平移畫布》 里講解過。
  • 解5:畫布的左上角不一定在body的左上角,也就是滑鼠當前位置可能和畫布對應的坐標不一樣,需要通過加減法計算一下。
  • 解6:Fabric.js 提供了一個方法可以將滑鼠當前坐標轉換為畫布對應的真實坐標,這個方法叫 restorePointerVpt


動手

我分幾個步驟慢慢實現上述功能。我知道你很急,但你先別急


創建畫布及元素

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/[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() 方法返回的對象中獲取到 topleft 兩個數據。這兩個數據就是 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)
  })
}


代碼倉庫

前面都是碎碎念,代碼一段一段的。如果需要完整版代碼可以打開鏈接自取。

Fabric.js 拖拽創建元素



推薦閱讀


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 定義: 相同文檔結構(Mapping)文檔的結合 由唯一索引名稱標定 一個集群中有多個索引 不同的索引代表不同的業務類型數據 註意事項: 索引名稱不支持大寫 索引名稱最大支持255個字元長度 欄位的名稱,支持大寫,不過建議全部統一小寫 ...
  • 檢索數據 SELECT語句 SELECT語句用途是從一個或多個表中檢索信息。為了使用SELECT檢索表數據,必須至少給出兩條信息——想選擇什麼,以及從什麼地方選擇。 檢索單個列 利用SELECT語句從products表中檢索一個名為prod_name的列: SELECT prod_name FROM ...
  • 摘要:增量備份是重要的常規備份策略,正確快速識別增量變化文件的相關信息對增量備份至關重要。 本文分享自華為雲社區《其疾如風,GaussDB(DWS)增量備份核心設計》,作者: 我的橘子呢 。 1、認識增量備份 GaussDB(DWS)數倉的備份恢復工具Roach支持集群級增量備份。全量備份會將源數據 ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 作者: 楊延昭 文章來源:GreatSQL社區投稿 在資料庫中,除傳統的計算資源(CPU、RAM、IO)的爭用以外,數據也是一種供許多用戶共用的資源。如 ...
  • 華為HMS Core 視頻編輯服務依托自身AI技術的核心優勢,在最新版本HMS Core 6.8.0中上線了全新的視頻美顏功能,能對指定圖片或視頻中的人臉實現磨皮、美白、大眼、瘦臉的美顏效果,適用於直播、相機、視頻剪輯、圖片處理等場景中,打造獨特自然的美顏效果。 HMS Core視頻美顏功能在技術上 ...
  • 一. Rollbar可以幫你解決哪些問題 無特別說明,文中Rollbar統指Rollbar-flutter 1. 代碼復用 Rollbar官方文檔說是純Dart實現,該特征意味著自帶”代碼復用”光環。 如圖當接入端(Third-APP)調用Rollbar SDK時表示包含的網路(異常數據上傳等)和存 ...
  • 方法一: 在 JavaScript 中,可以使用 URLSearchParams 對象來處理 URL 中的查詢字元串。 序列化(將 JavaScript 對象轉換為查詢字元串)可以使用 URLSearchParams 對象的 append() 方法,如下所示: let params = new UR ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 測試環境好好地功能,上了生產,莫名其妙報錯,開始以為是沒有設置Js安全介面功能變數名稱,結果讓相應人員一查,已經設置了相應的功能變數名稱,再看下公眾號內的介面許可權部分,相關js許可權也都是已授權,沒辦法,只能按照微信文檔上的常見錯誤及解決方法,設置debu ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...