iT邦幫忙

2021 iThome 鐵人賽

DAY 24
1
Modern Web

[ 重構倒數30天,你的網站不Vue白不Vue ] 系列 第 24

[重構倒數第07天] - 不用靠後端的 client 端上傳圖片預覽圖

前言

該系列是為了讓看過Vue官方文件或學過Vue但是卻不知道怎麼下手去重構現在有的網站而去規畫的系列文章,在這邊整理了許多我自己使用Vue重構很多網站的經驗分享給讀者們。

我們在開發平台的時候常常會有需要上傳商品圖片的需求,但是往往我們會需要確定我們上傳的圖片到底對不對,或是要做一些修改確認,這時候預覽的功能就很重要,在以前,前端的預覽功能會需要後端先把圖片存起來後,在前端在顯示出來,或是透過已經死去的 Flash 來達成預覽的功能,但是當html5的崛起,又出現了可以透過 canvas 的方式來做預覽的動作,但是相對來說 canvas 相對複雜一點,所以後來就出現了新的 API 可以幫我們完成前端預覽的功能。

  • URL.createObjectURL() : 可以將我們 input 所選取的 FIle 物件轉換成 Blob 物件給瀏覽器讀取。

MDN 文件:https://developer.mozilla.org/zh-TW/docs/Web/API/URL/createObjectURL

我們先來看一下我們要完成的樣子長什麼樣

01

當我今天 click 畫面上的 button 的時候,會選取我的圖片,然後確認會把這些圖片變成預覽圖放到畫面上。

首先我們來看一下我們要怎麼做,我首先會需要一個 button 的物件以及 type 是 file 的 input 元件

<template>
  <div>
    <input
      type="file"
      class="upload"
      name="imgUpload"
      multiple="multiple"
    />
    <button>上傳照片</button>
  </div>
</template>

這邊要記得把 input 加上 multiple="multiple"不然沒法多選檔案

然後把這個 input 物件給隱藏起來,因為畢竟原生的物件樣式沒有很好看,我這邊用了很多種藏起來的方式 (笑。

.upload {
  position: fixed;
  top: -500px;
  left: -500;
  z-index: -100;
  opacity: 0;
}

接下來才是重點,我們要點擊 button 來觸發 input 的 click 這個動作,才能開啟系統的選檔案視窗,所以我先新增一個 ref 變數來抓取 input 的實體。


<script>
import { ref } from "vue";
export default {
  setup() {
    // input DOM
    const inputDOM = ref(null);

    const fileChange = (e) => {
      console.log(e.target.files);
    };

    const uploadImages = () => {
      inputDOM.value.click();
    };

    return {
      inputDOM,
      fileChange,
      uploadImages,
    };
  },
};
</script>

<template>
  <div>
    <input
      ref="inputDOM"
      type="file"
      class="upload"
      name="imgUpload"
      multiple="multiple"
      @change="fileChange"
    />
    <button @click="uploadImages">上傳照片</button>
  </div>
</template>

當我點擊 button 的時候去觸發 input 的 click 函式,這樣就可以開啟系統的選取檔案視窗,然後當我選取的檔案後,它就會觸發 change 事件,然後我們就可以取得的到我們 input 的 file 物件,長成這樣。

https://ithelp.ithome.com.tw/upload/images/20210924/20125854TWGVpl2CyB.jpg

可以拿到物件之後我們就可以開始轉換成預覽用的 Blob 物件。

寫一個專門轉換的 Composition API 吧 !

因為我們的 file 專換成 Blob 是一個 input 與 output 的動作,而且可能會依照不同的需求轉換的中間過程會需要不同的處理,所以這邊我們就很適合把整個轉換的動作邏輯包成 Composition API。

首先我要檢查 URL 這個物件有沒有 在window 之中,因為不同的瀏覽器可能名字不一樣,這點很重要。

window.URL = window.URL || window.webkitURL;

在來我們要新增我們的 function

export function useFileUpdate() {
  // 預覽用檔案
  const previewMap = ref({});

  // 初始化
  const initData = () => {
    previewMap.value = {};
  };

  // 選擇多個檔案
  const setFile = async (file = []) => {
    initData();
    previewMap.value = useQueuePreview(file);
  };

  return { setFile, previewMap };
}

這個 Composition API 主要會丟出兩個東西,一個是負責接收File檔案轉換的 function,一個是轉換好的檔案物件,讓我們可以直接跑一個 v-for render的物件,所以你看這邊我 return 的兩個東西出去。

setFile這個 function 裡面有兩個東西,一個是 initData 函式,一個是 useQueuePreview 的 Composition API ,我們先來看 initData 函式,這是為了每一次在選取要預覽的圖片的時候,先去清空前一次選取的圖片所需要做的,讓整個上傳的行為變得正常。

至於 useQueuePreview 呢 ? 因為我們傳入的 File 物件是多張圖片,所以它是一個陣列,所以我需要透過迴圈,一張張的做轉換,所以我在這個檔案裡面寫了一個新的 Composition API,因為這個只會用在 useFileUpdate 裡面,所以我就不拆出去了。

// 本地預覽
function useQueuePreview(fileArr) {
  // 多圖多影片列表
  const previewMap = {};
  // 排序索引
  let idx = 0;
  for (const file of fileArr) {
    const fileData = useImageFilePreview(file);
    previewMap[idx] = fileData;
    idx++;
  }
  return previewMap;
}

// 讀取 image 資料
function useImageFilePreview(file) {
  return window.URL.createObjectURL(file);
}

我跑了一個迴圈,然後把資料格式重組成 Map 格式,至於為什麼不直接用成陣列就好,請參考前幾個章節的內容,迴圈的執行內容我又在另外的寫了一個useImageFilePreview 函式去做處理,把每個細節的動作都另外拆開,我可以很清楚的知道每個轉換的階段做了什麼事情。

完整的 useFileUpdate.js 的 code

import { ref } from "vue";

window.URL = window.URL || window.webkitURL;

// 讀取 image 資料
function useImageFilePreview(file) {
  return window.URL.createObjectURL(file);
}

// 本地預覽
function useQueuePreview(fileArr) {
  // 多圖多影片列表
  const previewMap = {};

  // 排序索引
  let idx = 0;
  for (const file of fileArr) {
    const fileData = useImageFilePreview(file);
    previewMap[idx] = fileData;
    idx++;
  }

  return previewMap;
}

export function useFileUpdate() {
  // 預覽用檔案
  const previewMap = ref({});

  // 初始化
  const initData = () => {
    previewMap.value = {};
  };

  // 選擇多個檔案
  const setFile = async (file = []) => {
    initData();
    previewMap.value = useQueuePreview(file);
    console.log(previewMap.value);
  };

  return { setFile, previewMap };
}

這樣一來,我們就可以在使用 useFileUpdate 的地方取得轉換成預覽圖的物件,直接使用。

我們來看一下使用了 setFile 後,取得 previewMap,加一個 <img /> 跑一下 v-for

<script>
import { ref } from "vue";
import { useFileUpdate } from "./composition-api/useFileUpdate.js";
export default {
  setup() {
    const { setFile, previewMap } = useFileUpdate();

    // input DOM
    const inputDOM = ref(null);

    const fileChange = (e) => {
      console.log(e.target.files);
      setFile(e.target.files);
    };

    const uploadImages = () => {
      inputDOM.value.click();
    };

    return {
      inputDOM,
      fileChange,
      uploadImages,
      previewMap,
    };
  },
};
</script>

<template>
  <div>
    <input
      ref="inputDOM"
      type="file"
      class="upload"
      name="imgUpload"
      multiple="multiple"
      @change="fileChange"
    />
    <button @click="uploadImages">上傳照片</button>
  </div>
  <div
    v-show="Object.values(previewMap).length !== 0"
    class="img_box"
    v-for="item in previewMap"
    :key="item"
  >
    <img :src="item" alt="" />
  </div>
</template>

01

codesandbox 完成範例 : https://codesandbox.io/s/vue3-upload-img-preview-c629o?file=/src/App.vue:0-938

大功告成......了嗎 !?

老闆或是客戶的腦袋永遠是你無法掌握的,我們雖然可以看到預覽圖了,但是圖片不足的地方就會露出黑色,畢竟使用者上傳圖片的時候我們不能去限制它上傳的圖片大小,所以我們需要把現在的黑底變成這樣有模糊的樣子。

https://ithelp.ithome.com.tw/upload/images/20210924/20125854LGLgjan9dU.jpg

這樣是看起來比較美觀啦,只是今天的篇幅夠長了,我想還是留一點到明天再說吧,那我們明天一起在來完成它,幫它加上模糊的效果。

QRcode

那如果對於Vue3不夠熟的話呢?

Ps. 購買的時候請登入或註冊該平台的會員,然後再使用下面連結進入網站點擊「立即購課」,這樣才可以讓我獲得更多的課程分潤,還可以幫助我完成更多豐富的內容給各位。

我有開設了一堂專門針對Vue3從零開始教學的課程,如果你覺得不錯的話,可以購買我課程來學習
https://hiskio.com/bundles/9WwPNYRpz?s=tc

那如果對於JS基礎不熟的朋友,我也有開設JS的入門課程,可以參考這個課程
https://hiskio.com/bundles/b9Rovqy7z?s=tc

訂閱Mike的頻道享受精彩的教學與分享

Mike 的 Youtube 頻道
Mike的medium
MIke 的官方 line 帳號,好友搜尋 @mike_cheng


上一篇
[重構倒數第08天] - 圖片瘦身與靜態資源gzip優化
下一篇
[重構倒數第06天] - 前端除了要做預覽圖還要把圖片變模糊 !
系列文
[ 重構倒數30天,你的網站不Vue白不Vue ] 32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言