主頁 > 企業開發 > 圖片上傳那點事

圖片上傳那點事

2020-10-08 23:59:34 企業開發

前端圖片上傳那些事兒

本文講的圖片上傳,主要是針對上傳頭像的,大家都知道,上傳頭像一般都會分成以下 4 個步驟:

選擇圖片 -> 預覽圖片 -> 裁剪圖片 -> 上傳圖片

接下來,就詳細的介紹每個步驟具體實作,

選擇圖片

選擇圖片有什么好講的呢?不就一個 input[type=file] ,然后點擊就可以了嗎?確實是這樣的,但是,我們想要做得更加的友好一些,比如需要過濾掉非圖片檔案, 或只允許從攝像頭拍斬訓取圖片等,還是需要進行一些簡單配置的,

下面就先來看看最簡單的選擇圖片:

<input type="file" />

這時候,點擊這個 input , 在 iOS 手機的顯示如下:

其中的 “瀏覽” 選項,可以查看到非圖片型別的檔案,這并不是我們想要的結果,畢竟我們只想要圖片型別,可以通過 accept 屬性來實作,如下:

<input type="file" accept="image/*">

這樣就可以過濾掉非圖片型別了,但是圖片的型別可能也太多了, 有些可能服務器不支持,所以,如果想保守一些,只允許 jpg 和 png 型別,可以寫成這樣:

<input type="file" accept="image/jpg, image/jpeg, image/png">

或:

<input type="file" accept=".jpg, .jpeg, .png">

OK, 過濾非圖片的需求搞定了,但是有時候 ,產品還要求只能從攝像頭采集圖片,比如需要上傳證件照,防止從網上隨便找別人的證件上傳,那 capture 屬性就可以派上用場了:

<input type="file" accept="image/*" capture>

這時候,就不能從檔案系統中選擇照片了,只能從攝像頭采集,到了這一步,可能覺得很完美了,但是還有個問題,可能有些變態產品要求默認打開前置攝像頭采集圖片,比如就是想要你的自拍照片, capture 默認呼叫的是后置攝像頭,默認啟用前置攝像頭可以設定 capture="user" ,如下:

<input type="file" accept="image/*" capture="user">

好啦,關于選擇圖片的就講么這么多了,有個注意的地方是,可能有些配置在兼容性上會有一些問題,所以需要在不同的機型上測驗一下看看效果,

下面再來談談預覽圖片的實作,

預覽圖片

在遠古時代,前端并沒有預覽圖片的方法,當時的做法時,用戶選擇圖片之后,立刻把圖片上傳到服務器,然后服務器回傳遠程圖片的 url 給前端顯示,這種方法略顯麻煩,而且會浪費用戶的流量,因為用戶可能還沒有確定要上傳,你卻已經上傳了,幸好,遠古時代已經離我們遠去了,現代瀏覽器已經實作了前端預覽圖片的功能,常用的方法有兩個,分別是 URL.createObjectURL()  FileReader ,雖然他們目前均處在 w3c 規范中的 Working Draft 階段, 但是大多數的現代瀏覽器都已經良好的支持了, 下面就介紹一下如何使用這兩個方法,

1. 使用 URL.createObjectURL 預覽

URL.createObjectURL() 靜態方法會創建一個 DOMString,其中包含一個表示引數中給出的物件的 URL,這個 URL 的生命周期和創建它的視窗中的 document 系結,這個新的URL 物件表示指定的 File 物件或 Blob 物件,用法用下:

objectURL = URL.createObjectURL(object);

其中, object 引數指 用于創建 URL 的 File 物件、Blob 物件或者 MediaSource 物件,

對于我們的 input[type=file] 而言, input.files[0] 可以獲取到當前選中檔案的 File 物件,示例代碼如下:

 <input id="inputFile" type="file" accept="image/*">
  <img src="" id="previewImage" alt="圖片預覽">
  <script>
    const $ = document.getElementById.bind(document);
    const $inputFile = $('inputFile');
    const $previewImage = $('previewImage');
    $inputFile.addEventListener('change', function() {
      const file = this.files[0];
      $previewImage.src = file ? URL.createObjectURL(file) : '';
    }, this);
  </script>

具體用法可以參考 MDN上的 URL.createObjectURL(),

2. 使用 FileReader 預覽

FileReader 物件允許Web應用程式異步讀取存盤在用戶計算機上的檔案(或原始資料緩沖區)的內容,使用 File 或 Blob 物件指定要讀取的檔案或資料,同理的,我們也可以通過 input.files[0] 獲取到當前選中的圖片的 File物件,

特別注意,FileReader 和 是異步讀取檔案或資料的!

下面是使用 FileReader 預覽圖片的示例:

<input id="inputFile" type="file" accept="image/*">
  <img src="" id="previewImage" alt="圖片預覽">
  <script>
    const $ = document.getElementById.bind(document);
    const $inputFile = $('inputFile');
    const $previewImage = $('previewImage');
    $inputFile.addEventListener('change', function() {
      const file = this.files[0];
      const reader = new FileReader();
      reader.addEventListener('load', function() {
        $previewImage.src = reader.result;
      }, false);
      
      if(file) {
        reader.readAsDataURL(file);
      }
    }, false)
  </script>

會發現, FileReader 會相對復雜一些.

更多關于 FileReader 的用法 ,可以參考 MDN 檔案 FileReader

兩種方法的對比

我個人更加傾向于使用 URL.createObjectURL() ,主要原先它的 API 簡潔,同步讀取,并且他回傳的是一個 URL ,比 FileReaer 回傳的base64 更加精簡,兼容性上,兩者都差不多,都是在 WD 的階段,性能上的對比, 在 chrome 上, 選擇了一張 2M 的圖片, URL.createObjectURL() 用時是 0 , 而 FileReader 用時 20ms 左右, 0 感覺不太合理,雖然這個方法立刻就會回傳一個 URL ,但是我猜測實際上這個 URL 指定的內容還沒有生成好,應該是異步生成的,然后才渲染出來的,所以并沒有很好的辦法來對比他們的性能,

如果想要學習更多關于圖片預覽,可以閱讀以下兩篇文章:

  • 使用FileReader實作前端圖片預覽
  • js圖片/視頻預覽 - URL.createObjectURL()

裁剪圖片

關于圖片的裁剪,很自然的會想到使用 canvas ,確實是要通過 canvas, 但是如果全部我們自己來實作,可能需要做比較多的作業,所以為了省力,我們可以站在巨人的肩膀上,比較優秀的圖片裁剪庫是 cropperjs , 該庫可以對圖片進行縮放、移動和旋轉,

cropperjs 的詳細配置這里就不展開了 ,需要的可以自己去看檔案就好,下面我們就以這個庫為基礎,實作一個裁剪人臉的例子:

<input id="inputFile" type="file" accept="image/*">
  <img class="preview-image" id="previewImage" src="" alt="">
  <!-- cropper裁剪框 -->
  <div class="cropper" id="cropper">
    <div class="inner">
      <div class="face-container">
        <img class="cropper-image" id="cropperImage">
      </div>
      <div class="tips">請將面部區域置于人臉框架內</div>
      <div class="toolbar">
        <div class="btn" id="confirm">確認</div>
      </div>
    </div>
  </div>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.js"></script>
 
 <style>
  .preview-image,
  .cropper-image {
    max-width: 100%;
  }

  .cropper {
    display: none;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background: #ccc;
    font-size: 0.27rem;
    text-align: center;
  }

  .inner {
      height: 100%;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }

    .face-container {
      position: relative;
      width: 320px;
      height: 320px;
      margin: 50px  auto;
    }

    .cropper-modal {
      background: url('https://ok.166.net/gameyw-misc/opd/squash/20191028/152551-m37snfsyu1.png') center no-repeat;
      background-size: 100% 100%;
      opacity: 1;
    }

    .cropper-bg {
      background: none;
    }

    .cropper-view-box {
      opacity: 0;
    }

    .tips {
      font-size: 16px;
    }

    .toolbar {
      display: flex;
      justify-content: center;
      margin: 50px 0;
    }

    .btn {
      width: 150px;
      line-height: 40px;
      font-size: 20px;
      text-align: center;
      color: #fff;
      background: #007fff;
    }
  </style>

  <script>
  const $ = document.getElementById.bind(document);
  const $cropper = $('cropper');
  const $inputFile = $('inputFile');
  const $previewImage = $('previewImage');
  const $cropperImage = $('cropperImage');
  const $confirmBtn = $('confirm')
  let cropperInstance = null;

  // 選擇圖片后,顯示圖片裁剪框
  $inputFile.addEventListener('change', function() {
    const file = this.files[0];
    if(!file) return;
    $cropperImage.src = URL.createObjectURL(file);
    showCropper();
  }, false);

  // 點擊確認按鈕,將裁剪好的圖片放到 img 標簽顯示,
  $confirmBtn.addEventListener('click', function() {
    const url = cropperInstance.getCroppedCanvas().toDataURL("image/jpeg", 1.0);
    $cropper.style.display = 'none';
    $previewImage.src = url;
  }, false);


  function showCropper() {
    $cropper.style.display = 'block';
    cropperInstance && cropperInstance.destroy();
    cropperInstance = new Cropper($cropperImage, {
      viewMode: 1,
      aspectRatio: 1,
      autoCropArea: 1,
      dragMode: 'move',
      guides: false,
      highlight: false,
      cropBoxMovable: false,
      cropBoxResizable: false
    });
  }
  </script>

 

效果圖如下:

上傳

前面的操作已經完成了圖片上傳前的準備,包括選擇圖片、預覽圖片、編輯圖片等,那接下來就可以上傳圖片了,上面的例子中,使用了 cropperInstance.getCroppedCanvas() 方法來獲取到對應的 canvas 物件 ,有了 canvas 物件就好辦了,因為 canvas.toBlob() 方法可以取得相應的 Blob 物件,然后,我們就可以把這個 Blob 物件添加到 FromData 進行無重繪的提交了,大概的代碼如下:

function uploadFile() {
    cropperInstance.getCroppedCanvas().toBlob(function(blob) {
      const formData = https://www.cnblogs.com/Army-Knife/p/new FormData();
      formData.append('avatar', blob);
      fetch('xxxx', {
        method: 'POST',
        body: formData
      });
    });
  }

這段代碼并不能真正執行,因為我們還沒有對應的后端服務器,如果想要嘗試上傳圖片的朋友,可以參考一下這篇文章 寫給新手前端的各種檔案上傳攻略,從小圖片到大檔案斷點續傳,由于篇幅原因,這里就不展開啦,

后記

關于圖片上傳的介紹,差不多不到些結束了,但是之前在 iPhone 和 小米 手機上,遇到一個奇怪的問題: 就是我使用前置攝像頭自拍出來的照片,選擇之后 ,會自逆時針旋轉 90 度,比如像下圖:

拍照的時候明明就是正著拍的,為什么預覽就會變成橫著了呢?當時第一次遇到這個問題的時候,也覺得好奇怪,后來查了一下,得知這是因為拍照時,相機都會記錄拍照的角度資訊,可能 iPhone 前置攝像頭記錄的角度資訊和其他的有點不一樣,而 iPhone 自己的相冊在瀏覽照片時,自動糾正了角度 ,而瀏覽器卻沒有糾正,所以才會出現這個旋轉,

為了解決這個問題,需要使用 EXIF 這個庫來處理,

我剛剛試了一下,發現我的 iPhone 現在竟然不會有這個問題了,大概是半年前,當時在做一個需求時,自拍的圖片會發生這種旋轉,有可能是 iOS 系統升級后, 已經修復了這個問題,而現在身邊又沒有小米手機, 所以也不好復現,還好,當時我保存了一張會自動旋轉的圖片,大家可以到這里下載:

https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png

這圖片下載后,用電腦的圖片查看器打開是正常的,但是,在瀏覽器中,選擇這個圖片后,使用 URL.createObjectURL()  FileReader 來預覽就會發生旋轉,甚至直接 img 標簽引入也會逆時針旋轉了 90 度,比如:

<img src="https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png">

效果如下:

下面就以這張圖片為例,介紹一下如何使用 EXIF 來檢測圖片角度,關于 EXIF 的詳細用法大家可以到 github 的主頁上查看 https://github.com/exif-js/exif-js

<img id="exifImage" src="https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png" alt="">

  <script src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.js"></script>
  <script>
      const $exifImage = document.getElementById('exifImage');
      $exifImage.onload = function() {
        EXIF.getData($exifImage, function() {
          let allMetaData = EXIF.getAllTags(this);
          console.log(allMetaData.Orientation); // 6
        });
      };
  </script>

上面代碼的輸出 allMetaData.Orientation 的結果為 6 , 那 6 到底是什么意思呢? 可以參考這個篇文章 http://sylvana.net/jpegcrop/exif_orientation.html 里面有個表格:

如果這個表格看不太懂,再參考一下這篇文章 JPEG Orientation,里有個圖:

可以看出,攝像頭資訊是逆時針旋轉了 90 度,那要怎么糾正呢?就順時針旋轉 90 度抵消掉這個角度就好,

事實上, CropperJS 也會檢測圖片的 EXIF 資訊,并且會自動糾正角度的,詳情參考 https://github.com/fengyuanchen/cropperjs#checkorientation
這里也提到了,但只支持讀取 jpg 圖片的 EXIF 資訊,而我們這張圖片是 PNG 所以并不支持,

有個 CSS 屬性叫做 image-orientation , 它有個值叫做 from-image , 就是使用圖片的 EXIF 資料來旋轉的,可惜,目前 chrome 不支持該屬性,有興趣的可以了解一下,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/163653.html

標籤:JavaScript

上一篇:JS頁面跳轉加密解密URL引數

下一篇:JS---案例:美女時鐘

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more