文件(File)实现技术

重要通知

类目简介

基本概况

上传文件

<button type="button">上传文件</button>


<script type="text/javascript">
  const button = document.querySelector("button");
  button.addEventListener("click", uploadAction, false);

  async function uploadAction() {
    const fileEl = document.createElement("input");
    if (!fileEl) return;

    fileEl.setAttribute("type", "file");
    fileEl.setAttribute("name", "input-file");
    fileEl.setAttribute("multiple", "multiple");
    fileEl.setAttribute("accept", "image/gif,image/jpeg,image/png");
    fileEl.setAttribute("accept", ".doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document");

    // accept不传,默认所有文件

    // 任何音频文件
    fileEl.setAttribute("accept", "audio/*");

    // 任何视频文件
    fileEl.setAttribute("accept", "video/*");

    // 任何图片文件
    fileEl.setAttribute("accept", "image/*");

    fileEl.click();

    const getFileData = (fileTarget) => {
      return new Promise((resolve) => {
        fileTarget.onchange = async function (e) {
          resolve(e.target.files[0]);
        };
      });
    };

    const fileData = await getFileData(fileEl);
    console.info("fileData: ", fileData);

    const getReaderData = (fileData) => {
      const reader = new FileReader();
      reader.readAsDataURL(fileData); // 二进制

      return new Promise((resolve) => {
        reader.onload = function () {
          resolve(reader.result);
        };
      });
    };

    const readerData = await getReaderData(fileData);
    console.info("readerData: ", readerData);
  }
</script>

File对象

File:https://developer.mozilla.org/zh-CN/docs/Web/API/File

File对象数据结构

{
  lastModified: 1713116761687
  lastModifiedDate: Mon Apr 15 2024 01:46:01 GMT+0800 (中国标准时间) {}
  name: "BB1lA8Hk.png"
  size: 524776
  type: "image/png"
  webkitRelativePath: ""
}

File 对象相关属性

File 对象是来自用户在一个 <input> 元素上选择文件后返回的 FileList 对象 File 接口没有定义任何方法, 从 Blob 接口继承了以下方法, 也继承了 Blob 接口的属性

FileList
  File 
    {
      lastModified       __(__ 返回当前 File 对象所引用文件最后修改时间,自 UNIX 时间起始值(19701100:00:00 UTC)以来的毫秒数
      lastModifiedDate   __(__ 返回当前 File 对象所引用文件最后修改时间的 Date 对象。
      name               __(__ 返回当前 File 对象所引用文件的名字
      size               __(__ 返回文件的大小
      type               __(__ 返回文件的 多用途互联网邮件扩展类型(MIME Type)
      webkitRelativePath __(__ 返回 File 相关的 path 或 URL}

// 结构语法
const file = new File(
  bits, // 一个包含ArrayBuffer,ArrayBufferView,Blob,或者 DOMString 对象的 Array — 或者任何这些对象的组合。这是 UTF-8 编码的文件内容。
  name, // USVString,表示文件名称,或者文件路径。
  options // 选项对象,包含文件的可选属性。type: DOMString,表示将要放到文件中的内容的 MIME 类型。默认值为 "" ,见WEB/AJAX/[Content-Type媒体类型与MIME 类型].md。lastModified: 数值,表示文件最后修改时间的 Unix 时间戳(毫秒)。默认值为 Date.now()。
);

File 对象相关方法

Blob.slice

Blob.slice(
  start:        // 这个参数代表 Blob 里的下标,表示第一个会被会被拷贝进新的 Blob 的字节的起始位置。
  end:          // 这个参数代表的是 Blob 的一个下标,这个下标-1的对应的字节将会是被拷贝进新的Blob 的最后一个字节。
  contentType:  // 给新的 Blob 赋予一个新的文档类型。这将会把它的 type 属性设为被传入的值。它的默认值是一个空的字符串。
);

const path = URL.createObjectURL(blob);
URL.revokeObjectURL(path);

FileReader

https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader

FileReader

FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。

FileReader属性

const reader = new FileReader();

reader.readyState
reader.result

// 异步
const getDataText = (content) => {
  const reader = new FileReader();
  reader.readAsText(content, 'utf-8');

  return new Promise((resolve) => {
    reader.onload = function() {
      resolve(reader.result);
    }
  });
}
const dataText = await getDataText(textBlob);

FileReader事件

const reader = new FileReader();

//解析文件开始
reader.onloadstart = function(e) {
  console.log(e);
}     

//解析文件进度
reader.onprogress = function(e) {
  console.log(e);
} 

//解析文件中断
reader.onabort = function(e) {
  console.log(e);
}

//解析文件错误
reader.onerror = function(e) {
  console.log(e);
}

//解析文件完成
reader.onload = function(e) {
  const result = e.target.result || this.result;
  console.log(e);
  img.src = result;
  //这样就获取到了文件的Base64字符串
  //var base64 = getBase64Image(image);
  //Base64字符串转二进制
  //var file = dataURLtoBlob(base64);

  //上传
  var blob = new Blob([result], { type: 'image/jpg' });
  var fd = new FormData();
  fd.append('file', blob);

  //提交到服务器, 见ajax
}

//解析文件结束
reader.onloadend = function(e) {
  console.log(e);
}

FileReader接口

const file = file.files[0];
const reader = new FileReader();

// 注意:需要在reader.onload中读取,因为内容才能保障解析完成

//读取为文本  readAsText(File|Blob [, encoding])    encoding:asni, utf-8, gbk2312, gb2312, binary
// reader.readAsText(file, 'gb2312');

//将文件读取为 二进制码  readAsBinaryString(File|Blob), 从 2012 年 7 月 12 日起,该方法已从 W3C 工作草案废除。
reader.readAsBinaryString(file);

//将文件读取为 DataURL  readAsDataURL(File|Blob)
reader.readAsDataURL(file);

//将文件读取为 缓冲区  readAsArrayBuffer(File|Blob)
reader.readAsArrayBuffer(file);

FileReaderSync

FileReaderSync接口允许以同步的方式读取File或Blob对象中的内容,该接口只在workers里可用,因为在主线程里进行同步I/O操作可能会阻塞用户界面。

https://developer.mozilla.org/zh-CN/docs/Web/API/FileReaderSync

基本类型 MIME-type

# -----------------------------------------------------------------------------------
	文件后缀       MIME-type
# -----------------------------------------------------------------------------------
	*.3gpp        audio/3gpp, video/3gpp
	*.ac3         audio/ac3
	*.asf         allpication/vnd.ms-asf
	*.au          audio/basic
	*.css         text/css
	*.csv         text/csv
	*.doc    			application/msword    
	*.dot    			application/msword    
	*.dtd    			application/xml-dtd    
	*.dwg    			image/vnd.dwg    
	*.dxf      		image/vnd.dxf
	*.gif         image/gif    
	*.htm    			text/html    
	*.html    		text/html    
	*.jp2         image/jp2    
	*.jpe       	image/jpeg
	*.jpeg    		image/jpeg
	*.jpg         image/jpeg    
	*.js       		text/javascript, application/javascript    
	*.json    		application/json    
	*.mp2    			audio/mpeg, video/mpeg    
	*.mp3    			audio/mpeg    
	*.mp4    			audio/mp4, video/mp4    
	*.mpeg    		video/mpeg    
	*.mpg    			video/mpeg    
	*.mpp    			application/vnd.ms-project    
	*.ogg    			application/ogg, audio/ogg    
	*.pdf    			application/pdf    
	*.png    			image/png    
	*.pot    			application/vnd.ms-powerpoint    
	*.pps    			application/vnd.ms-powerpoint    
	*.ppt    			application/vnd.ms-powerpoint    
	*.rtf         application/rtf, text/rtf    
	*.svf         image/vnd.svf    
	*.tif         image/tiff    
	*.tiff       	image/tiff    
	*.txt         text/plain    
	*.wdb    			application/vnd.ms-works    
	*.wps    			application/vnd.ms-works    
	*.xhtml    		application/xhtml+xml    
	*.xlc      		application/vnd.ms-excel    
	*.xlm    			application/vnd.ms-excel    
	*.xls         application/vnd.ms-excel    
	*.xlt      		application/vnd.ms-excel    
	*.xlw     		application/vnd.ms-excel    
	*.xml    			text/xml, application/xml    
	*.zip         application/zip    
	*.xlsx     		application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
# -----------------------------------------------------------------------------------

支持的文件类型

支持 doc, docx, xls, xlsx, xlsm, ppt, pptx, csv, tsv, dotm, xlt, xltm, dot, dotx,xlam, xla 等 Office 办公文档 支持 wps, dps, et, ett, wpt 等国产 WPS Office 办公文档 支持 odt, ods, ots, odp, otp, six, ott, fodt, fods 等OpenOffice、LibreOffice 办公文档 支持 vsd, vsdx 等 Visio 流程图文件 支持 wmf, emf 等 Windows 系统图像文件 支持 psd 等 Photoshop 软件模型文件 支持 pdf ,ofd, rtf 等文档 支持 xmind 软件模型文件 支持 bpmn 工作流文件 支持 eml 邮件文件 支持 epub 图书文档 支持 obj, 3ds, stl, ply, gltf, glb, off, 3dm, fbx, dae, wrl, 3mf, ifc, brep, step, iges, fcstd, bim 等 3D 模型文件 支持 dwg, dxf 等 CAD 模型文件 支持 txt, xml(渲染), md(渲染), java, php, py, js, css 等所有纯文本 支持 zip, rar, jar, tar, gzip, 7z 等压缩包 支持 jpg, jpeg, png, gif, bmp, ico, jfif, webp 等图片预览(翻转,缩放,镜像) 支持 tif, tiff 图信息模型文件 支持 tga 图像格式文件 支持 svg 矢量图像格式文件 支持 mp3,wav,mp4,flv 等音视频格式文件 支持 avi,mov,rm,webm,ts,rm,mkv,mpeg,ogg,mpg,rmvb,wmv,3gp,ts,swf 等视频格式转码预览

二进制文件(.bin)

二进制文件,格式binary的缩写,例如main.bin。

数据结构之间转换

Blob转File对象

  this.modal.data = URL.createObjectURL(data);
  this.modal.visual = false;

  // 转换函数
  const file = new window.File([data], this.file.name)

  let formData = new FormData();
  formData.append("content", file);

URL转Base64

URL转Blob

Base64转Blob

Blob转Base64

网络图像文件转Base64

function getBase64Image(img) {
  var canvas = document.createElement("canvas");
  canvas.width = img.width;
  canvas.height = img.height;
  var ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0, img.width, img.height);
  var ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase();
  var dataURL = canvas.toDataURL("image/" + ext);
  return dataURL;
};

Base64字符串转二进制

见腾讯云COS上传示例。

function dataURItoBlobUtil(base64: string) {
  var arr = dataurl.split(','),
    mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], {
    type: mime
  });
};

Base64字符串转二进制

dataURItoBlobUtil(dataURI) {
  // base64 解码
  let byteString = window.atob(dataURI.split(',')[1])
  let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
  let ab = new ArrayBuffer(byteString.length)
  let ia = new Uint8Array(ab)
  for (let i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i)
  }
  return new Blob([ab], {type: mimeString})
}

Blob | 使用 Blob 创建一个指向类型化数组的URL

后端返回blob数据:服务端返回的为资源的二进制数据 前端处理: 前端接收到二进制数据后,使用 URL.createObjectURL(blobData) 方法将服务端返回的二进制数据转换为 blob 的 url 资源挂载到相应的资源对象。

  var typedArray = GetTheTypedArraySomehow();
  var blob = new Blob([typedArray.buffer], {type: 'application/octet-stream'}); // 传入一个合适的 MIME 类型
  var url = URL.createObjectURL(blob);

利用canvas.toDataURL的API转化成base64

function base64ToBlob ({b64data = '', contentType = '', sliceSize = 512} = {}) {
  return new Promise((resolve, reject) => {
    // 使用 atob() 方法将数据解码
    let byteCharacters = atob(b64data);
    let byteArrays = [];
    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      let slice = byteCharacters.slice(offset, offset + sliceSize);
      let byteNumbers = [];
      for (let i = 0; i < slice.length; i++) {
          byteNumbers.push(slice.charCodeAt(i));
      }
      // 8 位无符号整数值的类型化数组。内容将初始化为 0。
      // 如果无法分配请求数目的字节,则将引发异常。
      byteArrays.push(new Uint8Array(byteNumbers));
    }
    let result = new Blob(byteArrays, {
      type: contentType
    })
    result = Object.assign(result,{
      // 这里一定要处理一下 URL.createObjectURL
      preview: URL.createObjectURL(result),
      name: `XXX.png`
    });
    resolve(result)
  })
 }

字符串与Blob转换

  // 字符串转换为Blob
  const text = "你好,我是字符串";
  const textBlob = new Blob([text],{type: 'text/plain'});
  console.info(textBlob);

  // Blob转换为字符串
  const getDataText = (content) => {
    const reader = new FileReader();
    reader.readAsText(content, 'utf-8');

    return new Promise((resolve) => {
      reader.onload = function() {
        resolve(reader.result);
      }
    });
  }
  const dataText = await getDataText(textBlob);

File对象与Blob对象转换

    document.getElementById('uploadFile').onclick = function() {
      const file = document.createElement('input');
      file.setAttribute('type', 'file');
      // file.setAttribute('accept', 'video/mp4,video/webm,video/ogg');
      file.click();

      const getFileData = (file) => {
        return new Promise((resolve) => {
          file.onchange = async function(e) {
            resolve(e.target.files[0]);
          }
        });
      }

      const fileData = await getFileData(file);
      console.info('File对象', fileData);

      // File转换为Blob
      const fileBlob = new Blob([fileData], {type: fileData.type});
      console.info(fileBlob); 

      // Blob转换为File
      const fileData = new File([fileBlob], file.name, {type: file.type});
      console.info(fileData);


      // 上传文件
      const formData = new FormData();
      formData.append('file', fileData);

    }  

Base64编码

数据结构

  • 图片
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAwAAAAGlCAYAAABNz5H6AAAACXBIWXMAAA7EAAAOxAGVKw4bAAD//0lEQVR4Aey96ZMk23Hll91dvb0VwBDEQpg4w23MJJNJJpP0Rf//R0lfRiMNiY0gsRHkYIiH13t16/yO3xPhERmRmVVd/fAw0u2uuH59d79L7JFX3//v/7d3n3z09PDx06eHq/v3D/cO7w737t07vH331vC7t9SHw713wuvf2+u3alV5K9pb8b958+ZwEP+Dq4eiXx/+9b/8l8O/fvHFQSKHg3RSAF3uyYb+Hj16dPj4k08PTx49cf.....此处省略大量字符串.....AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
  • 音频
data:audio/mpeg;base64,SUQzAgAAAAAPdlRDTQAACwH//i6WSU5/YgAAVFQxAAAdAf/+ZGvGiZGYCWdzUUYANQDzl1BOIAAyADEAAABUVDIAABMB//5GADUA85dQTiAAMgAxAAAAVEVOAAAhAFByb1RyYW5zY29kZXJUb29sIChBcHBsZSBNUDMgdjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.....此处省略大量字符串.....AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==

Base64的编码与解码:https://developer.mozilla.org/zh-CN/docs/Glossary/Base64#Appendix.3A_Decode_a_Base64_string_to_Uint8Array_or_ArrayBuffer

简介与核心思想

Base64就是一种基于64个可打印字符来表示二进制数据的方法。

解码和编码 base64 字符串

如果传入字符串不是有效的 base64 字符串,比如其长度不是 4 的倍数,则抛出DOMException。

// 编码
let encodedData = window.btoa("Hello, world");

// 解码
let decodedData = window.atob(encodedData);

Blob

注意

File、FileList、FileReader、FileReaderSync继承了Blob, 所以Blob的属性与方法也可以在这些对象中使用。

简介与核心思想

Blob() 构造函数返回一个新的 Blob 对象。 blob的内容由参数数组中给出的值的串联组成。

语法

  new Blob( 
    array,                      // array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings会被编码为UTF-8
    options: {                  // options 是一个可选的BlobPropertyBag字典,它可能会指定如下两个属性, {}
      type: '',                 // type,默认值为 "",它代表了将会被放入到blob中的数组内容的MIME类型。
      endings: 'transparent'    // endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个: "native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持blob中保存的结束符不变
    }
  );

Blob属性

Blob.size:Blob 对象中所包含数据的大小(字节)。 Blob.type:一个字符串,表明该 Blob 对象所包含数据的 MIME 类型。如果类型未知,则该值为空字符串。

Blob方法

Blob.slice([start[, end[, contentType]]]):返回一个新的 Blob 对象,包含了源 Blob 对象中指定范围内的数据。 Blob.stream():返回一个能读取blob内容的 ReadableStream。 Blob.text():返回一个promise且包含blob所有内容的UTF-8格式的 USVString。 Blob.arrayBuffer():返回一个promise且包含blob所有内容的二进制格式的 ArrayBuffer

从Blob中读取数据

  // 方法一:一种从Blob中读取内容的方法是使用 FileReader。
  var reader = new FileReader();
  reader.addEventListener("loadend", function() {
    // reader.result 包含被转化为类型数组 typed array 的 blob
  });
  reader.readAsArrayBuffer(blob);


  // 方法二:另一种读取Blob中内容的方式是使用Response对象。
  var text = await (new Response(blob)).text();

代码示例

  var debug = {hello: "world"};
  var blob = new Blob([JSON.stringify(debug, null, 2)], {type : 'application/json'});

  var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; // 一个包含DOMString的数组
  var oMyBlob = new Blob(aFileParts, {type : 'text/html'}); // 得到 blob


  let typedArrayToURL = (typedArray, mimeType) => {
    return URL.createObjectURL(new Blob([typedArray.buffer], {type: mimeType}))
    // 产生一个类似 blob:d3958f5c-0777-0845-9dcf-2cb28783acaf 这样的URL字符串
  }

  let bytes = new Uint8Array(59);

  for (let i=0; i<59; i++) {
    bytes[i] = 32+i;
  }

  let url = typedArrayToURL(bytes, "text/plain");
  let link = document.createElement("a");
  link.href = url;
  link.innerText = "Open the array URL";
  document.body.appendChild(link);
<input type="file" id="single" onchange="singleFile()">
/**
 * 大文件分片上传技术
 *
 */
//兼容性支持
function blobSlice(blob) {
  let blobSlice = blob;
  if (blob.mozSlice) {  //Firefox 12及更早版本
    blobSlice = blob.mozSlice;
  } 
  if (blob.webkitSlice) {  //Safari旧版本
    blobSlice = blob.webkitSlice;
  }
  //其他
  blobSlice = blob.slice;

  //分片技术
  return blobSlice;
}

//分片上传
function Burst() {
  //
}





let file = document.getElementsByTagName('input')[0];
function singleFile() {
  let content = file.files[0];
  let size = content.size;  //name、size、type、lastModified
  console.log('文件分片前大小: ' + size);

  let scale = 100 * 1024;
  //小于scale,则不分片
  if (size > scale) {
    //
  }
  let multiple = Math.ceil(size / scale);
  console.log('分片数量:' + multiple);
  let remainder = size % scale;

  //没有余数,全是倍数
  if (remainder == 0) {
    //
  }

  let integer = scale * (multiple - 1);
  let total = integer + remainder;
  console.log('分片合并后大小:' + total);

  //整合分片数据结构
  let sliceData = [];
  for (let i = 0; i < multiple; i++) {
    (function(i) {
      let slice = {};
      slice['type'] = content.type;
      slice['name'] = content.name;
      slice['lastModified'] = content.lastModified;
      slice['total'] = content.size;
      slice['id'] = i;
      slice['prev'] = i*scale;
      slice['next'] = (i+1)*scale;
      slice['blob'] = content.slice(i*scale, (i+1)*scale);
      slice['size'] = scale;
      //兼容分片不足整数部分
      if (i == multiple - 1) {
        let last = i*scale + remainder;
        slice['next'] = last;
        slice['blob'] = content.slice(i*scale, last);
        slice['size'] = remainder;
      } 
      
      sliceData.push(slice);
    })(i);
  }
  
  recurrence(sliceData);
  //decollate(sliceData);

}

//分割上传-递归等待分片上传成功
function recurrence(sliceData) {
  sliceData = sliceData || [];

  //递归推送, 成功继续推送
  let count = 0;
  function pushBlob(i) {
    //推送完成
    if (count == sliceData.length) {
      return ;
    }

    let dx = sliceData[i];

    //封装数据
    let formData = new FormData();
    formData.append("content", dx.blob);

    //拼接片段标志参数
    let query = "";
    for (let i in dx) {
      if (i != 'blob') {
        query += '&' + i + '=' + dx[i];
      }
    }
    query = query.replace(/^&/, '?');

    //开始推送
    ajax({ 
      base: 'http://127.0.0.1:7901/',
      path: 'decollate' + query,
      method: 'post',
      type: 'upload',
      timeout: 1500000,
      size: dx.size,
      data: formData,
      complete() {},
      success(res) {
        //继续推送下一流
        count += 1;
        pushBlob(count);
      },
      fail(err) {
        //推送失败,重复推送
        pushBlob(count);
      }
    });

  }
  pushBlob(count);

}

//分割上传-演示
function decollate(sliceData) {
  sliceData = sliceData || [];

  sliceData.forEach((dx)=> {
    let formData = new FormData();
    formData.append("content", dx.blob);

    //拼接片段标志参数
    let query = "";
    for (let i in dx) {
      if (i != 'blob') {
        query += '&' + i + '=' + dx[i];
      }
    }
    query = query.replace(/^&/, '?');

    //推送服务器
    ajax({ 
      base: 'http://127.0.0.1:7901/',
      path: 'decollate' + query,
      method: 'post',
      type: 'upload',
      timeout: 1500000,
      size: dx.size,
      data: formData,
      complete() {},
      success(res) {
        //console.log(res);
      },
      fail(err) {
        //console.log(err);
      }
    });
  });
}

Last Updated:
Contributors: 709992523, Eshen