📷

Next.js SVGをPNGに変換してダウンロードする処理

2023/12/13に公開

以下の動画のような、ボタンをクリックしてSVGファイルをPNGなどの画像ファイルとして保存できるような機能を実装しました。

使用技術

  • Next.js(14.0.3)
  • React(18.0.0)
  • TypeScript(5.0.0)
  • Tailwind CSS(3.3.0)

大まかな手順

  1. SVG要素の参照を取得する
  2. SVGデータをシリアライズする
  3. Blobを作成してURLを生成する
  4. Canvasを作成し、SVGを描画する
  5. Canvasから画像データを取得する
  6. ダウンロードリンクを作成し、クリックイベントを設定する

作成方法

1. SVG要素の参照を取得する

  • ReactのuseRefフックを使用して、SVG要素への参照(svgRef)を作成します。これにより、後でSVG要素に直接アクセスできます。
  • コードの実装:
    const svgRef = useRef<SVGSVGElement>(null);
    
    <svg ref={svgRef} width="500" height="500">{svgPaths}</svg>
    
    ここではuseRefを使ってsvgRefを作成し、SVG要素にこの参照を割り当てています。
    consoleでsvgRefを確認すると、以下のようにSVG要素にアクセスすることができます。
    {current: svg}
      current: svg
      __reactFiber$a5gqq177ye8: FiberNode {tag: 5, key: null, elementType: 'svg', type: 'svg', stateNode: svg, …}
      __reactProps$a5gqq177ye8: {width: '500', height: '500', children: Array(6)}
    

2. SVGデータをシリアライズする

  • XMLSerializerを使用して、参照されたSVG要素の内容を文字列として抽出します。
  • コードの実装:
    const svgData = new XMLSerializer().serializeToString(svgRef.current);
    
    XMLSerializerは、DOMオブジェクトをXML形式の文字列に変換するためのAPIです。serializeToStringメソッドは、与えられたDOMオブジェクト(この場合はSVG要素)をXML文字列に変換します。

3. Blobを作成してURLを生成する

  • 取得したSVGデータをBlob形式に変換し、一時的なURLを生成します。
  • コードの実装:
    const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
    const svgUrl = URL.createObjectURL(svgBlob);
    
    ここではBlobオブジェクトを作成し、その後URL.createObjectURLを使用してBlobのURLを生成しています。
    後続のステップで、SVGデータをCanvasに描画したり、画像としてダウンロードしたりする際にこのURLを使用します。

4. Canvasを作成し、SVGを描画する

  • 新しいCanvas要素を作成し、ImageオブジェクトとしてSVGを描画します。

  • コードの実装:

    const canvas = document.createElement('canvas');
    canvas.width = svgRef.current.clientWidth;
    canvas.height = svgRef.current.clientHeight;
    const ctx = canvas.getContext('2d');
    const img = new Image();
    img.onload = () => {
      ctx?.drawImage(img, 0, 0);
      URL.revokeObjectURL(svgUrl);
    };
    img.src = svgUrl;
    

    document.createElement('canvas')を使って新しいCanvas要素を作成します。
    svgRef.current.clientWidthsvgRef.current.clientHeightを使用して、参照されているSVG要素の現在の幅と高さを取得し、Canvasのサイズとして設定します。
    canvas.getContext('2d')は、Canvasに2Dグラフィックスを描画するためのAPIを提供します。

    SVGデータを画像としてCanvasに描画するため、まずImageオブジェクトにSVGデータを読み込ませる必要があるので、まずnew Image()で新しいImageオブジェクトを作成します。
    そして、img.src = svgUrlでSVGデータのURLをImageオブジェクトのソースとして設定します。

    ImageオブジェクトがSVGデータを読み込み終えた後、それをCanvasに描画します。
    img.onload = () => {...}で、ImageオブジェクトがSVGデータを読み込み終えたときに実行される関数を定義します。
    この関数の中でctx.drawImage(img, 0, 0)を呼び出し、読み込まれた画像(SVG)をCanvasに描画します。
    img.src = svgUrlが実行されると、img.onloadがすぐに開始されるため、onload関数を先に記述しておくと安心です。

      img.onload = () => {
      ctx?.drawImage(img, 0, 0);
      URL.revokeObjectURL(svgUrl);
    };
    img.src = svgUrl;
    

    URL.revokeObjectURL(svgUrl);で一時的に生成されたSVGデータのURLをメモリから解放させます。

5. Canvasから画像データを取得する

  • CanvasのtoDataURLメソッドを使用して、PNG形式の画像データを取得します。
  • コードの実装:
    const imageUrl = canvas.toDataURL(`image/${format}`);
    
    この行はCanvas上の描画された画像をDataURL(Base64エンコードされた画像データ)として取得します。
    このDataURLを使用して、PNG形式の画像ファイルとして保存や表示ができます。
    {format}にはPNGやJPEGなどが動的に入ることを想定しています。

6. ダウンロードリンクを作成し、クリックイベントを設定する

  • 画像データのURLを使用して新しいリンク要素を作成し、プログラムでリンクを「クリック」してファイルをダウンロードします。
  • コードの実装:
    const link = document.createElement('a');
    link.href = imageUrl;
    link.download = `customized-icon.${format}`;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    
    ここでは、新しいリンク要素を作成し、hrefに画像データURLを設定し、ダウンロード属性にファイル名を設定しています。
    link.click()でプログラム側で自動的にリンクをクリックすることで、ダウンロードを開始します。
    ブラウザによっては、DOMに追加されていないリンク要素にlink.click()をしても、イベントが発火しない場合もあるようなので、document.body.appendChild(link)でリンク要素をDOMに追加し、クリック後に削除します。

Discussion