👻

URL.createObjectURL() の使い方と Next.js(React.js) での画像編集プレビュー

2025/01/26に公開

はじめに

URL.createObjectURL() は、ローカルの File オブジェクトや Blob オブジェクトに対する一時的な URL を生成し、ブラウザ上でプレビュー表示する際に便利な JavaScript メソッドです。

本記事では、URL.createObjectURL() の基本的な使い方と、Next.js を使って画像編集時にプレビューを表示する方法を紹介します。

URL.createObjectURL() の基本的な使い方

URL.createObjectURL() の概要

const objectURL = URL.createObjectURL(file);

仕組み

  • fileFile または Blob オブジェクト
  • objectURL → 一時的な URL(blob:http://localhost/... のような形式)

この objectURLimg タグの src に設定すると、ローカルの画像ファイルをサーバーにアップロードする前にプレビューとして表示できます。

画像アップロード時のプレビュー表示

import { useState } from "react";

export default function ImageUploader() {
  const [preview, setPreview] = useState<string | null>(null);

  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (file) {
      setPreview(URL.createObjectURL(file));
    }
  };

  return (
    <div>
      <input type="file" onChange={handleFileChange} />
      {preview && <img src={preview} alt="Preview" className="w-64 h-64 object-cover" />}
    </div>
  );
}

ポイント

  • input[type="file"] でユーザーが画像を選択
  • URL.createObjectURL(file) でローカルファイルの URL を生成
  • <img>src に設定し、アップロード前にプレビュー表示

Next.js での実践:画像編集時にプレビューを表示する

次に、Next.js の useEffect() を使って 編集画面で既存の画像を取得し、プレビューを表示する方法 を紹介します。

Next.js の EditImage.tsx(画像編集ページ)

'use client'
import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import Link from "next/link";

export default function EditImage() {
  const { id } = useParams<{ id: string }>();
  const router = useRouter();
  const imageId = id ? Number(id) : null; // ✅ Convert id to number
  const API_URL = imageId ? `http://localhost:3000/api/v1/images/${imageId}` : null;

  const [title, setTitle] = useState("");
  const [file, setFile] = useState<File | null>(null);
  const [fileUrl, setFileUrl] = useState<string | null>(null);

  useEffect(() => {
    if (!imageId) return; // ✅ Ensure id is defined

    const fetchData = async () => {
      try {
        const response = await fetch(API_URL as string);
        if (!response.ok) throw new Error("Image not found");
        const data = await response.json();
        setTitle(data.title);
        const fileResponse = await fetch(data.file_url);
        const blob = await fileResponse.blob();
        const file = new File([blob], data.title, { type: blob.type });
        setFile(file);
        const url = URL.createObjectURL(file);
        setFileUrl(url);
        console.log(file);
      } catch (error) {
        console.error("Error fetching image:", error);
        router.push("/"); // ✅ Redirect if not found
      }
    };
    fetchData();

    return () => {
      if (fileUrl) {
        URL.revokeObjectURL(fileUrl);
      }
    };
  }, [imageId]);

  useEffect(() => {
    if (fileUrl) {
      URL.revokeObjectURL(fileUrl);
    }
    if (file) {
      const url = URL.createObjectURL(file);
      setFileUrl(url);
    }
  }, [file]);

このコードではuseEffect内でデータベースから画像を
const fileResponse = await fetch(data.file_url);によってfetchしてurlをstring形式でfileResponseに格納します。blob形式に変換するためconst blob = await fileResponse.blob();を使用します。const blob = await fileResponse.blob();でblobのインスタンスを作成して、最終的にsetFile(file)useState内のfileが更新されます。
これによりデータベースの画像も同じプレビューとして見ることが可能になります。

URL.createObjectURL(file) を使用すると、ブラウザのメモリ上に オブジェクト URL(blob:)が生成 されます。
この URL は 手動で解放しない限り、メモリを消費し続けるため、適切に URL.revokeObjectURL() を使用する必要があります

プレビュー表示箇所

  const handleUpdate = async (e: React.FormEvent) => {
    e.preventDefault();

    const formData = new FormData();
    formData.append("image[title]", title);
    if (file) formData.append("image[file]", file);

    const response = await fetch(API_URL as string, {
      method: "PATCH",
      body: formData,
    });

    if (response.ok) {
      router.push("/images");
    } else {
      console.error("Error updating image");
    }
  };

  return (
    <div className="container mt-5">
      <h1>Edit Image</h1>
      <form onSubmit={handleUpdate}>
        {file && <img alt="" src={URL.createObjectURL(file)} className="w-25"></img>}
        <div className="mb-3">
          <label htmlFor="title" className="form-label">Title:</label>
          <input
            type="text"
            className="form-control"
            id="title"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
          />
        </div>
        <div className="mb-3">
          <label htmlFor="file" className="form-label">Upload New Image:</label>
          <input
            type="file"
            className="form-control"
            id="file"
            onChange={(e) => setFile(e.target.files?.[0] || null)}
          />
        </div>
        <button type="submit" className="btn btn-primary">Update</button>
      </form>
      <button className="btn mt-3">
        <Link href="/images">Back</Link>
      </button>
    </div>
  );
}

次にプレビューとして表示するやり方を説明します。

{file && <img alt="" src={URL.createObjectURL(file)} className="w-25"></img>}

URL.createObjectURL(file) File オブジェクトを blob: URL に変換し、imgsrc にセットできる
この方式のメリットとして非同期処理不要で、クライアント側で一時的なURLを作成するだけなので高速な点が挙げられます。
ただし、画像データ自体のサイズ変更はできないので、圧縮したい場合はcanvasなどを使う必要があります。
以下にその例を載せます。

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  const file = event.target.files?.[0];
  if (!file) return;

  const img = new Image();
  img.onload = () => {
    const canvas = document.createElement("canvas");
    canvas.width = 300;  // 任意のサイズ
    canvas.height = 300; // 任意のサイズ
    const ctx = canvas.getContext("2d");
    ctx?.drawImage(img, 0, 0, canvas.width, canvas.height);
    setPreview(canvas.toDataURL("image/png"));
  };
  img.src = URL.createObjectURL(file);
};


Discussion