file

HTML DOM FileUpload 对象

Posted by huangqing on September 24, 2020

File API 接口总览:

FileList接口: 可以用来代表一组文件的JS对象,比如用户通过input[type="file"]元素选中的本地文件列表

Blob接口: 用来代表一段二进制数据,并且允许我们通过JS对其数据以字节为单位进行“切割”

File接口: 用来代表一个文件,是从Blob接口继承而来的,并在此基础上增加了诸如文件名、MIME类型之类的特性

FileUpload 对象

<input type = "file" id = "file" multiple = "multiple" accept = "image/*" />

<input type = "file" id = "file" multiple = "multiple" accept = "image/*" directory webkitdirectory />

FileUpload 对象的属性

  • accept 设置或返回指示文件传输的 MIME 类型的列表(逗号分隔)。
  • accessKey 设置或返回访问 FileUpload 对象的快捷键。
  • multiple 支持多选文件
  • directory(webkitdirectory) chrome浏览器支持选择文件夹
  • type 返回表单元素的类型。对于 FileUpload ,则是 “file” 。
  • alt 设置或返回不支持 <input type="file"> 时显示的替代文字。
  • defaultValue 设置或返回 FileUpload 对象的初始值。
  • disabled 设置或返回是否禁用 FileUpload 对象。
  • form 返回对包含 FileUpload 对象的表单的引用。
  • id 设置或返回 FileUpload 对象的 id。
  • name 设置或返回 FileUpload 对象的名称。
  • tabIndex 设置或返回定义 FileUpload 对象的 tab 键控制次序的索引号。
  • value 返回由用户输入设置的文本后,FileUpload 对象的文件名。

accept MIME

  • *.txt text/plain
  • *.css text/css
  • *.csv text/csv
  • *.htm text/html
  • *.html text/html
  • *.js text/javascript, application/javascript
  • *.xml text/xml, application/xml

  • *.dwg image/vnd.dwg
  • *.dxf image/vnd.dxf
  • *.gif image/gif
  • *.png image/png
  • *.jp2 image/jp2
  • *.jpe image/jpeg
  • *.jpeg image/jpeg
  • *.jpg image/jpeg
  • *.svf image/vnd.svf
  • *.tif image/tiff
  • *.tiff image/tiff

  • *.json application/json
  • *.pdf application/pdf
  • *.rtf application/rtf, text/rtf
  • *.dtd application/xml-dtd
  • *.xhtml application/xhtml+xml

  • *.doc application/msword
  • *.dot application/msword
  • *.asf application/vnd.ms-asf
  • *.mpp application/vnd.ms-project
  • *.pot application/vnd.ms-powerpoint
  • *.pps application/vnd.ms-powerpoint
  • *.ppt application/vnd.ms-powerpoint
  • *.wdb application/vnd.ms-works
  • *.wps application/vnd.ms-works
  • *.xlc application/vnd.ms-excel
  • *.xlm application/vnd.ms-excel
  • *.xls application/vnd.ms-excel
  • *.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
  • *.xlt application/vnd.ms-excel
  • *.xlw application/vnd.ms-excel
  • *.ogg application/ogg, audio/ogg

  • *.zip aplication/zip application/x-zip-compressed

  • *.mpeg video/mpeg
  • *.mpg video/mpeg

  • *.3gpp audio/3gpp, video/3gpp
  • *.ac3 audio/ac3
  • *.au audio/basic
  • *.mp2 audio/mpeg, video/mpeg
  • *.mp3 audio/mpeg
  • *.mp4 audio/mp4, video/mp4

FileUpload 对象的方法

  • blur() 从 FileUpload 对象上移开焦点。
  • focus() 为 FileUpload 对象赋予焦点。
  • select() 选取 FileUpload 对象。

FileUpload 对象的事件

onchange:

事件会在域的内容改变时发生。

file类型上会出现事件只触发一次问题,每次上传文件的时候,都会将当前的文件路径保存至$event.target.value中,当第二次选择文件时,由于两次$event.target.value相同,所以不会触发change事件。解决的方案是:每点击浏览激活onchange事件一次,就重新初始化一下这个控件

JS属性

var files = document.getElementById('file').files;

File:

  • name : 文件名称
  • type : 文件类型
  • size : 文件大小
  • lastModefiedDate : 文件的最后修改时间
  • webkitRelativePath : 选择文件夹模式下,文件相对选择文件夹的相对路径

文件拖拽

拖拽事件:

js能够监听到拖拽的事件有 dragstart 、dragdragend ; dragenterdragoverdragleavedragexit (没有浏览器实现)、 drop

其中,与拖拽文件相关的事件有dragenter(文件拖拽进)、dragover(文件拖拽在悬浮)、dragleave(文件拖拽离开)、drop(文件拖拽放下)。

阻止默认行为:

var dropZone = document.querySelector('#dropZone');

dropZone.addEventListener("dragenter", function (e) {
  e.preventDefault();
  e.stopPropagation();
}, false);
 
dropZone.addEventListener("dragover", function (e) {
  e.preventDefault();
  e.stopPropagation();
}, false);
 
dropZone.addEventListener("dragleave", function (e) {
  e.preventDefault();
  e.stopPropagation();
}, false);
 
dropZone.addEventListener("drop", function (e) {
  e.preventDefault();
  e.stopPropagation();
  // 处理拖拽文件的逻辑
}

注意:

绑定拖拽的容器存在子元素,会造成进入、离开子元素会触发dragenterdragleave事件,不是想要的结果。

<div class="drag-zone">
    <!-- 添加一个子元素 -->
    <div class="drag-zone--child"></div> 
</div>

增加css,保证drag-zone元素dragging-over的时候,子元素的pointer-event都被禁止

.dragging-over *{
  pointer-events: none;
}

拖放文件 – 拖放事件:

<!DOCTYPE html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
    <title>文件拖拽 (File Drag and Drop)</title>
    <style>
        #drop_zone {
            border: 2px dashed #BBB;
            padding: 25px 5px;
            text-align: center;
            font-size: 20pt;
            color: #BBB;
            border-radius: 5px;
        }

        #drop_zone.hovering {
            -webkit-box-shadow: inset 0px 0px 50px #BBB;
            -moz-box-shadow: inset 0px 0px 50px #BBB;
            -o-box-shadow: inset 0px 0px 50px #BBB;
            box-shadow: inset 0px 0px 50px #BBB;
        }

        #output_area {
            text-align: left;
        }

        #output_area li {
            margin: 10px 0;
        }
    </style>

</head>

<body>

    <div id="demo_area">
        <div class="example">
            <div id="drop_zone">将文件拖拽到这里</div>
            <div id="output_area"></div>
        </div>
    </div>

    <script>
        var output = document.getElementById('output_area');
        var dropZone = document.getElementById('drop_zone');

        function handleFileDragEnter(e) {
            e.stopPropagation();
            e.preventDefault();
            this.classList.add('hovering');
        }

        function handleFileDragLeave(e) {
            e.stopPropagation();
            e.preventDefault();
            this.classList.remove('hovering');
        }

        function handleFileDragOver(e) {
            e.stopPropagation();
            e.preventDefault();
            e.dataTransfer.dropEffect = 'copy';
        }

        function handleFileDrop(e) {
            e.stopPropagation();
            e.preventDefault();
            this.classList.remove('hovering');

            var files = e.dataTransfer.files;
            var outputStr = [];
            for (var i = 0, f; f = files[i]; i++) {
                var lastModified = f.lastModifiedDate;
                var lastModifiedStr = lastModified ? lastModified.toLocaleDateString() + ' ' + lastModified.toLocaleTimeString()
                    : 'n/a';
                outputStr += '<li><strong>' + f.name + '</strong> ('
                    + (f.type || 'n/a') + ')<br>大小:' + f.size
                    + '字节<br>修改时间:' + lastModifiedStr + '</li>';
            }
            output.innerHTML = '<ul>' + outputStr + '</ul>';
        }

        dropZone.addEventListener('dragenter', handleFileDragEnter, false);
        dropZone.addEventListener('dragleave', handleFileDragLeave, false);
        dropZone.addEventListener('dragover', handleFileDragOver, false);
        dropZone.addEventListener('drop', handleFileDrop, false);

    </script>
</body>

获得拖拽的文件、文件夹

drop这个事件的回调中的事件对象能够得到文件对象。

在事件对象中,一个e.dataTransfer这样的属性,它是一个DataTransfer类型的数据,有如下的属性:

属性 类型 说明
dropEffect String 用来hack某些兼容性问题
effectAllowed String 暂时不用
files FileList 拖拽的文件列表
items DataTransferItemList 拖拽的数据(有可能是字符串)
types Array 拖拽的数据类型 该属性在Safari下比较混乱

在Chrome中我们用items对象获得文件,其他浏览器用files获得文件.

  • 不能用 e.dataTransfer.files 的 File 对象,因为无法通过type判断一个 File 对象是文件还是文件夹
  • 必须用 e.dataTransfer.items 来判断文件或是文件夹。通过 kind 属性可以判断当前的 DataTransferItem 对象是文件还是字符串:item.kind === "file"
  • 使用 webkitGetAsEntry 方法:获取到一个 FileSystemFileEntry 对象或 FileSystemDirectoryEntry 对象。这两种都继承自 FileSystemEntry
  • 使用 FileSystemEntry 对象的 isFile 属性,判断是文件还是文件夹。
  • 文件的话,entry 的具体类型就是 FileSystemFileEntry,用 file 方法获得一个 File 对象:FileSystemFileEntry.file(successCallback[, errorCallback]);想要保留拖拽的层级结构的话,用fullPath 属性获取
  • 文件夹的话,entry 的具体类型就是 FileSystemDirectoryEntry ,可以使用 createReader 方法获得一个 FileSystemDirectoryReader 对象。readerreadEntries 方法,获取这个 entry 下的子级 entries

Chrome获取文件 – 禁止上传目录:

dropZone.addEventListener("drop", function (e) {
  e.preventDefault();
  e.stopPropagation();
  
  var df = e.dataTransfer;
  var dropFiles = []; // 存放拖拽的文件对象
  
  if(df.items !== undefined) {
    // Chrome有items属性,对Chrome的单独处理
    for(var i = 0; i < df.items.length; i++) {
      var item = df.items[i];
      // 用webkitGetAsEntry禁止上传目录
      if(item.kind === "file" && item.webkitGetAsEntry().isFile) {
        var file = item.getAsFile();
        dropFiles.push(file);
      }
    }
  }
}

Chrome获取文件 – DataTransferItem:

dropArea.addEventListener("drop", e => {
	let items = e.dataTransfer.items;
	for (let i = 0; i <= items.length - 1; i++) {
		let item = items[i];
		if (item.kind === "file") {
			// FileSystemFileEntry 或 FileSystemDirectoryEntry 对象
			let entry = item.webkitGetAsEntry();
			// 递归地获取entry下包含的所有File
			this.getFileFromEntryRecursively(entry);
		}
	}
	e.preventDefault();
});

getFileFromEntryRecursively(entry) {
      if (entry.isFile) {
        entry.file(
          file => {
            this.addFileToList({ file, path: entry.fullPath });
          },
          e => { console.log(e); }
        );
      } else {
        let reader = entry.createReader();
        reader.readEntries(
          entries => {
            entries.forEach(entry => this.getFileFromEntryRecursively(entry));
          },
          e => { console.log(e); }
        );
      }
}

整合:递归文件、文件夹

  1. 点击file选择文件
  2. 点击file选择文件夹
  3. 拖拽文件、文件夹

获取到 File 对象:

途径 得到 File 对象的过程
拖拽上传 drop 事件中获取 e.dataTransfer.items ,是一个 DataTransferItemList 对象,遍历得到 DataTransferItem 对象,用 webkitGetAsEntry 方法得到 FileSystemEntry 对象;根据 isFile 属性判断 entry 是文件还是文件夹。是文件的话,用 file 方法获取 File 对象;是文件夹的话,递归地用 reader 读取包含的文件
上传文件 change 事件中获取 input.files ,是一个 FileList 对象,遍历得到 File 对象
上传文件夹 同上

统一格式为 文件夹1/文件夹2/a.txt:

途径 相对路径 说明  
拖拽上传 entry.fullPath.substring(1) file.webkitRelativePath 都是空。所以从 entry 中取,注意要把 entry.fullPath 最前面的斜杠去掉
上传文件 file.name file.webkitRelativePath 都是空。直接用 file.name
上传文件夹 file.webkitRelativePath file.webkitRelativePath 就是想要的格式,直接用
<style>
    span {
        border: 1px solid;
    }
    .dropArea {
        border: 10px dashed;
        height: 150px;
    }
</style>
<span id="uploadFileButton">上传文件</span>
<span id="uploadDirectoryButton">上传文件夹</span>
<section class="dropArea" id="dropArea">把文件拽进来</section>
<script>
    new Superloader({
        uploadFileEl: document.getElementById("uploadFileButton"),
        uploadDirectoryEl: document.getElementById("uploadDirectoryButton"),
        dropAreaEl: document.getElementById("dropArea"),
        // “三合一”的成功回调,参数 file:文件实体,path:相对路径
        successCallback: ({ file, path }) => {
            console.log("文件名称:" + file.name + ",相对路径:" + path);
        },
    });
</script>
class SuperUploader{

    constructor({ uploadFileEl, uploadDirectoryEl, dropAreaEl, successCallback }) {
        uploadFileEl && this.initUploadFile(uploadFileEl, successCallback);
        uploadDirectoryEl && this.initUploadDirectory(uploadDirectoryEl, successCallback);
        dropAreaEl && this.initDragAndDrop(dropAreaEl, successCallback);
    }


    initUploadFile(el, successCallback) {
        const oRealButton = document.createElement("input");
        oRealButton.setAttribute("type", "file");
        oRealButton.setAttribute("multiple", true);
        oRealButton.style.display = "none";
        oRealButton.addEventListener("change", e => {
            let files = oRealButton.files;
            for (let i = 0; i <= files.length - 1; i++) {
                let file = files[i];
                // 这里的 file.webkitRelativePath 都是 "" ,不是我们想要的.要用 file.name
                successCallback({ file, path: file.name });
            }
        })
        document.body.appendChild(oRealButton);
        el.addEventListener("click", e => {
            oRealButton.click();
        });
    }

    initUploadDirectory(el, successCallback) {
        const oRealButton = document.createElement("input");
        oRealButton.setAttribute("type", "file");
        oRealButton.setAttribute("webkitdirectory", true);
        oRealButton.style.display = "none";
        oRealButton.addEventListener("change", e => {
            let files = oRealButton.files;
            for (let i = 0; i <= files.length - 1; i++) {
                let file = files[i];
                // 这里的 file.webkitRelativePath 就是我们想要的格式
                successCallback({ file, path: file.webkitRelativePath });
            }
        })
        document.body.appendChild(oRealButton);
        el.addEventListener("click", e => {
            oRealButton.click();
        });
    }

    initDragAndDrop(el, successCallback) {
        el.addEventListener("dragover", e => {
            e.preventDefault();
        });
        el.addEventListener("drop", e => {
            let items = e.dataTransfer.items;
            for (let i = 0; i <= items.length - 1; i++) {
                let item = items[i];
                if (item.kind === "file") {
                    let entry = item.webkitGetAsEntry();
                    this.getFileFromEntryRecursively(entry, successCallback);
                }
            }
            e.preventDefault();
        });
    }

    getFileFromEntryRecursively(entry, successCallback) {
        if (entry.isFile) {
            entry.file(file => {
                // 这里的 file.webkitRelativePath 都是 "" ,不是我们想要的.
                // entry.fullPath 是前面带斜杠的,要把斜杠去掉的
                let path = entry.fullPath.substring(1);
                successCallback({ file, path });
            }, e => { console.log(e); });
        } else {
            let reader = entry.createReader();
            reader.readEntries(entries => {
                entries.forEach(entry => this.getFileFromEntryRecursively(entry, successCallback));
            }, e => { console.log(e); });
        }
    }
}

文件上传

一、读取文件为blob并上传到服务器

HTML

<div class="container">
    <!--读取要上传的文件-->
    <input type="file" id="file"  />
    <input type="button" id="btn1" value="点击上传" onclick="uploadClick();" />
</div>

JS

//读取图片实例,并上传到服务器
var fileBox = document.getElementById('file');
fileBox.onchange = function () {
    //获取选择文件的数组
    var fileList = fileBox.files;
    for (var i = 0; i < fileList.length; i++) {
        var file = fileList[i];
        uploadFile(file);
    }
}
//关键代码上传到服务器
function uploadFile(file) {
    var reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = function () {
        var blob = new Blob([reader.result]);
        //提交到服务器
        var fd = new FormData();
        fd.append('file', blob);
        var ext = file.name.substring(file.name.lastIndexOf('.'));
        fd.append('extention', ext);
        fd.append('maxsize', 1024*1024*4);//Asp.net 默认一次上传最大4MB
        fd.append('isClip', -1);
        var xhr = new XMLHttpRequest();
        xhr.open('post', '../ashx/upload.ashx', true);
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4 && xhr.status == 200) {
                var data = eval('(' + xhr.responseText + ')');
                console.info(data);
                if (data.success) {
                    //上传成功
                    var imgName = data.msg;
                    alert(imgName);
                } else {
                    alert(data.msg);
                }
            }
        }
        //开始发送
        xhr.send(fd);
    }
}

后台C#处理关键代码:

//接收文件
HttpRequest req = _Context.Request;
string newname = _fileInfo.CreateNewName(newExtention);
//接收二级制数据并保存
Stream stream = _PostedFile.InputStream;
byte[] dataOne = new byte[stream.Length];
stream.Read(dataOne, 0, dataOne.Length);
FileStream fs = new FileStream(_fileInfo.TempFile + newname, FileMode.Create, FileAccess.Write);
try
{
    fs.Write(dataOne, 0, dataOne.Length);
}
finally
{
    fs.Close();
    stream.Close();
}
return newname;

二、读取图片文件,并上传到服务器

HTML

<div class="container">
    <!--图片类型验证方法1-->
    <input type="file" id="file" accept="image/*" />
    <input type="button" id="btn1" value="点击上传" onclick="uploadClick();" />
    <h4>选择文件如下:</h4>
    <img src="" id="img1" />
</div>

JS:


//读取图片内容 为DataURL
function readFile(file) {
    var reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function (e) {
        var result = reader.result;
        $('.container blockquote').text(result);
        $('#img1').attr('src', result)
    }
}

//读取图片实例,并上传到服务器
var fileBox = document.getElementById('file');
fileBox.onchange = function () {
    //获取选择文件的数组
    var fileList = fileBox.files;
    for (var i = 0; i < fileList.length; i++) {
        var file = fileList[i];
        //图片类型验证第二种方式
        if (/image\/\w+/.test(file.type))
            readFile(file);
        else
            console.log(file.name + ':不是图片');
    }
}


//关键代码上传到服务器
function uploadFile(file) {
    var reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = function () {
        var blob = new Blob([reader.result], { type: 'image/jpg' });
        //提交到服务器
        var fd = new FormData();
        fd.append('file', blob);
        var ext = file.name.substring(file.name.lastIndexOf('.'));
        fd.append('extention', ext);
        fd.append('isClip', -1);
        var xhr = new XMLHttpRequest();
        xhr.open('post', '../ashx/upload.ashx', true);
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4 && xhr.status == 200) {
                var data = eval('(' + xhr.responseText + ')');
                console.info(data);
                if (data.success) {
                    //上传成功
                    var imgName = data.msg;
                    alert(imgName);
                } else {
                    alert(data.msg);
                }
            }
        }
        //开始发送
        xhr.send(fd);
    }
}

//读取二进制图片文件,并上传到服务器
function uploadClick() {
    var fileList = fileBox.files;
    for (var i = 0; i < fileList.length; i++) {
        var file = fileList[i];
        //图片类型验证第二种方式
        if (/image\/\w+/.test(file.type))
            uploadFile(file);
        else
            console.log(file.name + ':不是图片');
    }
}