2017/05/15

[JavaScript]外部URLから画像を読み込んで表示する(+クロスドメインを突破する)

アイコン登録機能があるWebサービスをつくっていた。
基本的な操作方法は、以下のとおり。

  1. アイコン画像を用意する
  2. 画像を選択する(<input type="file">を使う)
  3. 画像を読み込み、エンコードして表示する
  4. base64エンコードした文字列をDBに保存する

これでも必要十分な機能はあるのだが面倒くさい。
できれば画像のURLを指定したら、それを読み込んでほしい。

ということで、「URLから画像を登録」機能を実現するためにいろいろ考えてみた。

URLから画像を読み込む


<!-- index.html -->
<input type="url" id="url">
<button id="getImageFromURL">get</button>
<div>
  <figure id="output"></figure>
  <p id="base64"></p>
</div>
/* style.css */
#output {
  width: 200px;
  height: 200px;
  background-repeat: no-repeat;
  background-size: contain;
}
// script.js
const output = document.getElementById('output');
const base64 = document.getElementById('base64');

document.getElementById('getImageFromURL').addEventListener('click', () => {
  const url = document.getElementById('url').value;

  const image = new Image();
  image.src = url;
  
  // CORSが許可されていない場合はエラーになる
  // [Tainted canvases may not be exported.]のエラー対策
  image.crossOrigin = 'anonymous';

  image.onload = () => {
    const canvas = document.createElement('canvas');
    canvas.width = image.width;
    canvas.height = image.height;
    canvas.getContext('2d').drawImage(image, 0, 0);
    
    const src = canvas.toDataURL('image/png');
    output.style.backgroundImage = `url(${src})`;
    base64.innerText = src;
  };
  
  image.onerror = e => {
    console.log(e);
    base64.innerText = '読み込みに失敗しました';
  };
});
image.srcにURLを指定して画像を読み込む。
そしてimage.onloadでHTMLCanvasElementを使ってdata:URL(toDataURL)を取得している。
data URLは「data:[<mediatype>][;base64],<data>」で構成されており、data:image/png;base64,xxxxxxxxxのようになる。

また、別オリジンから読み込むためにimage.crossOriginでcrossorigin属性を指定している。image.crossOriginを指定しないと、「Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.」というエラーが発生する。
画像は取得できるのだが、Canvasが汚染(taint)されてしまうからだ。

そのため、crossOriginでCORSヘッダを指定し、異なるoriginから読み出された画像をあたかも同じoriginから読み出したものとしてCanvasで扱えるようにしている

crossOriginの指定についてはあくまで「CORSが許可されているサーバ」に限る。
つまりCORSが許可されていないサーバから画像を取得しようとすると、「Access to Image at '取得元サーバ' from origin '自サーバ' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '自サーバ' is therefore not allowed access.」という別のエラーが発生する。



クロスドメインを突破して画像を読み込む


結論からいうと、残念ながらフロントエンドだけではクロスドメインを突破することができない。そのためサーバサイドのプログラムが必要になる。

今回は、Node.jsで実装した。リクエストはSuperAgentというライブラリを使っている。
// server.js
const request = require('superagent');

// Parameter(URL)
const url = process.argv[2];

const btoa = str => {
  let buffer;
  if (Buffer.isBuffer(str)) {
    buffer = str;
  } else {
    buffer = new Buffer(str.toString(), 'base64');
  }

  return buffer.toString('base64');
};

request
  .get(url)
  .end((err, res) => {
    const base64 = btoa(res.body);

    // Response
    console.log(`data:image/png;base64,${base64}`);
  });
$ node app.js http://example.com/image.png

サーバサイドにはCORSについて特に考える必要がないので、引数に渡されたURLをgetして、base64エンコードしている。
その結果をフロントエンドに返してあげればよい。



参考サイト





以上

written by @bc_rikko

0 件のコメント :

コメントを投稿