記錄--uni-app實現京東canvas拍照識圖功能

来源:https://www.cnblogs.com/smileZAZ/archive/2023/02/21/17141973.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 最近公司出了一個新的功能模塊(如下圖),大提上可以描述為實現拍照完上傳圖片,拖動四方框拍照完成上傳功能,大體樣子如下圖。但是我找遍了 dcloud 插件市場,找到的插件都是移動背景圖片來實現裁剪的,跟京東的功能是相反的,沒辦法只能自己來實 ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

最近公司出了一個新的功能模塊(如下圖),大提上可以描述為實現拍照完上傳圖片,拖動四方框拍照完成上傳功能,大體樣子如下圖。但是我找遍了 dcloud 插件市場,找到的插件都是移動背景圖片來實現裁剪的,跟京東的功能是相反的,沒辦法只能自己來實現這麼一個插件。

第一步

首先就需要實現一個四方框的功能了。從上圖可知,四方框有一下幾個特點

  1. 四個角粘連外框,隨著框的大小和移動範圍緊縛移動
  2. 四方框可隨意四個方向拖動
  3. 方框外區域陰影不影響方框內

那麼我們根據這個特性來實現下這個功能,對於 css 規範的話使用 bem 規範

<div class="clip__content">
  <div v-for="(item, index) in 4" :key="index" class="clip__edge"></div>
</div>

/more

$edge-border-width: 6rpx;
.clip {
  &__content {
    position: fixed;
    width: 400rpx;
    height: 400rpx;
    left: 0;
    top: 0;
    border: 1px solid red;
    z-index: 4;
    overflow: hidden;
    box-shadow: rgba(0, 0, 0, 0.5) 0 0 0 200vh;
  }
  &__edge {
    position: absolute;
    width: 34rpx;
    height: 34rpx;
    border: 10rpx solid red;
    pointer-events: auto;
    z-index: 2;
    &::before {
      content: "";
      position: absolute;
      z-index: 2;
      width: 40rpx;
      height: 40rpx;
      background-color: transparent;
    }
    &:nth-child(1) {
      left: $edge-border-width;
      top: $edge-border-width;
      border-bottom-width: 0 !important;
      border-right-width: 0 !important;
      &:before {
        top: -50%;
        left: -50%;
      }
    }
    &:nth-child(2) {
      right: $edge-border-width;
      top: $edge-border-width;
      border-bottom-width: 0 !important;
      border-left-width: 0 !important;
      &:before {
        top: -50%;
        left: 50%;
      }
    }
    &:nth-child(3) {
      left: $edge-border-width;
      bottom: $edge-border-width;
      border-top-width: 0 !important;
      border-right-width: 0 !important;
      &:before {
        bottom: -50%;
        left: -50%;
      }
    }
    &:nth-child(4) {
      right: $edge-border-width;
      bottom: $edge-border-width;
      border-top-width: 0 !important;
      border-left-width: 0 !important;
      &:before {
        bottom: -50%;
        left: 50%;
      }
    }
  }

根據上面的 html 和 css 出來的樣式大概如下圖 外部的陰影效果我們用: box-shadow: rgba(0, 0, 0, 0.5) 0 0 0 200vh 來達成

第二步

第二步的話就要實現移動功能了,這裡是一個比較考驗耐心的地方,因為涉及到多個方向的變化,需要不斷地進行調試,在此之前需要先分析下四個角變化的特性,下麵先看 4 個角的移動特性(以 H5 思維)

  1. 第一個角的移動會改變方框的 left,top,width,right4 個值
  2. 第二個角的移動會改變方框的 top,with,height3 個值
  3. 第三個角的移動會改變方框的 left, width,height3 個值
  4. 第四個角的移動會改變方框的 width,height2 個值
  5. 四個角的移動都不能小於 4 個角的寬高,四個角的移動都不能超過屏幕,相應的邏輯需要做一下限制

首先需要獲取下屏幕寬度,區域高度(因為頭部可能會有導航欄目占位,所以不拿屏幕高度),四方框初始寬高,

uni.getSystemInfo({
  success: res => {
    console.log(res)
    this.systemInfo = res
  }
})
uni
  .createSelectorQuery()
  .select('.clip__content')
  .fields({ size: true }, data => {
    this.width = data.width
    this.height = data.height
  })
  .exec()
uni
  .createSelectorQuery()
  .select('.clip')
  .fields({ size: true }, data => {
    this.screenHeight = data.height
  })
  .exec()

後續的話就可以進行四個角拖拽了,這裡用到了 touchStart 和 touchMove,動態地為方框綁定樣式

<div
  v-for="(item, index) in 4"
  class="clip__edge"
  @touchstart.stop.prevent="edgeTouchStart"
  @touchmove.stop.prevent="e => edgeTouchMove(e, index)"
  @touchend.stop.prevent="edgeTouchEnd"
></div>

接下來開始寫邏輯

edgeTouchStart(e) {
  // 記錄坐標xy初始位置
  this.clientX = e.changedTouches[0].clientX;
  this.clientY = e.changedTouches[0].clientY;
},
edgeTouchMove(e, index) {
  const currX = e.changedTouches[0].clientX;
  const currY = e.changedTouches[0].clientY;
  // 記錄坐標差
  const moveX = currX - this.clientX;
  const moveY = currY - this.clientY;
  // 更新坐標位置
  this.clientX = currX;
  this.clientY = currY;
  const { width, height, left, top, screenHeight } = this;
  const { screenWidth } = this.systemInfo;
  // 初始化最大寬高
  let maxWidth = 0,
    maxHeight = 0,
    maxTop = top + moveY < 0 ? 0 : top + moveY,
    maxLeft = left + moveX < 0 ? 0 : left + moveX;
  // 四個角的寬高限制
  if (index % 2 === 0) {
    maxWidth = width - moveX > screenWidth ? screenWidth : width - moveX;
  } else {
    maxWidth = width + moveX > screenWidth ? screenWidth : width + moveX;
  }
  if (index < 2) {
    maxHeight =
      height - moveY > screenHeight ? screenHeight : height - moveY;
  } else {
    maxHeight =
      height + moveY > screenHeight ? screenHeight : height + moveY;
  }

  // 四個角的規則計算邏輯 四邊方框暫定40 更詳細的要用.createSelectorQuery()去拿
  if (index === 0) {
    if (width - moveX <= 40 || height - moveY <= 40) return;
    console.log(maxLeft);
    this.clipStyle = {
      width: maxWidth,
      height: maxHeight,
      left: maxLeft,
      top: maxTop,
    };
    this.width = maxWidth;
    this.height = maxHeight;
    this.top = maxTop;
    this.left = maxLeft;
    // 右上角
  } else if (index === 1) {
    if (width + moveX <= 40 || height - moveY <= 40) return;
    this.clipStyle = {
      width: maxWidth,
      height: maxHeight,
      left,
      top: maxTop,
    };

    this.width = maxWidth;
    this.height = maxHeight;
    this.top = maxTop;
  } else if (index === 2) {
    if (width - moveX <= 40 || height + moveY <= 40) return;
    this.clipStyle = {
      width: maxWidth,
      height: maxHeight,
      left: maxLeft,
      top,
    };

    this.width = maxWidth;
    this.height = maxHeight;
    this.left = maxLeft;
  } else if (index === 3) {
    if (width + moveX <= 40 || height + moveY <= 40) return;
    this.clipStyle = {
      width: maxWidth,
      height: maxHeight,
      left,
      top,
    };

    this.width = maxWidth;
    this.height = maxHeight;
  }
}

效果如下圖

第三步

四個角拖拽邏輯完善之後,下一步目標就是做四方框的拖拽,這邊需要對四方框的拖拽做一次限制

<div
  class="clip__content"
  :style="style"
  @touchstart.stop.prevent="clipTouchStart"
  @touchmove.stop.prevent="clipTouchMove"
>
  ...
</div>
clipTouchStart(e) {
  this.touchX = e.changedTouches[0].pageX;
  this.touchY = e.changedTouches[0].pageY;
},
clipTouchMove(e) {
  const { screenWidth } = this.systemInfo;
  const currX = e.changedTouches[0].pageX;
  const currY = e.changedTouches[0].pageY;
  const moveX = currX - this.touchX;
  const moveY = currY - this.touchY;
  this.touchX = currX;
  this.touchY = currY;
  // 邊框限制邏輯
  if (this.left + moveX < 0) {
    this.left = 0;
  } else if (this.left + moveX > screenWidth - this.width) {
    this.left = screenWidth - this.width;
  } else {
    this.left = this.left + moveX;
  }
  if (this.top + moveY < 0) {
    this.top = 0;
  } else if (this.top + moveY > this.screenHeight - this.height) {
    this.top = this.screenHeight - this.height;
  } else {
    this.top = this.top + moveY;
  }
  this.clipStyle = {
    ...this.clipStyle,
    left: this.left,
    top: this.top,
  };
},

效果如下圖:

第四步就是做我們的截圖了,這裡用到了 canvas

<div class="clip__content">
  ...
  <canvas class="clip-canvas" canvas-id="clip-canvas"></canvas>
</div>

邏輯的話目前這個例子是使用了網路的 url 圖片 所以要進行 download,如果是不用網路圖片,那麼這一句可以刪除換成其他的獲取圖片 api

initCanvas() {
  uni.showLoading({
    title: "載入中...",
  });
  uni
    .createSelectorQuery()
    .select(".clip__content")
    .fields(
      {
        size: true,
        scrollOffset: true,
        rect: true,
        context: true,
        computedStyle: ["transform", "translateX"],
        scrollOffset: true,
      },
      (data) => {
        uni.downloadFile({
          url: this.imageUrl,
          success: (res) => {
            this.canvasInstance = uni.createCanvasContext(
              "clip-canvas",
              this
            );
            this.canvasInstance.drawImage(
              res.tempFilePath,
              -data.left,
              -data.top,
              this.systemInfo.screenWidth,
              this.screenHeight,
              0,
              0
            );
            this.canvasInstance.draw(
              false,
              (() => {
                setTimeout(() => {
                  uni.canvasToTempFilePath(
                    {
                      x: 0,
                      y: 0,
                      width: data.width,
                      height: data.height,
                      dWidth: data.width,
                      dHeight: data.height,
                      fileType: "jpg",
                      canvasId: "clip-canvas",
                      success: (data) => {
                        uni.hideLoading();

                        this.url = data.tempFilePath;
                        // this.canvasInstance.save();
                      },
                    },
                    this
                  );
                }, 500);
              })()
            );
          },
        });
      }
    )
    .exec();
},

效果如圖所示:

本文轉載於:

https://juejin.cn/post/6971977095652048910

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


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

-Advertisement-
Play Games
更多相關文章
  • PXC
    centos8下安裝pxc-80 官方地址 https://www.percona.com/doc/percona-repo-config/index.html https://www.percona.com/doc/percona-xtradb-cluster/LATEST/install/yum ...
  • 數據驅動理念已被各行各業所熟知,核心環節包括數據採集、埋點規劃、數據建模、數據分析和指標體系構建。在用戶行為數據領域,對常見的多維數據模型進行信息提煉和模型整合,可以形成一套常見的數據分析方法來發現用戶行為的內在聯繫,能更好洞察用戶的行為習慣和行為規律,幫助企業挖掘用戶數據的商業價值。 行業內... ...
  • 閱識風雲是華為雲信息大咖,擅長將複雜信息多元化呈現,其出品的一張圖(雲圖說)、深入淺出的博文(雲小課)或短視頻(雲視廳)總有一款能讓您快速上手華為雲。更多精彩內容請單擊此處。 摘要:Flink是一個批處理和流處理結合的統一計算框架,其核心是一個提供了數據分發以及並行化計算的流數據處理引擎。它的最大亮 ...
  • 摘要:你知道數倉是如何應運而生的嗎?你瞭解數倉未來的發展趨勢嗎?想知道國內數倉專家的看法嗎? 導語 數據倉庫的發展一直是備受關註的議題,隨著近年來技術的不斷演進,數倉也在更新迭代。 你知道數倉是如何應運而生的嗎?你瞭解數倉未來的發展趨勢嗎?想知道國內數倉專家的看法嗎? 今天我們邀請到了華為雲數據倉庫 ...
  • ETL的架構 ETL架構的優勢: ETL相對於EL-T架構可以實現更為複雜的數據轉化邏輯 ETL採用單獨的硬體伺服器,可以分擔資料庫系統的負載 ETL與底層的資料庫數據存儲無關,可以保持所有的數據始終在資料庫當中,避免數據的載入和導出,從而保證效率,提高系統的可監控性。 ELT主要通過資料庫引擎來實 ...
  • 九宮格圖片佈局,長按直接拖拽圖片,長按時顯示底部刪除佈局,拖拽到刪除佈局處鬆手可刪除佈局,最後添加按鈕不可拖拽,基於 BaseQuickAdapter 基礎上實現 BaseQuickAdapter 確實很好用,簡化我們的實現代碼,它本身也集成了一套拖拽實現,不過目前無法完美的滿足上面的需求,需要做一 ...
  • 背景 最近,端內在做 webView 統一的時候,個性簽名中的 WebView 替換為 CustomWebView 之後,發現字體突然變小。 一開始不知道是什麼原因,通過二分法查找最近的提交,排查之後,發現是 SignatureWebView 的繼承關係從 WebView 修改為 CustomWeb ...
  • Map Object本質上是鍵值對的集合(Hash結構),但Object只能將字元串當做鍵,這就給Object帶來了很大的限制。 let data = {} let s = { name : '東方不敗' } data[s] = '西方求敗' // 如果鍵傳入非字元串的值,會自動為字元串 conso ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...