Angular JS

ブラウザ上でローカルの画像を読み込みプレビューを表示→画像を圧縮しサーバーへアップロードを実装してみる

2015年9月10日

ブラウザ上で画像を選択し選んだ画像をプレビュー、その後画像を圧縮してサーバへとアップロードしてみます。
画像を選択し、画像のプレビューを表示する部分は angularJSとhtml5の新機能canvasを組み合わせ、サーバーへのアップロードはphpで実装していきます。

デモ

まずは画像をブラウザ上で読み込み、angularJSにデータを渡す

index.html
<html ng-app="photoUpload">
<head>....</head>
<body ng-controller="photoUploadController">
<form>
  <input type='file' file-model="imageFile" multiple />
  <ul>
    <li ng-repeat="photo in photosPreview"><img ng-src="photo" /></li>
  </ul>
  <button ng-click="resize()">圧縮開始</button>
  <button ng-click="upload()">写真をアップロード</button>
</form>
</body>
</html>

※formの内容をあるパラメータで分岐させて表示をオン/オフさせようとしてng-ifを使用してしまうと、”imageFile”の変更を監視できなくなるようです。この場合は、この方法ではうまく実装できませんのでご注意を。

画像選択をカスタムディレクティブで監視

photoUpload.js
var photoUpload = angular.module('photoUpload', []);
photoUpload.directive('fileModel', ["$parse", function ($parse) {
  return {
    restrict : 'A',
    link     : function(scope, element, attrs) {
      var model = $parse(attrs.fileModel);
      element.bind("change", function () {
        scope.$apply(function () {
          model.assign(scope, element[0].files);
        });
      });
    }
  };
}]);

これでフォーム内のボタンをクリックしてファイルを選択した時に、ファイルを検知できるようになりました。

続いて、ファイルが写真をFileReaderというhtml5のAPIを使ってプレビュー表示をしていきます。

選択した画像をURLに変更し、プレビューを表示

photoUpload.js
photoUpload.controller('photoUploadController',function($scope){
  $scope.$watch('imageFile',function(imageFile){                    // (a)
    if(imageFile){
      $scope.photoPreview = [];
      for(i=0; i<imageFile.length; i++){
        if(!imageFile[i] || !imageFile[i].type.match("image.*")){                  // (b)
          console.log('このファイルは写真ではありません。 : ' + imageFile[i].name); 
          return;
        }

        var reader = new FileReader();                       // (c)
        reader.readAsDataURL(imageFile[i]);                  // (d)
        reader.onload = function(theFile){                   // (e)
          $scope.$apply(function(){
            $scope.photosPreview.push(theFile.target.result);
          });
        }
        
      }
    }
  });
});

(a) : ファイルが選択されることを監視

$scope.$watch()でimageFileの変更を監視します。つまり、ファイルが選択されたらこの中の関数が実行されます。

(b) : ファイルが画像か確認

(c) : FileReaderインスタンスを作成

html5の新機能FileReader()。これを使うことで画像データをjavascriptで扱えるようにします。

(d) : 読み込んだ画像をデータURL化し、html上で表示できる形式にする

(e) : 画像が読み込まれたときの挙動を指定

ここでは、読み込んだ画像のデータURLを$scope.photosPreviewという変数の中に配列で格納しています。
この$scope.photosPreviewの値をhtmlに渡して写真のプレビューをしています。

画像を圧縮

photoUpload.js
$scope.resize = function(){
  $scope.photosResized = [];
  var photoResized;
  for(i = 0; i < $scope.photosPreview.length; i++){
    var photo0 = new Image();
    var phot0URL = $scope.photosPreview[i];
    photo0.src = photo0URL;                             // (a)
    photo0.onload = function(){                         // (b)
      var w0 = photo0.width;
      var h1 = photo0.height;
      var w1 = 400;
      var h1 = Math.floor(h0 * w1 / w0);
      var canvasResize = document.createElement('canvas');  // (c)
      canvasResize.width = w1;
      canvasResize.height = h1;
      var context = canvasResize.getContext("2d");
      context.drawImage(photo0, 0, 0, w1, h1);
      var photo1URL = canvasResize.toDataURL("image/jpeg"); // (d)
      
      $scope.$apply(function(){
        $scope.photoResized.push(photo1URL);
      });
    }
  }
}

(a) : Imageインスタンスを作成

このImageインスタンスにオリジナルの画像のデータURLを渡しています。

(b) : リサイズ前の画像が準備できたらリサイズを開始する。

img.srcにURLを割り当てても、すぐには画像の準備が出来ません。これは、画像が非同期で読み込まれるからです。
画像の準備ができていないままリサイズを開始しても当然うまくリサイズされません。
画像の準備が整ってからリサイズを実行するには.onloadにリサイズ関数を割り当てればOKです。

(c) : html5の新API Canvasでキャンバスを新規作成

このキャンバスのサイズをw1, h1(画像のリサイズ後のサイズ)としています。
このキャンバスにオリジナルの画像データを渡して、リサイズを実行します。

(d) : キャンバスの画像データをURLに変換

これでリサイズされた画像データURLを取得し、html上で表示することができます。

画像をサーバーにアップロード

photoUpload.js
$scope.upload = function(){
  $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';			
  $http.post('php/uploadphoto.php',$scope.photosResized)
    .success(function(data, status, headers, config){
      console.log(data);
    }).error(function(data, status, headers, config){
      console.log('エラー発生');
    });
}
upload.php
<?php

mb_language("uni");
mb_regex_encoding('utf-8');
mb_internal_encoding("utf-8");
mb_http_input("auto");
mb_http_output("utf-8");

$postdata = file_get_contents("php://input");            // (a)
$postdata = json_decode($postdata);                      // (a)
$photo    = $postdata->photoResized;                     // (a)

if(file_exists('images/')){                              // (b)
}else{
  mkdir('images');                                       // (b)
}

foreach($photo as $p){
  $p 		= preg_replace("/data:[^,]+,/i","",$p);  // (c)
  $p 		= base64_decode($p);                     // (c)
  $p 		= imagecreatefromstring($p);             // (c)
  $pName 	= '/images/samplePhoto'.$i.'.jpg';
  if(imagejpeg($p,$pName)){                              // (d)
    echo "写真のアップロード完了 : " . $pName;
  }else{
    echo "写真のアップロード失敗 : " . $pName;
  }
}
?>

(a) : javascriptから渡された引数を抽出

(b) : 保存先のフォルダを作成

フォルダが存在しないかチェックし、存在していない場合はフォルダを作成しています。

(c) : 画像データURLを画像オブジェクトに変換

javascriptから渡された画像データはbase64形式になっています。
その形式から画像へとデコードしています。

画像のアップロード

imagejpeg()は画像のアップロードを実行し、成功したらtrueを返します。

参考出典

html クイックリファレンス
Hello Absurd World!
x89”
while

お問い合わせやコメント

ウェブサイト制作の依頼やこの投稿へのコメントなど、なんでもいいのでお問い合わせ待っています!ウェブデザインのチューターもしているのでそういったことへの依頼も受け付けています。

送信完了しました。 お問い合わせ確認用メールを上記のメールアドレス宛てに送信しましたのでご確認ください。 24時間以内に返信いたしますのでお待ち下さいませ。
お問い合わせありがとうございました!

送信完了しました。 24時間以内に返信いたしますのでお待ち下さいませ。
お問い合わせありがとうございました!

送信に失敗しました。お手数ですが入力内容をご確認の上、再度送信ボタンをクリックしてください。