前提
本来はブラウザのVaryヘッダーを見て hogehoge.png
とリクエストが来ても
WebPに対応してる&WebP画像があれば hogehoge.png.webp
を返してくれますが
執筆現在、CloudflareはサーバーからのVaryヘッダーを無視してしまい
ブラウザのacceptによってwebpかpng|jpegかをハンドリングすることができません。
そのため、半ば無理やりフロントエンドでそれを実現しようと思います。
ちなみに使う技術はnuxt(webpack+vue+scss)です。
※ サーバー側でWebPを内部リダイレクトで返す処理は割愛させていただきます。
結論
画像のリクエストにWebP非対応のときだけクエリをつけることで
Cloudflare上で別リクエスト扱いさせ、WebPとそれ以外を別々にキャッシュさせます。
JS
const CAN_USE_WEBP = (() => {
const elem = document.createElement('canvas')
if (elem.getContext && elem.getContext('2d')) {
return elem.toDataURL('image/webp').indexOf('data:image/webp') === 0
}
return false
})()
if (!CAN_USE_WEBP) {
// webp非対応ならクラス付与
document.body.classList.add('no-webp')
}
Component
画像を表示するものはすべて共通のコンポーネントで表示するようにする。
あくまで下記は一例。ようするに通常のtypeとWebP用のtype二種類を用意してあげる。
<template>
<picture>
<source
v-for="(source, i) in sources"
:key="`image-def-${_uid}-${i}`"
:media="source.media || false"
:srcset="source.srcset"
type="image/webp"
/>
<source
v-for="(source, i) in sources"
:key="`image-webp-${_uid}-${i}`"
:media="source.media || false"
:srcset="source.srcset + '?nowebp'"
:type="defaultMIME"
/>
<img :src="defaultSrc + '?nowebp'" :alt="alt" :type="defaultMIME" loading="lazy" />
</picture>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'nuxt-property-decorator'
const MIME_REGX = /\.([a-z]+)$/
@Component
export default class extends Vue {
@Prop({ default: '' })
sources!: {
srcset: string
media?: string
}[]
@Prop({ default: '' })
alt!: string
get defaultSrc() {
return this.sources[this.sources.length - 1].srcset
}
get defaultMIME() {
const src = this.defaultSrc
const match = src && src.match(MIME_REGX)
switch (match && match[1]) {
case 'png':
return 'image/png'
case 'jpg':
case 'jpeg':
return 'image/jpeg'
case 'gif':
return 'image/gif'
default:
console.warn(`invalid extname [${src}]`)
return ''
}
}
}
</script>
SCSS
// mixin定義
@mixin webp-bgimage($url) {
background-image: url($url);
@at-root .no-webp & {
background-image: url($url+'?nowebp');
}
}
// 背景画像を指定するところで
.class {
@include webp-bgimage('hogehoge.jpg');
}
// そうすると下記のように2種類が指定される(.no-webpは上記JSで設定)
.class {
background-image: url('hogehoge.jpg');
}
.no-webp .class {
background-image: url('hogehoge.jpg?nowebp');
}
Nuxt(nuxt.config)
画像のパスに [query]
が付与されるようにする
export default {
build: {
filenames: {
img: ({ isDev }) => isDev ? '[path][name].[ext][query]' : 'images/[hash:7].[ext][query]'
}
}
}
以上です。
JSでbackground-imageなどを動的に実装するときは上で判定した CAN_USE_WEBP
を取得して
画像パスに同じように ?nowebp
をつけてあげるだけです。