LoginSignup
1
1

More than 3 years have passed since last update.

画像選択時のプレビュー表示とExif対応のhook版

Posted at

問題

Javascriptで画像ファイルを選択させた後に、クライアントのみで画像プレビューを出すのは簡単だが、縦横が実際の画像とは違って表示される(orientation)問題がある。

exifのorientationを考慮しないサンプル

PreviewImageTest.tsx
// 選択した画像ファイルを表示するだけ
// これだと画像のorientationを見ていないので、回転のかかった画像では縦横が変に表示される

import React from "react"
interface Props {}

const PreviewImageTest: React.FC<Props> = props => {
  const [url, setUrl] = React.useState(null)

  return (
    <>
      <input
        type="file"
        accept="image/*"
        onChange={({ target: { validity, files } }) => {
          if (validity.valid) {
            // 単純にFileオブジェクトから表示可能なURLを作ってるだけ
            setUrl(URL.createObjectURL(files[0]))
          }
        }}
      />

      {url ? (
        <img
          src={url}
          style={{ width: "100%" }}
          onLoad={() => {
            // メモリ解放
            URL.revokeObjectURL(url)
          }}
        />
      ) : null}
    </>
  )
}
export default PreviewImageTest

解決方法

を使う。

useExifOrientation.tsx
import React from "react"
import loadImage from "blueimp-load-image"
// canvas.toBlobが対応で無い場合はこれが必要
// https://developer.mozilla.org/ja/docs/Web/API/HTMLCanvasElement/toBlob
// import "blueimp-canvas-to-blob" SSRでなければここに

interface Props {
  image: File | string
}
const useExifOrientation = ({ image }: Props) => {
  const [src, setSrc] = React.useState<null | string>(null)
  React.useEffect(() => {
    if (typeof image === "string") {
      // 単なるurl文字ならそのまま返す
      setSrc(image)
    }

    // SSRでは[window undefined]問題があるのでここで動的にロード
    // そうでなければファイル上部に通常通り
    // import "blueimp-canvas-to-blob"
    // で問題ない
    import("blueimp-canvas-to-blob").then(() => {
      // blueimp-load-imageの関数[loadImage]を使い、
      loadImage(
        image,
        canvas => {
          // このcallback内でcanvasが返ってくるので
          // toBlobを使ってblobをとり、そのblobからcreateObjectURLでurlを作る
          canvas.toBlob(
            blob => {
              const src = URL.createObjectURL(blob)
              setSrc(src)
            },
            // png画像の場合はblobにするのが遅いので、JPEG表示にする
            "image/jpeg",
            // JPEGの圧縮を利かせる 80%
            0.8
          )
        },
        // orientationを利かせる これだけで縦横がちゃんと表示される
        // orientationを設定すると、cropの指定が必要?みたい
        { orientation: true, crop: false }
      )
    })
  }, [image])

  return src
}
export default useExifOrientation

PreviewImageTest.tsx
import React from "react"
import useExifOrientation from "./useExifOrientation"

interface OrientedImgProps {
  image: File
}
const OrientedImg: React.FC<OrientedImgProps> = ({ image }) => {
  const src = useExifOrientation({ image })
  return (
    <img
      src={src}
      style={{ width: "100%" }}
      onLoad={() => {
        URL.revokeObjectURL(src)
      }}
    />
  )
}

interface Props {}
const PreviewImageTest: React.FC<Props> = props => {
  const [file, setFile] = React.useState<File | null>(null)

  return (
    <>
      <input
        type="file"
        accept="image/*"
        onChange={({ target: { validity, files } }) => {
          if (validity.valid) {
            setFile(files[0])
          }
        }}
      />

      <OrientedImg image={file} />
    </>
  )
}
export default PreviewImageTest

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1