1
1

More than 3 years have passed since last update.

HTML&JavaScript - Drag&Dropでファイルを読み取るまでの解説とサンプル(バイナリビューワ)

Last updated at Posted at 2020-10-03

※HTML/JavaScript/CSSは仕事では使ってないので、変なところあるかもです。まずいところあればご指摘ください。

ファイルのDrag&Dropからデータの読み込みまでの流れ

1. HTMLの要素に対してイベント処理を登録する

HTML上の特定要素(ここでは<div id="dropZone">)に対して、下記のようにイベント処理を登録1する。

JavaScript

const dropZone = document.getElementById('dropZone');

dropZone.ondrop = function(evt) {
    /* ドロップされたときに実行する処理 */
}
dropZone.ondragover = function(evt) {
    /* ドラッグされたときのお決まりの処理。(いわゆる、おまじない)
       本文中の「ソースコード」章を参照ください。 */
}

Drag&Drop関連イベントの詳細

2. ドロップされたときにファイルのハンドル(?)を受け取る

ondropイベントで受け取れる引数(ここではevt)が、ファイルをハンドルするためのdataTransfer.filesをもっており、これを処理します。
filesという名前の通り、複数のファイルがドロップされる場合もあります。

JavaScript

dropZone.ondrop = function(evt) {
    /* 中略 */
    let files = evt.dataTransfer.files; /* ★Dropされたファイルたちのハンドル */
    /* 中略 */
    let f = files[0]; /* 1つ目のファイル */
    /* 中略 */
}

上記のfからは、ファイル名(name)やファイルサイズ(size)などの情報を得られます。

3. ファイルハンドルからファイルの中身を読み出す

ファイルの中身を読み出すには、FileReaderを使います。
ファイルハンドル(ここではf)に対してreadAsArrayBuffer(f)を実行すると、読み出しが始まります。
※このreadAsArrayBufferによる読み出し処理は非同期処理です。呼出し処理以下に記載された処理はそのままブロックされずに実行されます。

読み出しが完了するとonloadイベントが発生します。このイベントの引数(ここではevt)に含まれるtarget.resultが読み出し結果のデータにあたります。
ただし、このままだとArrayBufferという扱いづらい型になっているので、適宜byte配列などに変換したほうがよいでしょう。(下記コードではnew Uint8Arrayを使用して変換しています。)

JavaScript

const fileReader = new FileReader(); /* ★FileReaderの実体を1つ用意する */

dropZone.ondrop = function(evt) {
    /* 中略 */
    let files = evt.dataTransfer.files; /* 一旦変数に代入 */
    /* 中略 */
    let f = files[0]; /* 1つ目のファイル */
    /* 中略 */
    fileReader.readAsArrayBuffer(f); /* ★ファイルの読み込みを開始 */
    /* 中略 */
}

fileReader.onload = function(evt) {
    /* 読み込みが完了すると呼び出される */
    const content = new Uint8Array(evt.target.result); /* ★読み出し結果をbyte配列に変換 */
    /* 中略 */
}

fileReader.onprogress = function(evt) {
    /* 読み込み中に呼び出される */
}

あとはただのbyte配列データなので煮るなり焼くなりできるかと思います。

サンプル

See the Pen BinaryFileViewer(File Drag&Drop Sample) by kob58im (@kob58im) on CodePen.

※ハングアップ等回避のため、10KB以下のファイルしか受け取らないように処理を入れてあります。

ソースコード

JavaScript

const dropZone = document.getElementById('dropZone');
const progBar = document.getElementById('progBar');
const outFileInfo = document.getElementById('outFileInfo');
const outContent = document.getElementById('outContent');
const fileReader = new FileReader();
const maxFileSize = 10*1024; //bytes
const colN = 16;

dropZone.ondrop = function(evt)
{
    evt.stopPropagation();
    evt.preventDefault();

    let files = evt.dataTransfer.files; // FileList object.
    if ( files.length === 1 ) {
        let f = files[0];
        if ( f.size <= maxFileSize ) {
            outFileInfo.innerText = f.name+'\n'+f.size+'bytes';
            fileReader.readAsArrayBuffer(f);
        }
    }
}

dropZone.ondragover = function(evt)
{
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
}

fileReader.onload = function(evt)
{
    progBar.value = 100;

    const content = new Uint8Array(evt.target.result);
    generateTable(content);
}


fileReader.onprogress = function(evt)
{
    let per;
    if ( evt.total === 0 ) {
        per = 0;
    }
    else {
        per = (evt.loaded/evt.total) * 100;
    }
    progBar.value = per;
}
function generateTable(content)
{
    let output = [];
    const rowN = Math.ceil(content.length/16);

    output.push('<table><tr><th class="fixed01"></th>');
    for ( let col=0; col<colN; col++ ) {
        output.push('<th class="fixed02">+', col.toString(16), '</th>');
    }
    output.push('</tr>');
    for ( let row=0; row<rowN; row++ ) {
        output.push('<tr>');
        output.push('<th class="fixed02" align="right">',(row*colN).toString(16),':</th>');
        for ( let col=0; col<colN; col++ ) {
            let i = row * colN + col;
            if ( i < content.length ) {
                output.push('<td>', ('0'+content[i].toString(16)).slice(-2), '</td>');
            }
            else {
                output.push('<td></td>');
            }
        }
        output.push('</tr>');
    }
    output.push('</table>');
    outContent.innerHTML = output.join('');
}


function generatePreviewTable()
{
    const content = new Uint8Array(50);
    // make a random like sequence (don't use for technical usage)
    for(let i=0;i<content.length;i++){
        content[i] = (37*i+(i%17)+79)%256; // these value have no reason nor meaning.
    }
    generateTable(content);
}

generatePreviewTable();
HTML

<html>
  <body bgcolor="black" text="lightgray">
    <div id="dropZone">
      Drop a file.<br>(Note: this sample code checks file size to reject a file over 10240 bytes)<br>
        This sample code does not intend to transfer dropped file data to network. <br>But I strongly recommend you <u>should NOT drop file such as copyrighted nor confidential nor personal information etc... to browser</u> for security reason.<br>
      <progress id="progBar" max="100" value="0" style="width: 80px; "></progress>
      <output id="outFileInfo"></output><br>
      <output id="outContent"></output>
    </div>
  </body>
</html>
CSS

div {
  border: 2px solid #999999;
}
table {
  width: 100%
  font-size: 50%;
  font-family:monospace;
}
th,td{
  vertical-align: middle;
  padding: 0 7px;
  border: 1px solid #ccc;
}

/* to fix header column and header row for scrolling */
/* thanks to  https://since-inc.jp/blog/8675 */
.fixed01,
.fixed02{
  position: sticky;
  top: 0;
  left: 0;
  color: #66c;
  background: #333;
  &:before{
    content: "";
    position: absolute;
    top: -1px;
    left: -1px;
    width: 100%;
    height: 100%;
    border: 1px solid #ccc;
  }
}
.fixed01{
  z-index: 2;
}
.fixed02{
  z-index: 1;
}

参考サイト


  1. addEventListenerを使ってイベント処理を登録してもよい。 

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1