iT邦幫忙

2021 iThome 鐵人賽

DAY 25
1
Modern Web

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

[重構倒數第06天] - 前端除了要做預覽圖還要把圖片變模糊 !

前言

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

上個章節中,我們已經可以透過 URL.createObjectURL() 來製作上傳預覽圖片,那如果我需要背景是模糊圖片的話應該要怎麼辦呢?

01

這時候就不能只靠 URL.createObjectURL() 來製作,或許你會說可以靠 CSS 的模糊效果來處理,但是今天你的預覽圖片是放在模糊背景的裡面,如果透過 CSS 模糊來處理會連裡面的物件也跟者模糊,而且效果不好且支援度也有限,所以最理想的結果是透過 html5 canvas 的方式來處理圖片模糊的效果,畢竟本來canvas 處理圖片本來就是它的主要功能之一。

我們來看一下原本的 useFileUpdate.js,我們需要一個 Composition API 來處理圖片模糊的部分,所以我新增了一個 usePhotoToBlur.js的檔案

import { usePhotoToBlur } from "../composition-api/usePhotoToBlur.js";

// 其他省略...

async function useQueuePreview(fileArr) {
  const { parsePhoto } = usePhotoToBlur();

  // 多圖多影片列表
  const previewMap = {};

  // 排序索引
  let idx = 0;

  for (const file of fileArr) {
    const blob = useImageFilePreview(file);
    const blur = await parsePhoto(file);
    previewMap[idx] = {
      id: idx,
      src: blob,
      blurred_url: blur
    };
    idx++;
  }

  return previewMap;
}

// 其他省略...

在這邊你會看到原本的 previewMap單純只有放預覽的 Blob 物件,但是現在要加上模糊的圖片,所以我改成了物件的方式包起來 blurred_url就是我們放模糊圖片的欄位,接下來就是我們的 usePhotoToBlur()裡面是如何轉換的。

usePhotoToBlur.js

export function usePhotoToBlur() {
  const parsePhoto = (imgUrl) => {
    return new Promise((resolve, reject) => {
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      const reader = new FileReader();
      try {
        reader.onload = (event) => {
          const img = new Image();
          img.onload = () => {
            canvas.width = img.width;
            canvas.height = img.height;
            ctx.drawImage(img, 0, 0);
            const base64 = gBlur(6, canvas, ctx);
            resolve(base64);
          };
          img.src = event.target.result;
        };
        reader.readAsDataURL(imgUrl);
      } catch (error) {
        reject(error);
      }
    });
  };

  return {
    parsePhoto
  };
}

在這邊我使用了 canvas 的方式來處理圖片的 render 還有圖片的模糊,最後把 canvas 的內容轉成 base64,這樣一來就可以在網頁上面看到模糊的圖片,你會看到這邊有一個 gBlur的函式,這就是我們把原本的圖片轉成模糊的處理含式。

Canvas 教學文件:https://developer.mozilla.org/zh-TW/docs/Web/API/Canvas_API/Tutorial

const base64 = gBlur(6, canvas, ctx);

裡面是一種叫做 高斯模糊 的演算法。

const gBlur = (blur = 8, h5canvas, context) => {
  const canvas = h5canvas;
  const ctx = context;
  let sum = 0;
  let delta = 5;
  let alpha_left = 1 / (2 * Math.PI * delta * delta);
  let step = blur < 3 ? 1 : 2;
  for (let y = -blur; y <= blur; y += step) {
    for (let x = -blur; x <= blur; x += step) {
      let weight =
        alpha_left * Math.exp(-(x * x + y * y) / (2 * delta * delta));
      sum += weight;
    }
  }
  for (let y = -blur; y <= blur; y += step) {
    for (let x = -blur; x <= blur; x += step) {
      ctx.globalAlpha =
        ((alpha_left * Math.exp(-(x * x + y * y) / (2 * delta * delta))) /
          sum) *
        blur;
      ctx.drawImage(canvas, x, y);
    }
  }
  ctx.globalAlpha = 1;
  const base64 = canvas.toDataURL();
  return base64;
};

最後再透過 canvas.toDataURL()把圖片轉成 base64 給丟出去,不過這個算法不是我寫的,是我很久以前上網找到的,但是現在我找不到來源,所以這邊沒法附上,先跟算法的原作者說聲抱歉。

這邊要特別注意,因為我們其中有針對圖片去做 onload,這個動作是非同步的,所以今天我用 Promise 的方式包起來,這樣一來我們就可以在外面使用 async/await的方式確定 onload 完成後再去做其他的事情。

// 其他省略...
async function useQueuePreview(fileArr) {
  const { parsePhoto } = usePhotoToBlur();
  const previewMap = {};
  let idx = 0;
  for (const file of fileArr) {
    const blob = useImageFilePreview(file);
      
    // 等待模糊處理完後,在執行後續的動作  
    const blur = await parsePhoto(file);
    
    previewMap[idx] = {
      id: idx,
      src: blob,
      blurred_url: blur
    };
    idx++;
  }
  return previewMap;
}

export function useFileUpdate() {
  const previewMap = ref({});
  const initData = () => {
    previewMap.value = {};
  };
    
  const setFile = async (file = []) => {
    initData();
  	// 這邊也是需要等到 useQueuePreview 都處理完後在把資料寫回 previewMap
    previewMap.value = await useQueuePreview(file);
  };

  return { setFile, previewMap };
}

然後今天資料格式被改變了,所以我們 html 上面也要調整一下。

  <div
    v-show="Object.values(previewMap).length !== 0"
    class="img_box"
    v-for="item in previewMap"
    :key="item.id"
    :style="{ 'background-image': `url(${item.blurred_url})` }"
  >
    <img :src="item.src" alt="" />
  </div>

02

這樣一來我們上傳上去的圖片就可以有模糊的背景圖了。

codesandbox 完成範例:https://codesandbox.io/s/vue3-upload-img-preview-2-qswg3?file=/src/App.vue:756-996

最後

這個方式固然很好,但是圖片一多的時候或是圖片真的很大的時候等待的時間比竟會比較久,尤其是當我們在手機上面做這件事情的時候更是明顯,所以很多時候我們為了節省時間還是會利用後端去做轉檔然後回傳或是可能只會用在只有一張圖片的情況下,反正依照實際的情況在做調整,選一個比較適合的方案來處理就好。那今天就先到這邊告一段落了,明天見。

QRcode

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

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

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

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

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

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


上一篇
[重構倒數第07天] - 不用靠後端的 client 端上傳圖片預覽圖
下一篇
[重構倒數第05天] - 要如何再 Vue2 使用 Composition API
系列文
[ 重構倒數30天,你的網站不Vue白不Vue ] 31

尚未有邦友留言

立即登入留言