# [Vue] 利用 Webpack 使用 webp 來加速圖片載入速度 ###### tags: `webp` `chainWebpack` `Vue-cli` #### webp 是 2010年 Google 釋出的圖片格式,針對 PNG 可減少 26%,JPEG 約可減少 25% ~ 34%,支援度的話目前僅 safari 尚不支援,但在 ios 14 以後開始支援。 #### 在此區分為三個階段,分別為建置 webp 圖片、將 html 上的圖片轉為 webp 格式、將 style 內的圖片轉為 webp 格式 ### 1.建置 webp 圖片 #### 這邊使用 [webpack-plugin-image-transform-webp-and-mini](https://www.npmjs.com/package/webpack-plugin-image-transform-webp-and-mini) 來協助將原有圖片轉換成 webp 格式,在此寫在 WebPack 的 chain 中,程式碼如下: ```javascript= config.plugin("webP").use(ImageminWebpWebpackPlugin, [ { name: "static/img/[name].[hash:8].[ext]", logger: false, paths: { //将此路径下的图片进行转换 dir: path.resolve(__dirname, "./src/assets"), exclude: [] }, miniOptions: false //是否針對小圖做轉換 } ]) ``` ### 2.將 html 上的圖片轉為 webp #### 這邊使用 VueLazyload 原生載入 webp 方式,將圖片路徑改為 webp,故在 html 的 img 使用上均需要用 v-lazy 的方式才可以使用 webp。 ```javascript= vue.use(VueLazyload, { filter: { webp(listener) { if (vue.prototype.$supportWebp && !~listener.src.indexOf(".webp")) { listener.src = listener.src.replace(/\.(png|jpe?g)(\?.*)?$/, ".webp") listener.el.setAttribute("data-src", listener.src.replace(/\.(png|jpe?g)(\?.*)?$/, ".webp")) } } } }) ``` #### 偵測裝置是否支援 webp,這邊使用 canvas 來判斷 ```javascript= export async function isSupportWebp() { return new Promise(resolve => { let result = false const elem = document.createElement("canvas") if (elem.getContext && elem.getContext("2d")) { result = elem.toDataURL("image/webp").indexOf("data:image/webp") === 0 } resolve(result) }) } ``` ### 3.將 style 檔案內的圖片轉為 webp 格式 #### 因在 vue 中,若找不到圖片會導致程式無法正常執行,在 style 檔案內只能放現有的圖片路徑,無法放副檔名為 webp 的路徑,而原本的規則只讀一般的圖片,不會將圖片轉換為 webp 格式,故將原有的 images rule 清除後再自行生成規則,所以針對 webpack 的 chain 再做下列調整: ```javascript= let rule = config.module.rule("images") rule.uses.clear() rule .use("./webploader.js") .loader("./webploader.js") .end() .use("url-loader") .loader("url-loader") .options({ limit: 4096, fallback: { loader: "file-loader", options: { name: "static/img/[name].[hash:8].[ext]" } } }) ``` #### webploader.js 檔案內最主要用來將副檔名改為 webp 格式,內容如下: ```javascript= const path = require("path") module.exports = function(source, map) { let result = source if (this.resourceQuery && this.resourceQuery.includes("type=webp") && !this.resource.includes("data:image")) { let extname = path.extname(this.resourcePath) result = source.replace(extname, ".webp") } // return result this.callback(null, result, map) } ``` #### 因 style 內的副檔名還是 png/jpeg,為了要在程式中做區隔,便在路徑後方加上 Query 來分辨,用來控制所要轉的圖片,並在程式進入點(main.js)判斷裝置支援 webp 之後寫上全域的 class,讓畫面轉讀取 webp 圖片。 #### main.js ```javascript= filter(Vue) ;(async () => { Vue.prototype.$supportWebp = await isSupportWebp() if (Vue.prototype.$supportWebp) { document.documentElement.classList.add("webp") } })() ``` #### less ```css= div{ background:url("圖片路徑") no-repeat; .webp & { background:url("圖片路徑?type=webp") no-repeat; } } ``` ### 補充:若有些畫面不想因為 lazyLoad 讀取圖片導致畫面閃爍,這邊擴充單純使用 webp 方式 #### 在 main.js 增加 directive ```javascript= Vue.directive("webp", { bind(el, binding) { el.src = Vue.prototype.$supportWebp ? binding.value.replace(/\.(png|jpe?g)(\?.*)?$/, ".webp") : binding.value }, update(el, binding) { el.src = Vue.prototype.$supportWebp ? binding.value.replace(/\.(png|jpe?g)(\?.*)?$/, ".webp") : binding.value } }) ``` #### 使用方式 (pug) ```pug= img(v-webp="路徑" alt="") ```