FileAPIで画像を1枚ずつブラウザに表示する(表示中も他の操作を受け付ける)

仕事で延々とやってたわけなんですが、
FileAPIのreadAsDataURLとかって基本非同期なんですよね。
Image.onloadとかと同じようにやるわけなんですが、
画像が重いとブラウザに表示するときにすごいもっさりというか他の操作受け付けなくなるわけですよ。
まあスレッド1つなんで当たり前なんですけど。

でまあ画面表示も非同期にしようという話。
何用なのかと言うと、多数の画像ファイルをアップロードするときに、何が今アップロード中なのかサムネが出てたほうが文字情報よりわかりやすいよなあという用件です。
jQuery使います。
動作状況は↓な感じ
動作キャプチャ

[js title=”JavaScript”]
$(function(){
if(!(window.File && window.FileReader && window.FileList && window.Blob))
{
alert(‘このブラウザはFileAPIをサポートしていません’);
return;
}
var imgObj = {};
imgObj.imgs = [];
imgObj.total = 0;
imgObj.is_loading = false;
var thumbElem = ‘<div class="thumb"><div class="thumb-temp"></div><div class="thumb-loading"></div></div>’;
$(‘#files’).on(‘change’,function(e)
{
var files = e.target.files;
var file_no_1st = imgObj.total;
for(var i=0,f;f=files[i];i++)
{
if(!f.type.match(‘image.*’)){continue;}
$(thumbElem).attr(‘data-no’,imgObj.total).attr(‘data-order’,$(‘#image-area’).children().length+1).appendTo(‘#image-area’);
imgObj.imgs.push(f);
imgObj.total++;
}
if((imgObj.total>file_no_1st)&&(!imgObj.is_loading))
{
imgObj.is_loading = true;
setTimeout(function(){getFileImage(file_no_1st);},0);
}
});

function getFileImage(fileNo)
{
var r = new FileReader();
var f = imgObj.imgs[fileNo];
delete imgObj.imgs[fileNo];
r.onload = function(e)
{
$(‘#image-area > .thumb[data-no=’+fileNo+’] > .thumb-temp’).on(‘oTransitionEnd mozTransitionEnd webkitTransitionEnd transitionend’,function(){$(this).parent().find(‘.thumb-loading’).remove();});
$(‘#image-area > .thumb[data-no=’+fileNo+’] > .thumb-temp’).css({backgroundImage:’url(‘+e.target.result+’)’}).addClass(‘fadeInTemp’);
if(imgObj.total>fileNo+1){setTimeout(function(){getFileImage(fileNo+1);},0);}else{imgObj.is_loading = false;}
}
r.readAsDataURL(f);
}
});
[/js]

deleteはほとんど使わないと思いますが、配列の要素を削除する演算子ですが、undefinedになるだけで詰めてはくれません。
ここでは諸事情で要素を詰められると困るのでこうしてます。

[css title=”CSS”]
#image-area > .thumb
{
width:100px;
height:100px;
margin:10px;
display:inline-block;
border:1px solid #ccc;
position:relative;
}
.thumb > .thumb-loading
{
background-repeat:no-repeat;
background-position:center center;
background-image:url(data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPj4+Dg4OISEhAYGBiYmJtbW1qioqBYWFnZ2dmZmZuTk5JiYmMbGxkhISFZWVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFUCAgjmRpnqUwFGwhKoRgqq2YFMaRGjWA8AbZiIBbjQQ8AmmFUJEQhQGJhaKOrCksgEla+KIkYvC6SJKQOISoNSYdeIk1ayA8ExTyeR3F749CACH5BAkKAAAALAAAAAAQABAAAAVoICCKR9KMaCoaxeCoqEAkRX3AwMHWxQIIjJSAZWgUEgzBwCBAEQpMwIDwY1FHgwJCtOW2UDWYIDyqNVVkUbYr6CK+o2eUMKgWrqKhj0FrEM8jQQALPFA3MAc8CQSAMA5ZBjgqDQmHIyEAIfkECQoAAAAsAAAAABAAEAAABWAgII4j85Ao2hRIKgrEUBQJLaSHMe8zgQo6Q8sxS7RIhILhBkgumCTZsXkACBC+0cwF2GoLLoFXREDcDlkAojBICRaFLDCOQtQKjmsQSubtDFU/NXcDBHwkaw1cKQ8MiyEAIfkECQoAAAAsAAAAABAAEAAABVIgII5kaZ6AIJQCMRTFQKiDQx4GrBfGa4uCnAEhQuRgPwCBtwK+kCNFgjh6QlFYgGO7baJ2CxIioSDpwqNggWCGDVVGphly3BkOpXDrKfNm/4AhACH5BAkKAAAALAAAAAAQABAAAAVgICCOZGmeqEAMRTEQwskYbV0Yx7kYSIzQhtgoBxCKBDQCIOcoLBimRiFhSABYU5gIgW01pLUBYkRItAYAqrlhYiwKjiWAcDMWY8QjsCf4DewiBzQ2N1AmKlgvgCiMjSQhACH5BAkKAAAALAAAAAAQABAAAAVfICCOZGmeqEgUxUAIpkA0AMKyxkEiSZEIsJqhYAg+boUFSTAkiBiNHks3sg1ILAfBiS10gyqCg0UaFBCkwy3RYKiIYMAC+RAxiQgYsJdAjw5DN2gILzEEZgVcKYuMJiEAOwAAAAAAAAAAAA==);
width:100%;
height:100%;
position:absolute;
top:0;left:0;
}
.thumb > .thumb-temp
{
background-repeat:no-repeat;
background-size:contain;
width:100%;
height:100%;
position:absolute;
top:0;left:0;
-webkit-transition: all 0.5s;
-moz-transition: all 0.5s;
-ms-transition: all 0.5s;
-o-transition: all 0.5s;
transition: all 0.5s;
opacity: 0;
}
.thumb > .fadeInTemp
{
//opacity:0.5;
opacity:1;
}
[/css]

jQueryのanimateではなくCSSアニメーションにしてます。基本的にCSSアニメーションのほうが軽いです(動くかどうかは知りませんが)

[html title=”ソース”]
<input type="file" id="files" multiple>
<div id="image-area"></div>
[/html]

HTMLはこの程度です。

大量の画像(100枚くらい)突っ込んでみると、1枚ずつフェードインしてくると思います。
全体が表示しきる前にinputでファイルを追加する動作も可能かと。

まあそんな感じです。
ここからさらにファイルをサーバにアップロードしてやってとか、
何パーセントまで来たかとか、
その辺を付け足していくといいんじゃないかと思います。

動作確認はこちら
サーバには画像アップしてません(ソース見ればわかると思いますが)