質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.45%

  • JavaScript

    21080questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • HTML5

    5314questions

    HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

  • Chrome

    806questions

    Google Chromeは携帯、テレビ、デスクトップなどの様々なプラットフォームで利用できるウェブブラウザです。Googleが開発したもので、Blink (レンダリングエンジン) とアプリケーションフレームワークを使用しています。

  • canvas

    320questions

    HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

大量の画像を表示するとブラウザがクラッシュする

受付中

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 779

flc

score 1

ローカルHTML + JavaScriptで、自分用の画像ビューアのようなものを作っています(探してみても目的に合ったフリーソフトがないため)。

表示したいフォルダーは決まっており、フォルダー内の画像はすべて

  • ファイル名 … 「0000.jpg」~「9999.jpg」(実際は3000枚程度)
  • 大きさ … 1280 * 720px
  • 容量 … 200KB前後

となっています。フォルダーの総容量はおよそ1GB台、使用PCのRAMは4GBです。

これらを一度に表示しようとすると、メモリを使いすぎるせいかページ自体が表示されなかったり、ブラウザ(Chrome)がクラッシュしたりしてしまいます。

やってみたこと

  1. 画像を一旦canvasに小さく描く(1/2サイズ)
  2. その小さいcanvasをtoBlob()もしくはtoDataURL()で<img>タグに反映

としてみることで、若干ながら軽量化を図ってみました。

それでも、表示枚数を何千枚という値にしてみるとやはりクラッシュしてしまいます…

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

const WIDTH = 1280;
const HEIGHT = 720;

const divide = 2;

const w = WIDTH / divide; //1280 ÷ 2 = 640
const h = HEIGHT / divide; //720 ÷ 2 = 360

canvas.width = w;
canvas.height = h;

const max = 3000; //ひとまず3000枚を表示してみる

for( let i=0; i<max; i++ ){

    const serial = `000${i}`.slice(-4);
    const img = new Image();

    //ローカル画像を読み込むため、Chromeの起動オプションにあらかじめ--allow-file-access-from-filesをつけてあります
    img.crossOrigin = "Anonymous";
    img.src = `file:///C:/新しいフォルダー/${serial}.jpg`; //元のサイズは1920 * 1080px

    img.onload = () => {

        ctx.drawImage( img, 0, 0, this.width, this.height, 0, 0, w, h );

        canvas.toBlob( (blob) => {

            const newImg = document.createElement('img');
            newImg.crossOrigin = "Anonymous"; 
            const url = URL.createObjectURL( blob );

            //あるいは、toBlobを使わずに
            //const url = canvas.toDataURL('image/jpeg', 1);
            //画質を落とせば軽量化はされるものの、できれば綺麗に表示したい

            newImg.src = url;
            newImg.title = serial;
            document.body.appendChild( newImg );

        });

    }

}

他に考えついた方法

画面内に表示されていない(スクロール外の)画像はメモリから解放し(ただのプレースホルダーとしての空白にし)、スクロールされた時点で画像を表示する… という手法も考えられそうですが

スクロールされてから画像を読み込んでいては遅いですし、かといって適切なプリロードのタイミングというのもいまいちイメージできず…

何かよい方法はないでしょうか?

(もしかしたらそもそもHTML + JavaScriptで作ることを諦めたほうがよかったりするのかもしれませんが、知識不足でして…)

追記 再帰的?にしてみたもの

for文をやめて以下に書き換えてみると、toBlob()する前に生成していた元画像ぶんの余計なメモリ使用が抑えられたようです
(それでも3000枚は表示できませんが)

都度delete演算子で画像オブジェクトのプロパティを削除することで、ガベージコレクションがメモリを解放してくれるのを期待してこのような形ですが(そういうことを意識するのは初めてなので合っているかわかりません)

そのため、すぐにメモリが解放されるわけではなく時間差があるようで、Chromeに内属されたタスクマネージャで見ていると一旦メモリがある程度増加してから下がる… という挙動を見せています

なのでだんだんblobによるメモリ使用量が増えてくる後半、ヒヤヒヤしながら見守るのは変わらず…(うちのPCでは600MBほど使うとChromeが落ちる?ようです)

let tags = [];

const blobbing = (index) => {

    const serial = `000${index}`.slice(-4);
    let img = new Image(); //あとでdeleteしてメモリから解放するので、constではなくlet

    //ローカル画像を読み込むため、Chromeの起動オプションにあらかじめ--allow-file-access-from-filesをつけてあります
    img.crossOrigin = "Anonymous";
    img.src = `file:///C:/新しいフォルダー/${serial}.jpg`; //元のサイズは1920 * 1080px

    img.onload = () => {

        ctx.drawImage( img, 0, 0, this.width, this.height, 0, 0, w, h );
        delete img; //ここで元画像をメモリから解放(すぐに解放されるわけではなく時間差がある?)

        canvas.toBlob( (blob) => {

            const url = URL.createObjectURL( blob );
            tags[index] = `<img src="${url}" title="${serial}" style="width: ${w}px; height: ${h}px;">`;
            index++;

            if( index === max ) {

                $('.images').append( tags.join() );
            }
            else {

                blobbing( index ); //自分自身を呼び出してループ
            }

        });

    }


}

blobbing( 0 ); //ループ開始

あるいはdeleteを使わなくても、単にlet img = new Image(); で最初に作ったオブジェクトをそのままずっと使いまわしてもいいのかもしれません(そのほうがよかったりするのかはわかりませんが)

let tags = [];
let img = new Image();

const blobbing = (index) => {

    const serial = `000${index}`.slice(-4);

    //ローカル画像を読み込むため、Chromeの起動オプションにあらかじめ--allow-file-access-from-filesをつけてあります
    img.crossOrigin = "Anonymous";
    img.src = `file:///C:/新しいフォルダー/${serial}.jpg`; //元のサイズは1920 * 1080px

    img.onload = () => {

        ctx.drawImage( img, 0, 0, this.width, this.height, 0, 0, w, h );

        canvas.toBlob( (blob) => {

            const url = URL.createObjectURL( blob );
            tags[index] = `<img src="${url}" title="${serial}" style="width: ${w}px; height: ${h}px;">`;
            index++;

            if( index === max ) {

                $('.images').append( tags.join() );
            }
            else {

                blobbing( index ); //自分自身を呼び出してループ
            }

}

blobbing( 0 ); //ループ開始

2000枚近く読み込めるようにはなったものの、

net::ERR_INSUFFICIENT_RESOURCES 200 (OK)

というのが出ますね… やはりリソースがあと少し足りないようで

(ここまでしてBlobを使って画像を縮小する必要があるのか、そもそも複数の方からのご意見があったようにJavaScript以外の手段で縮小画像を作っておくべきなのかという話にもなってきますが)

引き続きご意見をお待ちしております。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • ockeghem

    2018/12/18 14:19

    1920*1080のフルカラー画像は伸長後で6MBくらいになります。3000枚というと、掛け算で18GBということになります。テストされたPCの搭載メモリはどれくらいでしょうか?

    キャンセル

  • flc

    2018/12/18 14:35

    写真ではないためか、1枚1枚の容量はそこまでにはなっていないようです。
    伸長といいますと… JPEGが圧縮画像であることと関係していますか?

    フォルダーの容量は総計およそ1GB台、PCのメモリは4GBになります。
    それと申し訳ありません、1920 * 1080というのは質問文を書く際の誤表記でした。正しくは1280 * 720です。

    キャンセル

  • flc

    2018/12/18 14:38

    ちょっと検証が必要ですが、もしかしたら単に拡張子をjpgとしただけのpngなのかもしれません…

    キャンセル

回答 3

+2

200KBといってもそれはJPEG圧縮されたファイルのサイズで、実際に画面に描画されるときには、1枚で約6MBの容量が必要になります。

予めサムネイル用の小さな画像を作っておくのが一般的な対応ですね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/18 14:44

    乱暴に言えばビットマップとしての容量ということでしょうか?
    並べた画像を詳細に見比べたいので、できればあまり小さくしたくはないのですが…
    (クリックした1枚だけを拡大とかではなく)

    キャンセル

  • 2018/12/18 14:53

    はい、画面に表示するためには、非圧縮の画像サイズ分の容量が必要になります。
    比較したいのであれば、比較する画像を選択して、別画面で選択された画像だけを表示するようにするとかですかね。

    キャンセル

  • 2018/12/18 15:09 編集

    選択して別画面とかそういう話になってくると、スクロールだけで見比べられるものと比べだいぶかけ離れてきてしまいますね…

    キャンセル

  • この投稿は削除されました

+2

imgタグのsrc越しに画像ファイルを落としてきて200kb、
そこからキャンバスに載せたり、blobで取得したりで複製という形で
単純に3000枚*200kb≒600MB…ではなく、600MB*n個の複製=数GB単位でメモリを消耗してしまっているのでしょう。

まずはタスクマネージャー(Windows)やアクティビティモニタ(Mac)等を開いて、
ブラウザがどれほどのメモリを消耗しているのか、自分のマシンのメモリの許容量はいくらかを探る必要があるでしょう。

また、3000回document.body.appendChild( newImg );を繰り返すと、
都度レンダリングのやり直し扱いとなり、ブラウザが再描画の為に大忙しになります。
予めタグを全部作っておいてinnerHTMLで一気に流し込んだ方が負荷が減るでしょう。

改善案としてはこんな感じです。

<body>
<div class="images"></div>
<script>
const images = Array(3000)
  .fill(0)
  .map((_, i) => `000${i}`.slice(-4))
  .map(serial => `<img src="file:///C:/新しいフォルダー/${serial}.jpg" alt="${serial}" style="width: 1920px; height: 1080px;">`)
  .join('');
document.querySelector('.images').innerHTML(images);
</script>
</body>

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/18 14:36

    ああ、確かにJPEG画像を描画するために展開すればそれだけ容量食いますよね。。。

    キャンセル

  • 2018/12/18 16:06 編集

    あっ…! いつもjQueryとかではそのようにしていたのに、経過を見たくて都度appendChildする形式のままになってました… それも負荷の一因となっていたかもしれませんね

    Chromeに内蔵されたタスクマネージャによると、どうも画像1500枚ほど・メモリ使用量600MB前後が限度で、それ以上はクラッシュするようです
    ん、ということは画像1枚につき400KBほど…? JPEGを展開してるにしてはあまりメモリ使用してない気もしますね…

    メモリ削減したくてやっていたBlobが逆にメモリ使用量の増加を招いていたのでしょうか
    解放すればいいとかそういう話でもないのでしょうかね…

    いや、きっとonloadのタイミングのせいもあるかもしれません
    0000.jpgに対するonloadの中身が完了したらそれ(letした変数img)をdeleteで解放し、つぎに0001.jpgの処理を…というふうに変えてみたら…ちゃんとメモリ使用量削減できてるっぽいです!(それでも3000枚までは行けずに途中で落ちますが)

    キャンセル

  • 2018/12/18 16:32

    delete演算子を使ったコードを追記いたしました

    キャンセル

+1

本質的な解決方法としてはサーバー側の処理でuploadされた際に
サムネイルをつくるしかないです
その手の処理が入れられないのであればページングなど1ページあたりの
容量を減らして下さい

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/18 14:48

    質問文にも書かれているとおり、サーバーには上げずローカルで使うHTMLになります。

    キャンセル

  • 2018/12/18 14:51

    それならなおさら、ローカルでサムネイルをつくって同時アップするだけの話でしょう
    またローカルであってもhttpdを導入して処理は可能です

    キャンセル

  • 2018/12/18 16:43

    アップとは…?

    キャンセル

  • 2018/12/18 16:46

    あくまでもローカルというならコピーと言い換えてもよいかもしれません

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.45%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • JavaScript

    21080questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • HTML5

    5314questions

    HTML5 (Hyper Text Markup Language、バージョン 5)は、マークアップ言語であるHTMLの第5版です。

  • Chrome

    806questions

    Google Chromeは携帯、テレビ、デスクトップなどの様々なプラットフォームで利用できるウェブブラウザです。Googleが開発したもので、Blink (レンダリングエンジン) とアプリケーションフレームワークを使用しています。

  • canvas

    320questions

    HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。