| 概要 |
過去にも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) ;
?>
| まとめ |
ブラウザーによる処理能力や挙動の違いを感じました。調整が及ばない場合は、ファイル容量の制限などの対応も必要かもしれません。もう少し注意しながら状況を確認してみようと思います。
また、画像サイズなども様々なので注意が必要です。