【備忘】Pictureタグのだし分け

自前対応する場合の備忘


## 出し分けたい条件

* jpg,png,svgに対応する
* jpgとpngの場合はwebp対応する
* 画面幅によってスマホ用とパソコン用の画像を出しわける
* スマホ用の画像はファイル名には末尾に「_sp」をつける
* スマホ用の画像を指定しない場合も対応する
* Retina対応として、パソコンの場合は1倍と2倍、スマホは2倍のみとする
* 2倍書き出しの画像はファイル名には末尾に「_@2x」をつける


## 🐖pug

```

mixin Picture(fileName, format, alt, width, height, media=true)
  - const path = "./assets/images/"
  if format !== "svg"
    picture
      if media
        source(media="(max-width: 1024px)" srcset=`${path}${fileName}_sp.webp` type="image/webp")
        source(media="(max-width: 1024px)" srcset=`${path}${fileName}_sp.${format}` type="image/jpg")
        source(media="(min-width: 1025px)" srcset=`${path}${fileName}.webp ${path}${fileName}@2x.webp 2x` type="image/webp")
        img(src=`${path}${fileName}.${format}` alt=`${alt}` width=`${width}` height=`${height}`)
      else 
        source(srcset=`${path}${fileName}.webp ${path}${fileName}@2x.webp 2x` type="image/webp")
        img(src=`${path}${fileName}.${format}` alt=`${alt}` width=`${width}` height=`${height}`)
  else 
    picture
      img(src=`${path}${fileName}.${format}` alt=`${alt}` width=`${width}` height=`${height}`)


```
## Vue(v3)

```
<template>
  <ClientOnly>
    <picture class="client_only_picture" :class="className">
      <!-- retina出しわけ SVGなら２倍は設定しない -->
      <template v-if="retinaImg">
        <source
          :srcset="`${requireImg(file)} 1x, ${requireImg(retinaImg)} 2x`"
          :media="spFile ? 'min-width:769px' : ''"
        />
      </template>
      <template v-else-if="file">
        <source
          :srcset="requireImg(file)"
          :media="spFile ? 'min-width:769px' : ''"
        />
      </template>
      <!-- SP出しわけ -->
      <template v-if="spFile">
        <source :srcset="requireImg(spFile)" media="(max-width: 768px)" />
      </template>
      <img
        :src="file ? requireImg(file) : requireImg(spFile)"
        :alt="alt"
        :width="width"
        :height="height"
        :loading="loading"
        decoding="async"
      />
    </picture>
  </ClientOnly>
</template>

<script lang="ts" setup>
type Props = {
  file: string
  spFile?: string
  alt: string
  width?: string
  height?: string
  isRetina?: boolean
  loading?: "lazy" | "eager" | undefined
  className?: string
}
const props = withDefaults(defineProps<Props>(), {
  file: "",
  spFile: "",
  alt: "",
  width: "",
  height: "",
  isRetina: false,
  loading: undefined,
  className: "",
})

const retinaImg = computed(() => {
  const reg = /\.(png|jpg|webp|gif)/i
  return props.isRetina && !props.file.includes(".svg")
    ? props.file.replace(reg, "@2x.$1")
    : null
})

const requireImg = (file: string) => {
  const img = new URL(`../../assets/images/${file}`, import.meta.url).href
  return img
}
</script>

```

## Vue(v2)

```
TODO
```


## React
もっといい方法ありそう…


```
'use client';

/**
 * @des 画像のファイル名を取得
 * @param {String} name
 * @returns String
 */
const getImageFileName = (name) => {
  const lastDotIndex = name.lastIndexOf('.');
  if (lastDotIndex !== -1) {
    return name.substring(0, lastDotIndex);
  }
  return name;
};

const DefaultPicture = ({
  src = '',
  file = '',
  spFile = '',
  extension = '',
  alt = '',
  width = null,
  height = null,
  webpAlt = 'jpg',
  loading = '',
  isRetina = true,
  className = '',
}) => {
  const fileName = file ? getImageFileName(file) : '';
  const retinaImg = isRetina ? `${file}@2x` : null;

  return (
    <picture className={className}>
      {/* SP用：幅768px以下なら表示 */}
      {spFile && (
        <>
          <source
            srcSet={`/assets/images${src}/${spFile}.${extension}`}
            type={`image/${extension}`}
            media="(max-width: 768px)"
          />
          {/* WEBP:代替 */}
          {extension === 'webp' && (
            <source
              srcSet={`/assets/images${src}/${spFile}.${webpAlt}`}
              type={`image/${webpAlt}`}
              media="(max-width: 768px)"
            />
          )}
        </>
      )}

      {extension === 'svg' ? (
        <source
          srcSet={`/assets/images${src}/${file}.${extension}`}
          type="image/svg"
        />
      ) : retinaImg ? (
        <>
          <source
            srcSet={`/assets/images${src}/${file}.${extension} 1x, /assets/images${src}/${retinaImg}.${extension} 2x`}
            type={`image/${extension}`}
            media={spFile ? '(min-width:769px)' : null}
          />

          {/* WEBP:代替 */}
          {extension === 'webp' && (
            <source
              srcSet={`/assets/images${src}/${fileName}.${webpAlt} 1x, /assets/images${src}/${retinaImg}.${webpAlt} 2x`}
              type={`image/${webpAlt}`}
              media={spFile ? '(min-width:769px)' : null}
            />
          )}
        </>
      ) : (
        <>
          <source
            srcSet={`/assets/images${src}/${file}.${extension}`}
            type={`image/${extension}`}
            media={spFile ? '(min-width:769px)' : null}
          />
          {/* WEBP:代替 */}
          {extension === 'webp' && (
            <source
              srcSet={`/assets/images${src}/${fileName}.${webpAlt}`}
              type={`image/${webpAlt}`}
              media={spFile ? '(min-width:769px)' : null}
            />
          )}
        </>
      )}

      {/* デフォルト */}
      <img
        src={`/assets/images${src}/${file}.${extension}`}
        alt={alt}
        width={width}
        height={height}
        loading={loading}
      />
    </picture>
  );
};

const PictureImage = (props) => {
  return <DefaultPicture {...props} />;
};

export default PictureImage;
```