概要 |
過去にもJavaScriptでローカルPC上の写真データをアップロードする方法について書いた記憶がありますが、今回の目標は、“連続的に”ということをクリアすることです。
複数のファイルを同時に選択し、CANVAS上に配置する時に縮小し、CANVAS上の画像データをサーバーにアップロードします。ファイル選択の一連の処理の自動化を試みます。
複数画像ファイルの選択 |
先ず、複数の画像ファイルを同時に取り込んでブラウザー上に表示する処理のテストプログラムです。とりあえず画像ファイルの読み込み評価を目的にしていますので、画像の縮尺比等は未考慮です。また、ファイルの読み込み結果(③)は、ファイル選択順(②)とは必ずしも一致しません。これは、ファイルサイズ等によって読込時間が異なり、読込完了順にブラウザーに表示する為です。今のところ必要性を感じていませんが、必要な場合は全ファイル読込後、順番にブラウザに表示するなどの対応が必要かもしれません。(→(A)複数画像ファイル取り込みプログラム)
(1)操作概要
①テストプログラム初期画面(ページを開いた状態)
②“ファイル選択” ダイアログボックスで対象ファイル選択
③選択ファイルをブラウザー上に自動表示
(上段:ファイル名 , 下段:画像表示)
(2)テストプログラム
今回の目標は“連続的に”処理することです。1ファイルでは過去にも確認していると思います。ということで、行番63の<input>タグには複数選択できるように、“multiple” を設定しています。
ファイル選択時、行番16~20の関数 fileReader() を呼び出します。行番17~19のループ処理内で一つづつ選択した File オブジェクトを関数 file_loader() に渡して、ファイル読込,ブラウザへの表示を行います。
最初は、行番17~19のループ内に関数 file_loader() の処理内容を組み込んでいたのですが、ファイル名・サイズ・種類等の関連情報取得とファイル読み込みが同期しないので、情報と画像が一致しない問題がありました。今回の様に分けることで改善されたことを確認できました。
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>FILE SELECT</title> <style> </style> <script type="text/javascript"> // 選択ファイル取得 function fileReader(files) { for (var i = 0; i < files.length; i++){ file_loader(files[i]); // 各ファイル処理 } } function file_loader(file){ var file_src = file ; // イメージ var file_nam = file.name ; // ファイル名 var file_typ = file.type ; // ファイル種類 var file_siz = file.size ; // ファイルサイズ var reader = new FileReader(); if (file_typ.match('image.*') ) { reader.onload = function(event) { var fileLi = document.createElement("li"); fileLi.innerHTML = file_nam ; var prpInf_object = document.getElementById("rlt_info") ; //parent_object.appendChild(fileLi); // 後 追加 prpInf_object.insertBefore(fileLi, prpInf_object.children[0]) ; // 前 追加 var tagImg = document.createElement('img') ; tagImg.src = event.target.result ; tagImg.style.width="200px" ; tagImg.style.height="200px" ; var parent_object = document.getElementById("img_data") ; //parent_object.appendChild(tagImg); // 後 追加 parent_object.insertBefore(tagImg, parent_object.children[0]) ; // 前 追加 } reader.readAsDataURL(file_src); }else { alert("対象外ファイルが選択されました。") ; } } </script> </head> <body> <input type="file" multiple onChange="fileReader(this.files)" accept="image/*"> <P>ここにファイル名が表示されます。</P> <ul id="rlt_info"></ul> <P>ここにファイル内容が表示されます。</P> <div id="img_data"></div> </body> </html>
画像ファイル アップロード処理 |
次に選択した複数の画像ファイルをサーバーに連続アップロードする処理について検討しました。
(1)操作概要
①複数画像ファイル連続登録処理
下の図はファイル選択ページですが、ファイル選択すると選択画像をページ内に追加表示するのと同時にサーバーにアップロードします。上段の6つの写真は<img>要素ですが、下段の写真はCANVAS要素に描画した図になっており、選択した画像をCANVASに縮小し描画した後、描画内容をサーバーに連続的にアップロードします。
②複数登録画像ファイル結果表示(並び替え可)
①の登録処理と次の結果処理のページにはそれぞれボタンを配置し、相互にページを呼び出せる様にしています。今回の例では①も登録処理の結果は、次の図の中の写真のNo.18~22に登録されています。既に前の投稿で紹介している通り、各写真は並び替え出来る様になっています。また、ファイル全削除ボタンをつけて、登録したファイルを全て消去できる様にしています。選択画像のみ削除する様な機能もいずれ検討したいと思いますが、とりあえず今後の課題としておきます。
元写真サイズは、1~6(Mbyte/枚)程度で、データ登録時はサイズ的に1/100程度になっていますが、CANVASへの描画時等の負荷も大きいと思われます。最初処理が中断してしまうことがありましたので、遅延処理等を入れることで、とりあえず安定しました。処理条件が変わったら再調整は必要かもしれません。また、IE11は更にサイズの大きな画像では安定的に処理出来ませんでした。Google Chromeは画像ファイルを扱う上で安定していると感じています。
(2)テストプログラム
スクリプトを参考までに載せておきます。
①複数画像ファイル連続登録処理
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>FILE SELECT</title> <style> canvas { float : left ; } </style> <script type="text/javascript"> var page_cnt = 0 ; var pg_cn_all ; // 選択ファイル取得 function fileReader(files) { page_cnt = 0 ; pg_cn_all = files.length ; for (var i = 0; files.length > i ; i++){ file_loader(files[i]); // 各ファイル処理 } } // 選択ファイル描画・アップロード処理 function file_loader(file){ var file_src = file ; // イメージ var file_nam = file.name ; // ファイル名 var file_typ = file.type ; // ファイル種類 var file_siz = file.size ; // ファイルサイズ var cvs_l = document.getElementById("CVS_LG") ; var ctx_l = cvs_l.getContext("2d") ; var cvs_s = document.getElementById("CVS_SM") ; var ctx_s = cvs_s.getContext("2d") ; var reader = new FileReader(); if (file_typ.match('image.*') ) { reader.onload = function(event) { var g_img = new Image() ; g_img.onload = function() { var result = save_canvas(file_nam); var mk_result="【NG】 " ; if(result=="OK"){ mk_result="【OK】 " ; } // 挿入写真サイズ取得 var img_o_w = g_img.width ; var img_o_h = g_img.height ; var cvs_l_w = 250 ; var cvs_l_h = Math.floor( img_o_h * cvs_l_w / img_o_w ) ; var cvs_s_w = 100 ; var cvs_s_h = Math.floor( img_o_h * cvs_s_w / img_o_w ) ; cvs_l.width = cvs_l_w ; cvs_l.height = cvs_l_h ; cvs_s.width = cvs_s_w ; cvs_s.height = cvs_s_h ; ctx_l.drawImage( g_img , 0 , 0 , cvs_l_w , cvs_l_h ) ; //ctx_l.strokeRect( 0 , 0 , cvs_l_w , cvs_l_h ) ; ctx_s.drawImage( g_img , 0 , 0 , cvs_s_w , cvs_s_h ) ; //ctx_s.strokeRect( 0 , 0 , cvs_s_w , cvs_s_h ) ; // ファイル名追加 var fileLi = document.createElement("li"); fileLi.innerHTML = mk_result + file_nam ; var prpInf_object = document.getElementById("rlt_info") ; //parent_object.appendChild(fileLi); // 後 追加 prpInf_object.insertBefore(fileLi, prpInf_object.children[0]) ; // 前 追加 // 画像ファイル追加 var tagImg = document.createElement('img') ; tagImg = g_img ; tagImg.style.width="200px" ; tagImg.style.height="200px" ; var parent_object = document.getElementById("img_data") ; //parent_object.appendChild(tagImg); // 後 追加 parent_object.insertBefore(tagImg, parent_object.children[0]) ; // 前 追加 page_cnt = page_cnt + 1 ; var div_page_no = document.getElementById("page_no") ; div_page_no.innerHTML = String(page_cnt) + " ファイル完了(全" + String(pg_cn_all) + "ファイル)" ; } g_img.src = event.target.result ; } reader.readAsDataURL(file_src); }else { alert("対象外ファイルが選択されました。") ; } } function save_canvas(fl_nm){ var pos_comma = fl_nm.lastIndexOf("."); var file_name = fl_nm.slice( 0 , pos_comma ) var imageType = "image/jpeg" ; var tgt_cvs = ["CVS_LG", "CVS_SM"]; var fil_nam = [ file_name + "" , file_name + "_SM" ]; var dat_ary = ["",""] ; // 大きい方のみ送付(下記にてループ数を1回にしている) for ( var i = 0; 1 > i ; i++ ) { var target_cvs = document.getElementById(tgt_cvs[i]) ; var base64 = target_cvs.toDataURL(imageType) ; var blob = Base64toBlob(base64) ; var formData = new FormData() ; formData.append( "hoge" , blob , fil_nam[i] + ".jpg" ) ; var rtn = "" ; var xmlhttp = createXMLHttpRequest() ; if( xmlhttp != null ){ xmlhttp.open( "POST" , "rcv_blob.php" , false ); //xmlhttp.setRequestHeader('Content-Type','multipart/form-data'); xmlhttp.send( formData ) ; rtn = xmlhttp.responseText ; }else{ rtn = "ERROR (HTTP REQ)" ; } dat_ary[i] = fil_nam[i] + " : " + rtn ; } return rtn ; //alert("◆アップロード結果◆\n\n"+dat_ary[0]+"\n"+dat_ary[1]+"\n"+fl_nm); } function Base64toBlob(base64){ var tmp = base64.split(',') ; var data = atob(tmp[1]) ; var mime = tmp[0].split(':')[1].split(';')[0] ; var buf = new Uint8Array(data.length); for (var i=0 ; data.length > i ; i++ ){ buf[i] = data.charCodeAt(i) ; } var blob = new Blob( [buf] , {type:mime} ) ; return blob ; } function createXMLHttpRequest(){ if(window.XMLHttpRequest){return new XMLHttpRequest()} if(window.ActiveXObject){ try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(e){} try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(e){} try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(e){} } return false; } </script> </head> <body> <input type="file" multiple onChange="fileReader(this.files)" accept="image/*" style="margin:0 10px;"> <input type="button" value="構成編集ページに移動" onclick="location.href='./tmbl_musdrg_001'" style="margin:0 10px;"> <div id="page_no">―</div> <P style = "clear:both;">ここにファイル名が表示されます。</P> <ul id="rlt_info"></ul> <P>ここにファイル内容が表示されます。</P> <div id="img_data"></div> <canvas id="CVS_LG"></canvas> <canvas id="CVS_SM"></canvas> </body> </html>
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>DRAG & DROP</title> <style> * { margin:0; } .draw_info th, .draw_info td { border:solid 1px #000000; text-align:center; vertical-align:middle; } .draw_info { border: solid 1px #000000; border-collapse:collapse; margin:5px 10px;} .div_dragbl { cursor:move; position:absolute; display: inline-block; background-color: #fffacd; background-position: center; background-repeat: no-repeat; margin: 5px; width: 200px; height: 200px; border: 1px solid #000000; background-size: cover; } </style> <script type="text/javascript"> var mv_fg=""; var mvs_x=0; var mvs_y=0; var tgt_id_no; var z_idx_ini=100; var X_STT_P=0; var Y_STT_P=100; var img_wdt=200; var img_hgt=150; var max_fnm=0; var row_num=0; var fct_num=0; var index_bef ; var index_cur ; var elm_order = [] ; var elm_chodr = [] ; // ◆初期設定◆ function init_chk(){ get_window_info() ; check_elements_order("0") ; } // ◆マウスダウン◆ window.onmousedown = function(e) { mv_fg = "true" ; tgt_id_no = event.target.id ; event.preventDefault(); // ◆重要◆ img要素移動時必須 event.stopPropagation(); if(tgt_id_no) { z_idx_ini = z_idx_ini + 1 ; document.getElementById(tgt_id_no).style.zIndex = z_idx_ini ; mvs_x = event.clientX - parseInt(document.getElementById(tgt_id_no).style.left.replace("px","")) ; mvs_y = event.clientY - parseInt(document.getElementById(tgt_id_no).style.top.replace("px","")) ; var cur_x = event.clientX - mvs_x ; var cur_y = event.clientY - mvs_y ; // 対象要素基準座標(左上) index_bef = get_index_num_by_pos( cur_x , cur_y ) ; } } // ◆マウスムーブ◆ window.onmousemove = function(e) { if(mv_fg == "true" && tgt_id_no) { var cur_x = event.clientX - mvs_x ; var cur_y = event.clientY - mvs_y ; // 対象要素基準座標(左上) index_cur = get_index_num_by_pos( cur_x , cur_y ) ; document.getElementById(tgt_id_no).style.left = cur_x + "px" ; document.getElementById(tgt_id_no).style.top = cur_y + "px" ; if(index_cur >= 0){ check_elements_order("1") ; } } } // ◆マウスアップ◆ window.onmouseup = function(e) { if(mv_fg == "true" && tgt_id_no) { mv_fg = "false" ; tgt_id_no = "" ; // 順番確定 if(index_cur >= 0){ elm_order = [] ; for(var i=0 ; elm_chodr.length > i ; i++) { elm_order[i] = elm_chodr[i] ; } } for(var i=0 ; elm_order.length > i ; i++) { var cur_lin = Math.floor( i / max_fnm ) ; var cur_row = i - cur_lin * max_fnm ; document.getElementById(elm_order[i]).style.left = X_STT_P + cur_row * img_wdt + "px" ; document.getElementById(elm_order[i]).style.top = Y_STT_P + cur_lin * img_hgt + "px" ; } } } // ◆ウィンドウリサイズ◆ window.onresize = function(e) { get_window_info() ; } // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ // 対象要素座標からインデックス番号を取得 function get_index_num_by_pos(cur_x,cur_y){ var c_c_x = cur_x + img_wdt /2 ; var c_c_y = cur_y + img_hgt / 2 ; // 対象要素中心位置 var tmp_x = c_c_x - X_STT_P ; var tmp_y = c_c_y - Y_STT_P ; // 配置基準位置からの座標 var x_num=-1 ; var y_num=-1 ; var idx_num=-1; if(tmp_x>0 && max_fnm*img_wdt>tmp_x && tmp_y>0 && (row_num+1)*img_hgt>tmp_y){ x_num = Math.ceil(tmp_x/img_wdt) ; y_num = Math.ceil(tmp_y/img_hgt) ; idx_num = max_fnm * (y_num-1) + x_num -1 ; } //if(idx_num>fct_num){ idx_num = fct_num ; } // (IDX番号制限時使用) return idx_num ; // 座標から計算したインデックス取得 } // 要素配置初期化 function get_window_info(){ var sW = window.innerWidth ; var sH = window.innerHeight ; // ウィンドウサイズ var elm_div = document.getElementById("draggable_div") ; // 移動領域要素取得 var rect = elm_div.getBoundingClientRect() ; // 移動領域取得 X_STT_P = Math.ceil(rect.left/10) + 20 ; // 移動領域座標(X) Y_STT_P = Math.ceil(rect.top/10) + 10 ; // 移動領域座標(Y) var drg_elm = document.getElementsByClassName("div_dragbl") ; // 移動対象要素取得 var fct_gap = 40 ; // Windowとのギャップ(両端合計) fct_num = drg_elm.length ; // 移動対象要素数取得 max_fnm = Math.floor((sW-fct_gap)/img_wdt) ; // 1行最大表示要素数 row_num = Math.ceil(fct_num / max_fnm) ; // 行数表示 for(var i=0 ; fct_num > i ; i++){ var cur_lin = Math.floor( i / max_fnm ) ; var cur_row = i - cur_lin * max_fnm ; drg_elm[i].style.top = Y_STT_P + cur_lin * img_hgt + "px" ; drg_elm[i].style.left = X_STT_P + cur_row * img_wdt + "px" ; drg_elm[i].style.width = img_wdt + "px" ; drg_elm[i].style.height = img_hgt + "px" ; drg_elm[i].style.zIndex = z_idx_ini ; } } // 要素配置確認 function check_elements_order(cmd_no) { if(cmd_no=="0"){ // 初回呼び出し時 var drg_elm = document.getElementsByClassName("div_dragbl") ; // 移動対象要素取得 elm_order = [] ; for(var i=0 ; drg_elm.length > i ; i++){ elm_order.push(drg_elm[i].id); } }else{ // 要素移動途中(配列順序の更新) ※ index_bef , index_cur に基づき更新 elm_chodr = [] ; for(var i=0 ; elm_order.length > i ; i++){ if(i==index_cur){ elm_chodr.push(tgt_id_no); } if(tgt_id_no!=elm_order[i]){ elm_chodr.push(elm_order[i]); } } if(index_cur>fct_num-1){ elm_chodr.push(tgt_id_no); } } } // サーバー側の画像ファイル全抹消 function delete_all(){ var xmlhttp=createXmlHttpRequest(); if(xmlhttp!=null){ xmlhttp.open("POST", "tmbl_musdrg_del.php", false); //xmlhttp.setRequestHeader('Content-Type','multipart/form-data'); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send('data=RQ_DEL_FILE_ALL'); var rtn=xmlhttp.responseText; alert("サーバー側画像ファイル抹消処理結果 : " + rtn ) ; }else{ alert("ERROR!(HTTP通信API設定失敗)") ; } location.reload() ; } // HTTP通信API設定 function createXmlHttpRequest(){ var xmlhttp=null; if(window.ActiveXObject){ try { xmlhttp=new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { try { xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e2){ } } }else if(window.XMLHttpRequest){ xmlhttp = new XMLHttpRequest(); } return xmlhttp; } </script> </head> <body onLoad="init_chk()"> <H2 style="margin:15px;">マウスドラッグで並び替えして下さい</H2> <input type="button" value="ページ再読込" onclick="location.reload()" style="margin:0 10px 0 25px ;"> <input type="button" value="画像ファイル登録ページへ移動" onclick="location.href='./tmbl_musdrg_rg_001'" style="margin:0 10px;"> <input type="button" value="画像ファイル全削除" onclick="delete_all()" style="margin:0 10px 0 25px ;"> <BR> <div id="draggable_div" style="position:relative ; clear:both;"> <?php //$serch_file = glob("../image_sample/*.jpg") ; $id_chked = 0 ; $serch_file = glob("../image_upload/img_dtn/*.jpg") ; for($ii=0; $ii<count($serch_file) ;$ii++){ $id_no = substr("000000".strval($id_chked+$ii+1),-6) ; $tmp_fl_nm = basename($serch_file[$ii]); echo '<span class="div_dragbl" id="'.$id_no.'" title="'.$tmp_fl_nm.'" style="background-image: url(\''.$serch_file[$ii].'\')">'.$id_no.'</span>'; } $id_chked = count($serch_file) ; $serch_file = glob("../image_upload/img_new/*.jpg") ; for($ii=0; $ii<count($serch_file) ;$ii++){ $id_no = substr("000000".strval($id_chked+$ii+1),-6) ; $tmp_fl_nm = basename($serch_file[$ii]); $dat_inf = date("ymdHis")."_".substr("0000000000".strval(Math.floor(10000000*microtime())),-7) ; $tmp_fl_pt = '../image_upload/img_dtn/'.$dat_inf.'.jpg' ; rename( $serch_file[$ii] , $tmp_fl_pt ) ; echo '<span class="div_dragbl" id="'.$id_no.'" title="'.$dat_inf.'" style="background-image: url(\''.$tmp_fl_pt.'\')">'.$id_no.'</span>'; } ?> </div> </body> </html>
③ファイル登録処理
<?php $result = move_uploaded_file($_FILES['hoge']['tmp_name'] , "../image_upload/img_new/".$_FILES['hoge']['name'] ) ; if($result===true){ echo "OK" ; }else{ echo "NG" ; } ?>
④ファイル全削除処理
<?php $data=htmlspecialchars($_POST['data']); if($data != "RQ_DEL_FILE_ALL"){ echo "ERR_CMD"; exit(); } $fil_cnt=0; $serch_file = glob("../image_upload/img_dtn/*") ; for($ii=0; $ii<count($serch_file) ;$ii++){ unlink($serch_file[$ii]); } $serch_file = glob("../image_upload/img_new/*") ; for($ii=0; $ii<count($serch_file) ;$ii++){ unlink($serch_file[$ii]); } $fil_cnt=count(glob("../image_upload/img_dtn/*"))+count(glob("../image_upload/img_new/*")); echo substr('000'.strval(intval($fil_cnt)),-3) ; ?>
まとめ |
ブラウザーによる処理能力や挙動の違いを感じました。調整が及ばない場合は、ファイル容量の制限などの対応も必要かもしれません。もう少し注意しながら状況を確認してみようと思います。
また、画像サイズなども様々なので注意が必要です。