HTML
JavaScript

画像の無断転載を防ぐ方法アイデアメモ

※20180131追記
誤字について編集リクエストが来ておりましたので修正いたしました。ご指摘ありがとうございます。

※20180210追記
コメントで教えていただいたのですが、FirefoxではShiftキーを押しながら右クリックすると、スクリプトによる制御を無視して強制的にデフォルトのコンテキストメニューを表示できるようです。ブラウザ上での表示サイズでは簡単にダウンロードできてしまいます。
そのため、Firefoxでは下記内容の効果を発揮できません。勉強不足でした、申し訳ございません。


公開していたイラストが別のWebサイトに勝手に転載されていたり、SNSのアイコンで使われていたり。
画像の無断利用に関して悩んだことのあるイラストレーターの方は少なくないと思います。

直接相談を受けたことはありませんがTwitterで無断利用に関するツイートをよく目にするので、私も何か助けになれることは無いかと思い、画像の無断転載や無断利用を防ぐ方法を考えました。
備忘録ですが、誰かの助けになれたら嬉しいので、Qiitaに記載いたします。

他にも方法があれば、コメントなどで教えていただけると嬉しいです。

※ライブラリではありません。ただのアイデアです

無断利用を制限する一文を記載する

まずはWebサイト中に「無断利用禁止」など、無断での利用を認めていないという旨の注意文を記載しておきます。閲覧者の良識に訴える作戦ですね。

スクリーンショット 2018-01-28 19.34.29.png

これだけでも無断利用しないようにする方が多いと思いますが、注意文に気づかない方、あえて無視する方も少なくないはずなので、効果は薄いかもしれません。

そもそも画像をダウンロードさせないようにする

無断利用禁止!と、いくら強く書いても聞く耳を持たない人はいますので、無断利用を防ぐには画像をダウンロードさせないことが一番有効です。

結論

調べていて思ったのですが、ダウンロードを100%防ぐことはできそうにありません……。

とはいっても可能な限り難しくすることは可能です。
昔からある方法も含め、ダウンロードの防止策を以下に記載します。

右クリックを禁止する

画像のダウンロードは、

  1. 右クリックでメニューを表示。
  2. 画像保存ボタンをクリック。

という手順で行われることが多いため、右クリックを無効にすることで、そもそも画像保存ボタンを表示させないようにする方法です。

ページ内のどこかをクリックすると、「右クリックは禁止しています」というダイアログボックスが出てくるWebサイトをよく目にします。ただ、あのような形式だと、OKボタンをクリックするまで操作ができなくなってしまうのでストレスを感じます。
右クリックしたときにアラートを表示するのではなく、コンテキストメニューが出ないようにするだけでも十分だと私は考えています。その方が閲覧者のストレスは少なくなるかもしれません。

以下のようなスクリプトで実現できます。

script.js
window.addEventListener('DOMContentLoaded', function(){
  var images = document.images
  var cancelContextMenu = function(e){
    e.preventDefault()
  };
  Array.prototype.forEach.call(images, function (elm) {
    elm.addEventListener('contextmenu', cancelContextMenu})
  })
})

※document.imagesには、ページ内に使われているimg要素が全て定義されています。

画像のドラッグを禁止する

ドラッグして別タブで画像を表示すると、そこでは右クリックできるので、ページ内で右クリックを制限しても実はあまり意味が無いのです。
加えて言うと、ドラッグさえ出来てしまえばfinderやExplorerに直接画像をコピーすることも可能です。
右クリックだけでなくドラッグも禁止しておけば、かなりの割合でダウンロードを防ぐことができると考えました。

右クリックを禁止するスクリプトを以下のように書き換えて実現できます。

script.js
window.addEventListener('DOMContentLoaded', function(){
  var images = document.images
  var cancelEvent = function(e){
    e.preventDefault()
  };
  Array.prototype.forEach.call(images, function (elm) {
    elm.addEventListener('contextmenu', cancelEvent})
    elm.addEventListener('dragstart', cancelEvent})
  })
})

img要素のsrc属性値をJavaScriptで動的に書き換える

右クリックとドラッグを禁止してしまえば、ダウンロード防止に非常に有効だと考えていますが、ソースコードを見られてしまうと画像のURLを見られてしまいます。
画像に直接アクセスされてしまうと、これまでの対策は全くの無意味です。
そのため、HTMLのソースコードだけでは画像のURLがわからないように、src属性値はJavaScriptを使ってセットすると無断転載防止に効果的かもしれません。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta charset="UTF-8">
  <title>画像をダウンロードさせないページサンプル</title>
</head>
<body>
  <img data-img-index="img1">
  <img data-img-index="img2">
  <img data-img-index="img3">
  <img data-img-index="img4">
  <img data-img-index="img5">
</body>
</html>
script.js
window.addEventListener('DOMContentLoaded', function(){
  var images = document.querySelectorAll('img[data-img-index]')
  var imagePath = {
    'img1': 'img/01.jpg',
    'img2': 'img/02.jpg',
    'img3': 'img/03.jpg',
    'img4': 'img/04.jpg',
    'img5': 'img/05.jpg',
  }
  var cancelEvent = function (e) {
    e.preventDefault()
  }
  Array.prototype.forEach.call(images, function (elm) {
    var attr = elm.getAttribute('data-img-index')
    elm.src = imagePath[attr] !== undefined ? imagePath[attr] : ''
    elm.addEventListener('contextmenu', cancelEvent, false)
    elm.addEventListener('dragstart', cancelEvent, false)
  })
})

例ではまず、ダウンロードさせたくない画像のimg要素からsrc属性を削除し、代わりにdata-img-index属性にユニークな値をセットしています。
script側には、新しくオブジェクトを作成して、data-img-index属性値をプロパティ名に、画像のURLを値に定義しています。

こうすることでhtml上では画像のURLがわからなくなるので、JavaScriptを知らない方が画像をダウンロードすることは困難になります。

また、右クリックとドラッグの禁止処理にはJavaScriptが動かない環境では動作しないという弱点があります。この方法を使うと、そういった環境ではそもそも画像が表示されないので、ユーザビリティは下がってしまいますが弱点をカバーすることも可能です。

画像の描画にcanvas要素を使う

ここまでの対策でも十分に防げると思います。ただ、主要なブラウザについている開発者ツールでは、JavaScriptで書き換えた後のsrc属性値も見えてしまいます。
img要素ではなくcanvas要素を使えば、属性値にURLを記載しなくても画像を表示できるので、画像のURLが見られないようにするために有効だと考えました。

htmlはそのままで、JavaScriptを次のように書き換えると、img要素を削除して、代わりにcanvas要素を挿入します。

script.js
window.addEventListener('DOMContentLoaded', function(){
  var images = document.querySelectorAll('img[data-img-index]')
  var imagePath = {
    'img1': 'img/01.jpg',
    'img2': 'img/04.jpg',
    'img3': 'img/02.jpg',
    'img4': 'img/03.jpg',
    'img5': 'img/05.jpg',
  }
  var cancelEvent = function (e) {
    e.preventDefault()
  }
  Array.prototype.forEach.call(images, function (elm) {
    const attr = elm.getAttribute('data-img-index')
    elm.src = imagePath[attr]
    elm.onload = function (e) {
      const canvas = document.createElement('canvas')
      const imageStyle = document.defaultView.getComputedStyle(elm, '')
      const ctx = canvas.getContext('2d')
      const imageWidth = imageStyle.width.replace(/px$/i, '')
      const imageHeight = imageStyle.height.replace(/px$/i, '')
      canvas.width = imageWidth
      canvas.height = imageHeight
      ctx.drawImage(elm, 0, 0, imageWidth, imageHeight)
      ctx.scale(0.5, 0.5)
      elm.parentElement.insertBefore(canvas, elm)
      elm.parentElement.removeChild(elm)
      canvas.addEventListener('contextmenu', cancelEvent)
      canvas.addEventListener('dragstart', cancelEvent)
    }
  })
})

※過度に縮小した画像をcanvas要素で表示すると、ジャギーが目立ってしまうようなのでご注意ください。

画像のURLにアクセスするとbase64文字列を返すようにする。

これまでの方法、実はjsファイルを見られてしまうと意味がなくなってしまいます。そのため画像のURLがわかっても簡単に保存できないように、もう少し踏み込んで対策を考えてみました。

サーバサイドのスクリプトを使って画像をbase64の文字列に変換し、それをJavaScriptで取得するような仕組みにすれば、もっとダウンロードが難しくなるのではないでしょうか。

例えば画像のURLへのアクセスに対して、以下のようなPHPスクリプトを経由するようにすれば、base64に変換して返すようになります。

※20180131追記
はてなブックマークのコメントで教えていただきましたが、セキュリティの懸念があるとのことでした。次のスクリプトをそのまま使用するのはお控えくださいますようお願いいたします。

可能性を探るのは面白いけど、画像に透かしを入れる、とかの方が健全だと思う。/ routing.phpはWindows環境で動かした場合 ..%5c..%5c みたいな入力でディレクトリトラバーサル出来ちゃったりしないのかな。つbasename(),pathinfo()

ファイル名だけを取り出せば問題ないと考えたのですが、甘かったです……。申し訳ございません。

routing.php
<?php
  //アクセスされたURLを取得
  $filepath = $_SERVER['REQUEST_URI'];

  //URLからファイル名を抜き出す
  $filepathArr = explode('/', $filepath);
  $filename = array_pop($filepathArr);

  //ファイル名がjpge,jpg,png,gifで終わっているものに対して変換処理
  if ( preg_match( "/\.(jpe?g|png|gif)$/", $filename )){
    try {
      if ($file = file_get_contents((dirname(__FILE__) . '/' . $filename ))){
        $img = base64_encode($file);
        echo $img;
      } else {
        throw new Exception();
      }
    } catch (Exception $e){
      return false;
    }
  } else {
    return false;
  }
?>

この方法なら画像のURLに直接アクセスされても、文字列しか表示されないのでbase64のことを知らない方が画像をダウンロードしづらくなります。

例)http://label841.com/sample1/img/01.jpg

htaccessなどを使って、画像のURLにアクセスすると、上記のPHPスクリプトを経由させるようにすることで想定の動作になります。併せてこれまでのJavaScriptを下記のように変更すると、base64文字列を受け取ったあと、canvas要素で画像を描画するようになります。
※base64のデータを非同期で取得するために、axiosというライブラリを使っています。

script.js
window.addEventListener('DOMContentLoaded', function () {
  var images = document.querySelectorAll('[data-img-index]')
  var imagePath = {
    'img1': './img/01.jpg',
    'img2': './img/04.jpg',
    'img3': './img/02.jpg',
    'img4': './img/03.jpg',
    'img5': './img/05.jpg',
  }
  var base64Type = {
    'jpg': 'data:image/jpeg;base64,',
    'jpeg': 'data:image/jpeg;base64,',
    'png': 'data:image/png;base64,',
    'gif': 'data:image/gif;base64,'
  }
  var cancelEvent = function (e) { e.preventDefault() }
  Array.prototype.forEach.call(images, function (elm) {
    var attr = elm.getAttribute('data-img-index')
    axios.get(imagePath[attr])
    .then( function (e) {
      var ext = e.config.url.match(/\/.*\.(\w+)$/)[1]

      elm.style.visibility = 'hidden';
      elm.src = (base64Type[ext] + e.data)

      elm.onload = function (e) {
        var canvas = document.createElement('canvas')
        var imageStyle = document.defaultView.getComputedStyle(elm, '')
        var ctx = canvas.getContext('2d')
        var imageWidth = imageStyle.width.replace(/px$/i, '')
        var imageHeight = imageStyle.height.replace(/px$/i, '')
        canvas.width = imageWidth
        canvas.height = imageHeight
        ctx.drawImage(elm, 0, 0, imageWidth, imageHeight)
        ctx.scale(0.5, 0.5)
        elm.parentElement.insertBefore(canvas, elm)
        elm.parentElement.removeChild(elm)
        canvas.addEventListener('contextmenu', cancelEvent)
        canvas.addEventListener('dragstart', cancelEvent)
      }
    })
  })

スクリーンショットの防止について

画像をダウンロードしづらくしてもスクリーンショットは通常通り撮れますので、こちらもどうにかして防ぎたいです。

Window Media Playerでは、保護されている動画のキャプチャを取ろうとすると、真っ黒なものしか撮れないらしいので参考に処理を考えてみました。
以下の場合に画面が真っ黒になれば、MacとPCのどちらもほとんどの場合にスクリーンショットを撮りづらくなります。

  • Command + Shiftが押されたとき
    →Macでスクリーンショットを撮るとき、Command + Shift + 3(or 4)を押すため。
  • print screenキーが押されたとき
    →PCでスクリーンショットを撮るときprint screenキーを押すため。
  • ブラウザが非アクティブになったとき
    →スクリーンショットを撮るアプリケーションを起動したら、ブラウザが非アクティブになるため。

これらの要件をクリアするために、CSSとスクリプトを追加しました。

style.css
body.ov::after{
  content: "";
  position: fixed;
  width: 100%;
  height: 100%;
  background: #000;
  top: 0;
  left: 0;
  z-index: 100;
}

上記のCSSを追加すると、bodyに.ovがついているとき、after擬似要素を追加し、画面全体を真っ黒にします。

script.js
  var addOverlay = function () {
    if (!document.body.classList.contains('ov')) {
      document.body.classList.add('ov')
    }
  }
  var removeOverlay = function () {
    if (document.body.classList.contains('ov')) {
      setTimeout(function () {
        document.body.classList.remove('ov')
      }, 300)
    }
  }
  var overlay = function () {
      addOverlay()
      removeOverlay()
  }
  var keyevent = function (e) {
    if (e.keyCode === 44) {
      overlay()
    }
    if (e.metaKey && e.shiftKey){
      addOverlay()
    } else if ( !e.metaKey || !e.shiftKey ) {
      removeOverlay()
    }
  }
  window.addEventListener('keydown', keyevent)
  window.addEventListener('keyup', keyevent)
  window.addEventListener('blur', addOverlay)
  window.addEventListener('focus', removeOverlay)

上記のJavaScriptを追加すると、以下のような動作を行います。

  • キーを押したとき、Shiftキー + metaキー(Commandキー)が同時に押されていたらbodyに.ovを追加。
  • PrtScーを押したときにbodyに.ovを追加、0.3秒後に、bodyの.ovを削除
  • キーを離したときShiftキー、metaキーのどちらかが押されていなかったらbodyの.ovを削除
  • windowが非アクティブのとき、bodyに.ovを追加
  • windowがアクティブのとき、bodyの.ovを削除

スマートフォンは解決策を見つけられませんでした……。

今後の課題とまとめ

最終的に次のリンク先の形になりました。

画像をダウンロードさせないページのデモ

canvasで画像を縮小表示するとどうしても荒くなってしまうので、スマートフォンなどでは気になってしまいます。
また、ウィンドウをリサイズしたあとも画像サイズがそのままになってしまっているので、その点を改修すればもう少し使い勝手がよくなるかもしれません。
それらの課題を解決して、一つのライブラリにまとめ、簡単に導入できるようにしたいと考えています。

TwitterやPixivに掲載しているイラストにはこの方法は使えませんが、イラストレーターの方がご自身で運営しているWebサイトには適用できると思います!


今回のファイルは次のリンクからダウンロード可能です。ライブラリとしては使えませんが、アイデアとしてお使いいただければ幸いでございます。

今回使ったファイル

(distディレクトリの中に入っているファイルがアップロードされています。設定ファイルの雑さはお目こぼしをお願いします。。)