js实现浏览与保存本地常见文件 (2022.10.24)

1、需求分析

        在平时的学习和工作中,相信大家会经常遇到这样一个问题:我在阅读网页时遇到一个json文件,保存到本地后需要查看,那么我该选择什么样的IDE来打开这些文件呢?
        此时,为了解决上述问题,一般来说大致有三类解决方案:第一种,其中最简单的方式是用记事本Notepad打开,但可能有的小伙伴不喜欢使用Window操作系统自带的记事本查看文本文件,因为没有特定的颜色和变量标识,不利于编码;第二种方式,可以安装一些 IDE文本编辑器 来打开文件进行编辑;第三种,同时也是我认为较好的方方法,尤其对于比较懒的一些工程师或者开发者而言,浏览器Browser往往是最快捷、最普遍、最简单的文件查看器。

2、使用js方法介绍

        为了编写JavaScript代码来加载和保存本地文件,我们需要进行文件的读取操作,即文件读取文件写入,具体涉及到的方法就是FileSaverFileReader

2.1 FileSaver.js 简介

        FileSaver是一种在客户端保存文件的解决方案,并且具有非常完美的应用表现,尤其是客户端Web应用的文件生成方面。它支持的浏览器包括Firefox、Chrome、Edge、IE、Opera 和 Safari

Github上的FileSaver介绍

FileSaver.js文件内容

(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    define([], factory);
  } else if (typeof exports !== "undefined") {
    factory();
  } else {
    var mod = {
      exports: {}
    };
    factory();
    global.FileSaver = mod.exports;
  }
})(this, function () {
  "use strict";

  /*
  * FileSaver.js
  * A saveAs() FileSaver implementation.
  *
  * By Eli Grey, http://eligrey.com
  *
  * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)
  * source  : http://purl.eligrey.com/github/FileSaver.js
  */
  // The one and only way of getting global scope in all environments
  // https://stackoverflow.com/q/3277182/1008999
  var _global = typeof window === 'object' && window.window === window ? window : typeof self === 'object' && self.self === self ? self : typeof global === 'object' && global.global === global ? global : void 0;

  function bom(blob, opts) {
    if (typeof opts === 'undefined') opts = {
      autoBom: false
    };else if (typeof opts !== 'object') {
      console.warn('Deprecated: Expected third argument to be a object');
      opts = {
        autoBom: !opts
      };
    } // prepend BOM for UTF-8 XML and text/* types (including HTML)
    // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF

    if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
      return new Blob([String.fromCharCode(0xFEFF), blob], {
        type: blob.type
      });
    }

    return blob;
  }

  function download(url, name, opts) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.responseType = 'blob';

    xhr.onload = function () {
      saveAs(xhr.response, name, opts);
    };

    xhr.onerror = function () {
      console.error('could not download file');
    };

    xhr.send();
  }

  function corsEnabled(url) {
    var xhr = new XMLHttpRequest(); // use sync to avoid popup blocker

    xhr.open('HEAD', url, false);

    try {
      xhr.send();
    } catch (e) {}

    return xhr.status >= 200 && xhr.status <= 299;
  } // `a.click()` doesn't work for all browsers (#465)


  function click(node) {
    try {
      node.dispatchEvent(new MouseEvent('click'));
    } catch (e) {
      var evt = document.createEvent('MouseEvents');
      evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
      node.dispatchEvent(evt);
    }
  } // Detect WebView inside a native macOS app by ruling out all browsers
  // We just need to check for 'Safari' because all other browsers (besides Firefox) include that too
  // https://www.whatismybrowser.com/guides/the-latest-user-agent/macos


  var isMacOSWebView = /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent);
  var saveAs = _global.saveAs || ( // probably in some web worker
  typeof window !== 'object' || window !== _global ? function saveAs() {}
  /* noop */
  // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView
  : 'download' in HTMLAnchorElement.prototype && !isMacOSWebView ? function saveAs(blob, name, opts) {
    var URL = _global.URL || _global.webkitURL;
    var a = document.createElement('a');
    name = name || blob.name || 'download';
    a.download = name;
    a.rel = 'noopener'; // tabnabbing
    // TODO: detect chrome extensions & packaged apps
    // a.target = '_blank'

    if (typeof blob === 'string') {
      // Support regular links
      a.href = blob;

      if (a.origin !== location.origin) {
        corsEnabled(a.href) ? download(blob, name, opts) : click(a, a.target = '_blank');
      } else {
        click(a);
      }
    } else {
      // Support blobs
      a.href = URL.createObjectURL(blob);
      setTimeout(function () {
        URL.revokeObjectURL(a.href);
      }, 4E4); // 40s

      setTimeout(function () {
        click(a);
      }, 0);
    }
  } // Use msSaveOrOpenBlob as a second approach
  : 'msSaveOrOpenBlob' in navigator ? function saveAs(blob, name, opts) {
    name = name || blob.name || 'download';

    if (typeof blob === 'string') {
      if (corsEnabled(blob)) {
        download(blob, name, opts);
      } else {
        var a = document.createElement('a');
        a.href = blob;
        a.target = '_blank';
        setTimeout(function () {
          click(a);
        });
      }
    } else {
      navigator.msSaveOrOpenBlob(bom(blob, opts), name);
    }
  } // Fallback to using FileReader and a popup
  : function saveAs(blob, name, opts, popup) {
    // Open a popup immediately do go around popup blocker
    // Mostly only available on user interaction and the fileReader is async so...
    popup = popup || open('', '_blank');

    if (popup) {
      popup.document.title = popup.document.body.innerText = 'downloading...';
    }

    if (typeof blob === 'string') return download(blob, name, opts);
    var force = blob.type === 'application/octet-stream';

    var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari;

    var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);

    if ((isChromeIOS || force && isSafari || isMacOSWebView) && typeof FileReader !== 'undefined') {
      // Safari doesn't allow downloading of blob URLs
      var reader = new FileReader();

      reader.onloadend = function () {
        var url = reader.result;
        url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;');
        if (popup) popup.location.href = url;else location = url;
        popup = null; // reverse-tabnabbing #460
      };

      reader.readAsDataURL(blob);
    } else {
      var URL = _global.URL || _global.webkitURL;
      var url = URL.createObjectURL(blob);
      if (popup) popup.location = url;else location.href = url;
      popup = null; // reverse-tabnabbing #460

      setTimeout(function () {
        URL.revokeObjectURL(url);
      }, 4E4); // 40s
    }
  });
  _global.saveAs = saveAs.saveAs = saveAs;

  if (typeof module !== 'undefined') {
    module.exports = saveAs;
  }
});

2.1.1 FileSaver的import引入(Node环境)

        在Vue等大型项目当中,一般通过npm安装FileSaver,输入命令npm install file-saver --save,推荐使用import方式导入js类库,然后进行相关方法的调用,具体如下:

import { saveAs } from 'file-saver';

2.1.2 FileSaver的script引入(HTML文件)

        如果要在html文档中使用FileSaver库,那么仅仅在script标签中引入对应的js文件即可,具体如下:

 <script src="https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.js"></script>

2.1.3 FileSaver的调用方法

var file = new File(["Hello, world!"], "hello world.txt", {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(file);
// 保存txt文件
var blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "hello world.txt");

//保存 URL
FileSaver.saveAs("https://httpbin.org/image", "image.jpg");

//保存 canvas
var canvas = document.getElementById("my-canvas");
canvas.toBlob(function(blob) {
    saveAs(blob, "pretty image.png");
});

当然,如果想要保存网络文件,可以参考Saving a remote file
在这里插入图片描述

2.2 FileReader

        这里使用FileReader来对本地磁盘文件进行读取操作,

2.2.1 检测浏览器是否支持FileReader的js函数

function detect_FileReader() {
   if (window.FileReader) {
        var fr = new FileReader();
        return true;
    } else {
        return false;
    }
}

2.2.2 Blob.js

在这里插入图片描述

        下面放置了Blob.js文件的代码,里面介绍了BlobFileFileReader的相关构造器实现,可供感兴趣的开发者参考查看,受益匪浅。
Blob.js文件内容:

/* Blob.js
 * A Blob, File, FileReader & URL implementation.
 * 2019-04-19
 *
 * By Eli Grey, http://eligrey.com
 * By Jimmy Wärting, https://github.com/jimmywarting
 * License: MIT
 *   See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
 */

;(function () {
  var global = typeof window === 'object'
      ? window : typeof self === 'object'
      ? self : this

  var BlobBuilder = global.BlobBuilder
    || global.WebKitBlobBuilder
    || global.MSBlobBuilder
    || global.MozBlobBuilder

  global.URL = global.URL || global.webkitURL || function (href, a) {
  	a = document.createElement('a')
  	a.href = href
  	return a
  }

  var origBlob = global.Blob
  var createObjectURL = URL.createObjectURL
  var revokeObjectURL = URL.revokeObjectURL
  var strTag = global.Symbol && global.Symbol.toStringTag
  var blobSupported = false
  var blobSupportsArrayBufferView = false
  var arrayBufferSupported = !!global.ArrayBuffer
  var blobBuilderSupported = BlobBuilder
    && BlobBuilder.prototype.append
    && BlobBuilder.prototype.getBlob

  try {
    // Check if Blob constructor is supported
    blobSupported = new Blob(['ä']).size === 2

    // Check if Blob constructor supports ArrayBufferViews
    // Fails in Safari 6, so we need to map to ArrayBuffers there.
    blobSupportsArrayBufferView = new Blob([new Uint8Array([1, 2])]).size === 2
  } catch (e) {}

  /**
   * Helper function that maps ArrayBufferViews to ArrayBuffers
   * Used by BlobBuilder constructor and old browsers that didn't
   * support it in the Blob constructor.
   */
  function mapArrayBufferViews (ary) {
    return ary.map(function (chunk) {
      if (chunk.buffer instanceof ArrayBuffer) {
        var buf = chunk.buffer

        // if this is a subarray, make a copy so we only
        // include the subarray region from the underlying buffer
        if (chunk.byteLength !== buf.byteLength) {
          var copy = new Uint8Array(chunk.byteLength)
          copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength))
          buf = copy.buffer
        }

        return buf
      }

      return chunk
    })
  }

  function BlobBuilderConstructor (ary, options) {
    options = options || {}

    var bb = new BlobBuilder()
    mapArrayBufferViews(ary).forEach(function (part) {
      bb.append(part)
    })

    return options.type ? bb.getBlob(options.type) : bb.getBlob()
  }

  function BlobConstructor (ary, options) {
    return new origBlob(mapArrayBufferViews(ary), options || {})
  }

  if (global.Blob) {
    BlobBuilderConstructor.prototype = Blob.prototype
    BlobConstructor.prototype = Blob.prototype
  }



  /********************************************************/
  /*               String Encoder fallback                */
  /********************************************************/
  function stringEncode (string) {
    var pos = 0
    var len = string.length
    var Arr = global.Uint8Array || Array // Use byte array when possible

    var at = 0  // output position
    var tlen = Math.max(32, len + (len >> 1) + 7)  // 1.5x size
    var target = new Arr((tlen >> 3) << 3)  // ... but at 8 byte offset

    while (pos < len) {
      var value = string.charCodeAt(pos++)
      if (value >= 0xd800 && value <= 0xdbff) {
        // high surrogate
        if (pos < len) {
          var extra = string.charCodeAt(pos)
          if ((extra & 0xfc00) === 0xdc00) {
            ++pos
            value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000
          }
        }
        if (value >= 0xd800 && value <= 0xdbff) {
          continue  // drop lone surrogate
        }
      }

      // expand the buffer if we couldn't write 4 bytes
      if (at + 4 > target.length) {
        tlen += 8  // minimum extra
        tlen *= (1.0 + (pos / string.length) * 2)  // take 2x the remaining
        tlen = (tlen >> 3) << 3  // 8 byte offset

        var update = new Uint8Array(tlen)
        update.set(target)
        target = update
      }

      if ((value & 0xffffff80) === 0) {  // 1-byte
        target[at++] = value  // ASCII
        continue
      } else if ((value & 0xfffff800) === 0) {  // 2-byte
        target[at++] = ((value >> 6) & 0x1f) | 0xc0
      } else if ((value & 0xffff0000) === 0) {  // 3-byte
        target[at++] = ((value >> 12) & 0x0f) | 0xe0
        target[at++] = ((value >> 6) & 0x3f) | 0x80
      } else if ((value & 0xffe00000) === 0) {  // 4-byte
        target[at++] = ((value >> 18) & 0x07) | 0xf0
        target[at++] = ((value >> 12) & 0x3f) | 0x80
        target[at++] = ((value >> 6) & 0x3f) | 0x80
      } else {
        // FIXME: do we care
        continue
      }

      target[at++] = (value & 0x3f) | 0x80
    }

    return target.slice(0, at)
  }

  /********************************************************/
  /*               String Decoder fallback                */
  /********************************************************/
  function stringDecode (buf) {
    var end = buf.length
    var res = []

    var i = 0
    while (i < end) {
      var firstByte = buf[i]
      var codePoint = null
      var bytesPerSequence = (firstByte > 0xEF) ? 4
        : (firstByte > 0xDF) ? 3
          : (firstByte > 0xBF) ? 2
            : 1

      if (i + bytesPerSequence <= end) {
        var secondByte, thirdByte, fourthByte, tempCodePoint

        switch (bytesPerSequence) {
          case 1:
            if (firstByte < 0x80) {
              codePoint = firstByte
            }
            break
          case 2:
            secondByte = buf[i + 1]
            if ((secondByte & 0xC0) === 0x80) {
              tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)
              if (tempCodePoint > 0x7F) {
                codePoint = tempCodePoint
              }
            }
            break
          case 3:
            secondByte = buf[i + 1]
            thirdByte = buf[i + 2]
            if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
              tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)
              if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
                codePoint = tempCodePoint
              }
            }
            break
          case 4:
            secondByte = buf[i + 1]
            thirdByte = buf[i + 2]
            fourthByte = buf[i + 3]
            if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
              tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)
              if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
                codePoint = tempCodePoint
              }
            }
        }
      }

      if (codePoint === null) {
        // we did not generate a valid codePoint so insert a
        // replacement char (U+FFFD) and advance only 1 byte
        codePoint = 0xFFFD
        bytesPerSequence = 1
      } else if (codePoint > 0xFFFF) {
        // encode to utf16 (surrogate pair dance)
        codePoint -= 0x10000
        res.push(codePoint >>> 10 & 0x3FF | 0xD800)
        codePoint = 0xDC00 | codePoint & 0x3FF
      }

      res.push(codePoint)
      i += bytesPerSequence
    }

    var len = res.length
    var str = ''
    var i = 0

    while (i < len) {
      str += String.fromCharCode.apply(String, res.slice(i, i += 0x1000))
    }

    return str
  }

  // string -> buffer
  var textEncode = typeof TextEncoder === 'function'
    ? TextEncoder.prototype.encode.bind(new TextEncoder())
    : stringEncode

  // buffer -> string
  var textDecode = typeof TextDecoder === 'function'
    ? TextDecoder.prototype.decode.bind(new TextDecoder())
    : stringDecode

  function FakeBlobBuilder () {
    function isDataView (obj) {
      return obj && DataView.prototype.isPrototypeOf(obj)
    }
    function bufferClone (buf) {
      var view = new Array(buf.byteLength)
      var array = new Uint8Array(buf)
      var i = view.length
      while (i--) {
        view[i] = array[i]
      }
      return view
    }
    function array2base64 (input) {
      var byteToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='

      var output = []

      for (var i = 0; i < input.length; i += 3) {
        var byte1 = input[i]
        var haveByte2 = i + 1 < input.length
        var byte2 = haveByte2 ? input[i + 1] : 0
        var haveByte3 = i + 2 < input.length
        var byte3 = haveByte3 ? input[i + 2] : 0

        var outByte1 = byte1 >> 2
        var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4)
        var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6)
        var outByte4 = byte3 & 0x3F

        if (!haveByte3) {
          outByte4 = 64

          if (!haveByte2) {
            outByte3 = 64
          }
        }

        output.push(
          byteToCharMap[outByte1], byteToCharMap[outByte2],
          byteToCharMap[outByte3], byteToCharMap[outByte4]
        )
      }

      return output.join('')
    }

    var create = Object.create || function (a) {
      function c () {}
      c.prototype = a
      return new c()
    }

    if (arrayBufferSupported) {
      var viewClasses = [
        '[object Int8Array]',
        '[object Uint8Array]',
        '[object Uint8ClampedArray]',
        '[object Int16Array]',
        '[object Uint16Array]',
        '[object Int32Array]',
        '[object Uint32Array]',
        '[object Float32Array]',
        '[object Float64Array]'
      ]

      var isArrayBufferView = ArrayBuffer.isView || function (obj) {
        return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
      }
    }

    function concatTypedarrays (chunks) {
      var size = 0
      var i = chunks.length
      while (i--) { size += chunks[i].length }
      var b = new Uint8Array(size)
      var offset = 0
      for (i = 0, l = chunks.length; i < l; i++) {
        var chunk = chunks[i]
        b.set(chunk, offset)
        offset += chunk.byteLength || chunk.length
      }

      return b
    }

    /********************************************************/
    /*                   Blob constructor                   */
    /********************************************************/
    function Blob (chunks, opts) {
      chunks = chunks || []
      opts = opts == null ? {} : opts
      for (var i = 0, len = chunks.length; i < len; i++) {
        var chunk = chunks[i]
        if (chunk instanceof Blob) {
          chunks[i] = chunk._buffer
        } else if (typeof chunk === 'string') {
          chunks[i] = textEncode(chunk)
        } else if (arrayBufferSupported && (ArrayBuffer.prototype.isPrototypeOf(chunk) || isArrayBufferView(chunk))) {
          chunks[i] = bufferClone(chunk)
        } else if (arrayBufferSupported && isDataView(chunk)) {
          chunks[i] = bufferClone(chunk.buffer)
        } else {
          chunks[i] = textEncode(String(chunk))
        }
      }

      this._buffer = global.Uint8Array
        ? concatTypedarrays(chunks)
        : [].concat.apply([], chunks)
      this.size = this._buffer.length

      this.type = opts.type || ''
      if (/[^\u0020-\u007E]/.test(this.type)) {
        this.type = ''
      } else {
        this.type = this.type.toLowerCase()
      }
    }

    Blob.prototype.arrayBuffer = function () {
      return Promise.resolve(this._buffer)
    }

    Blob.prototype.text = function () {
      return Promise.resolve(textDecode(this._buffer))
    }

    Blob.prototype.slice = function (start, end, type) {
      var slice = this._buffer.slice(start || 0, end || this._buffer.length)
      return new Blob([slice], {type: type})
    }

    Blob.prototype.toString = function () {
      return '[object Blob]'
    }

    /********************************************************/
    /*                   File constructor                   */
    /********************************************************/
    function File (chunks, name, opts) {
      opts = opts || {}
      var a = Blob.call(this, chunks, opts) || this
      a.name = name.replace(/\//g, ':')
      a.lastModifiedDate = opts.lastModified ? new Date(opts.lastModified) : new Date()
      a.lastModified = +a.lastModifiedDate

      return a
    }

    File.prototype = create(Blob.prototype)
    File.prototype.constructor = File

    if (Object.setPrototypeOf) {
      Object.setPrototypeOf(File, Blob)
    } else {
      try { File.__proto__ = Blob } catch (e) {}
    }

    File.prototype.toString = function () {
      return '[object File]'
    }

    /********************************************************/
    /*                FileReader constructor                */
    /********************************************************/
    function FileReader () {
    	if (!(this instanceof FileReader)) {
        throw new TypeError("Failed to construct 'FileReader': Please use the 'new' operator, this DOM object constructor cannot be called as a function.")
      }

    	var delegate = document.createDocumentFragment()
    	this.addEventListener = delegate.addEventListener
    	this.dispatchEvent = function (evt) {
    		var local = this['on' + evt.type]
    		if (typeof local === 'function') local(evt)
    		delegate.dispatchEvent(evt)
    	}
    	this.removeEventListener = delegate.removeEventListener
    }

    function _read (fr, blob, kind) {
    	if (!(blob instanceof Blob)) {
        throw new TypeError("Failed to execute '" + kind + "' on 'FileReader': parameter 1 is not of type 'Blob'.")
      }

    	fr.result = ''

    	setTimeout(function () {
    		this.readyState = FileReader.LOADING
    		fr.dispatchEvent(new Event('load'))
    		fr.dispatchEvent(new Event('loadend'))
    	})
    }

    FileReader.EMPTY = 0
    FileReader.LOADING = 1
    FileReader.DONE = 2
    FileReader.prototype.error = null
    FileReader.prototype.onabort = null
    FileReader.prototype.onerror = null
    FileReader.prototype.onload = null
    FileReader.prototype.onloadend = null
    FileReader.prototype.onloadstart = null
    FileReader.prototype.onprogress = null

    FileReader.prototype.readAsDataURL = function (blob) {
    	_read(this, blob, 'readAsDataURL')
    	this.result = 'data:' + blob.type + ';base64,' + array2base64(blob._buffer)
    }

    FileReader.prototype.readAsText = function (blob) {
    	_read(this, blob, 'readAsText')
    	this.result = textDecode(blob._buffer)
    }

    FileReader.prototype.readAsArrayBuffer = function (blob) {
      _read(this, blob, 'readAsText')
       // return ArrayBuffer when possible
      this.result = (blob._buffer.buffer || blob._buffer).slice()
    }

    FileReader.prototype.abort = function () {}

    /********************************************************/
    /*                         URL                          */
    /********************************************************/
    URL.createObjectURL = function (blob) {
      return blob instanceof Blob
        ? 'data:' + blob.type + ';base64,' + array2base64(blob._buffer)
        : createObjectURL.call(URL, blob)
    }

    URL.revokeObjectURL = function (url) {
      revokeObjectURL && revokeObjectURL.call(URL, url)
    }

    /********************************************************/
    /*                         XHR                          */
    /********************************************************/
    var _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send
    if (_send) {
      XMLHttpRequest.prototype.send = function (data) {
        if (data instanceof Blob) {
          this.setRequestHeader('Content-Type', data.type)
          _send.call(this, textDecode(data._buffer))
        } else {
          _send.call(this, data)
        }
      }
    }

    global.FileReader = FileReader
    global.File = File
    global.Blob = Blob
  }

  function fixFileAndXHR () {
    var isIE = !!global.ActiveXObject || (
      '-ms-scroll-limit' in document.documentElement.style &&
      '-ms-ime-align' in document.documentElement.style
    )

    // Monkey patched
    // IE don't set Content-Type header on XHR whose body is a typed Blob
    // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6047383
    var _send = global.XMLHttpRequest && global.XMLHttpRequest.prototype.send
    if (isIE && _send) {
      XMLHttpRequest.prototype.send = function (data) {
        if (data instanceof Blob) {
          this.setRequestHeader('Content-Type', data.type)
          _send.call(this, data)
        } else {
          _send.call(this, data)
        }
      }
    }

    try {
      new File([], '')
    } catch (e) {
      try {
        var klass = new Function('class File extends Blob {' +
          'constructor(chunks, name, opts) {' +
            'opts = opts || {};' +
            'super(chunks, opts || {});' +
            'this.name = name.replace(/\//g, ":");' +
            'this.lastModifiedDate = opts.lastModified ? new Date(opts.lastModified) : new Date();' +
            'this.lastModified = +this.lastModifiedDate;' +
          '}};' +
          'return new File([], ""), File'
        )()
        global.File = klass
      } catch (e) {
        var klass = function (b, d, c) {
          var blob = new Blob(b, c)
          var t = c && void 0 !== c.lastModified ? new Date(c.lastModified) : new Date()

          blob.name = d.replace(/\//g, ':')
          blob.lastModifiedDate = t
          blob.lastModified = +t
          blob.toString = function () {
            return '[object File]'
          }

          if (strTag) {
            blob[strTag] = 'File'
          }

          return blob
        }
        global.File = klass
      }
    }
  }

  if (blobSupported) {
    fixFileAndXHR()
    global.Blob = blobSupportsArrayBufferView ? global.Blob : BlobConstructor
  } else if (blobBuilderSupported) {
    fixFileAndXHR()
    global.Blob = BlobBuilderConstructor
  } else {
    FakeBlobBuilder()
  }

  if (strTag) {
    File.prototype[strTag] = 'File'
    Blob.prototype[strTag] = 'Blob'
    FileReader.prototype[strTag] = 'FileReader'
  }

  var blob = global.Blob.prototype
  var stream

  function promisify(obj) {
    return new Promise(function(resolve, reject) {
      obj.onload =
      obj.onerror = function(evt) {
        obj.onload =
        obj.onerror = null

        evt.type === 'load'
          ? resolve(obj.result || obj)
          : reject(new Error('Failed to read the blob/file'))
      }
    })
  }


  try {
    new ReadableStream({ type: 'bytes' })
    stream = function stream() {
      var position = 0
      var blob = this

      return new ReadableStream({
        type: 'bytes',
        autoAllocateChunkSize: 524288,

        pull: function (controller) {
          var v = controller.byobRequest.view
          var chunk = blob.slice(position, position + v.byteLength)
          return chunk.arrayBuffer()
          .then(function (buffer) {
            var uint8array = new Uint8Array(buffer)
            var bytesRead = uint8array.byteLength

            position += bytesRead
            v.set(uint8array)
              controller.byobRequest.respond(bytesRead)

            if(position >= blob.size)
              controller.close()
          })
        }
      })
    }
  } catch (e) {
    try {
      new ReadableStream({})
      stream = function stream(blob){
        var position = 0
        var blob = this

        return new ReadableStream({
          pull: function (controller) {
            var chunk = blob.slice(position, position + 524288)

            return chunk.arrayBuffer().then(function (buffer) {
              position += buffer.byteLength
              var uint8array = new Uint8Array(buffer)
              controller.enqueue(uint8array)

              if (position == blob.size)
                controller.close()
            })
          }
        })
      }
    } catch (e) {
      try {
        new Response('').body.getReader().read()
        stream = function stream() {
          return (new Response(this)).body
        }
      } catch (e) {
        stream = function stream() {
          throw new Error('Include https://github.com/MattiasBuelens/web-streams-polyfill')
        }
      }
    }
  }


  if (!blob.arrayBuffer) {
    blob.arrayBuffer = function arrayBuffer() {
      var fr = new FileReader()
      fr.readAsArrayBuffer(this)
      return promisify(fr)
    }
  }

  if (!blob.text) {
    blob.text = function text() {
      var fr = new FileReader()
      fr.readAsText(this)
      return promisify(fr)
    }
  }

  if (!blob.stream) {
    blob.stream = stream
  }
})()

2.2.3 FileReader调用方法

       &nbsp;FileReader可用来读取文本文件图片文件,功能确实强大,希望前端开发者都可以多学习学习,精益求精。

//读取图片文件(如.jpg、.png、.gif等)
 const fileReader = new FileReader();
 fileReader.readAsDataURL(imgFile);
 fileReader.onload = function() {
     document.getElementById("img").src = fileReader.result;
     console.log(img.getAttribute("src"));
 }
 
//读取文本文件(如.txt、.json、.html、.js、.cs、.cpp、.py、.java、.m、.py、.dat、.csv、.cmd、.bat等)
const fileReader = new FileReader();
fileReader.readAsText(file);
fileReader.onload = function() {
    document.getElementById("result").innerText = fileReader.result;
    console.log(JSON.parse(fileReader.result));
};

3、input简介

        在html中,input标签可用作输入框,为用户进行表单输入提供条件;另外,input标签如果设置为file类型,则支持用户浏览本地文件。

accept 属性只能与 <input type="file"> 配合使用。它规定能够通过文件上传进行提交的文件类型。
 
值   描述
audio/* 接受所有的声音文件。
video/* 接受所有的视频文件。
image/* 接受所有的图像文件。
MIME_type   一个有效的 MIME 类型,不带参数。请参阅 IANA MIME 类型,获得标准 MIME 类型的完整列表。
accept可以指定如下信息:
 
*.3gpp  audio/3gpp, video/3gpp  3GPP Audio/Video
*.ac3   audio/ac3   AC3 Audio
*.asf   allpication/vnd.ms-asf  Advanced Streaming Format
*.au    audio/basic AU Audio
*.css   text/css    Cascading Style Sheets
*.csv   text/csv    Comma Separated Values
*.doc   application/msword  MS Word Document
*.dot   application/msword  MS Word Template
*.dtd   application/xml-dtd Document Type Definition
*.dwg   image/vnd.dwg   AutoCAD Drawing Database
*.dxf   image/vnd.dxf   AutoCAD Drawing Interchange Format
*.gif   image/gif   Graphic Interchange Format
*.htm   text/html   HyperText Markup Language
*.html  text/html   HyperText Markup Language
*.jp2   image/jp2   JPEG-2000
*.jpe   image/jpeg  JPEG
*.jpeg  image/jpeg  JPEG
*.jpg   image/jpeg  JPEG
*.js    text/javascript, application/javascript JavaScript
*.json  application/json    JavaScript Object Notation
*.mp2   audio/mpeg, video/mpeg  MPEG Audio/Video Stream, Layer II
*.mp3   audio/mpeg  MPEG Audio Stream, Layer III
*.mp4   audio/mp4, video/mp4    MPEG-4 Audio/Video
*.mpeg  video/mpeg  MPEG Video Stream, Layer II
*.mpg   video/mpeg  MPEG Video Stream, Layer II
*.mpp   application/vnd.ms-project  MS Project Project
*.ogg   application/ogg, audio/ogg  Ogg Vorbis
*.pdf   application/pdf Portable Document Format
*.png   image/png   Portable Network Graphics
*.pot   application/vnd.ms-powerpoint   MS PowerPoint Template
*.pps   application/vnd.ms-powerpoint   MS PowerPoint Slideshow
*.ppt   application/vnd.ms-powerpoint   MS PowerPoint Presentation
*.rtf   application/rtf, text/rtf   Rich Text Format
*.svf   image/vnd.svf   Simple Vector Format
*.tif   image/tiff  Tagged Image Format File
*.tiff  image/tiff  Tagged Image Format File
*.txt   text/plain  Plain Text
*.wdb   application/vnd.ms-works    MS Works Database
*.wps   application/vnd.ms-works    Works Text Document
*.xhtml application/xhtml+xml   Extensible HyperText Markup Language
*.xlc   application/vnd.ms-excel    MS Excel Chart
*.xlm   application/vnd.ms-excel    MS Excel Macro
*.xls   application/vnd.ms-excel    MS Excel Spreadsheet
*.xlt   application/vnd.ms-excel    MS Excel Template
*.xlw   application/vnd.ms-excel    MS Excel Workspace
*.xml   text/xml, application/xml   Extensible Markup Language
*.zip   application/zip Compressed Archive

除上述类型外,2007后各文档如docx需配置的accept属性值如下:
Extension MIME Type
.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xltx application/vnd.openxmlformats-officedocument.spreadsheetml.template
.potx application/vnd.openxmlformats-officedocument.presentationml.template
.ppsx application/vnd.openxmlformats-officedocument.presentationml.slideshow
.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
.sldx application/vnd.openxmlformats-officedocument.presentationml.slide
.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
.dotx application/vnd.openxmlformats-officedocument.wordprocessingml.template
.xlsm application/vnd.ms-excel.addin.macroEnabled.12
.xlsb application/vnd.ms-excel.sheet.binary.macroEnabled.12

3.1 input输入框用法(type为text)

        input标签设置为text类型,用户可在框中进行输入文本。

<input style="background:transparent;border:2px solid rgb(231, 240, 240);color:rgb(74, 219, 230);height:30px;" id="website" value="http://mars3d.cn/project/vue/jcxm.html" />
<input id="json_filename" type="text" style="width:75%;background:transparent;" />

3.2 input 文件浏览按钮用法(type为file+button)

       input标签分别设置为filebutton类型,用户可点击框选择本地文件。

<input type="file" id="up_jsonfile" name="jsonfile" style="display:none" required="required" accept="application/json, text/plain" onchange="readJSONData()" />
<input type="button" name="submit" style="width:15%;height:30px;border:1px solid #34495E;background-color:#5e3444;color:#ffffff" value="浏览json文件" onclick="select_jsonfile();" />

<input type="file" id="up_imagefile" name="imagefile" style="display:none" required="required" accept="image/gif, image/jpg, image/png" onchange="readImageData()" />
<input type="button" name="submit" style="width:15%;height:30px;border:1px solid #34495E;background-color:#9eca23;color:#ffffff" value="浏览图片文件" onclick="select_imagefile();" />

4、具体实现(以json文件和jpg文件为例)

        假设本地计算机存在多个json文件和多个jpg文件,为了在浏览器上查看这些文件,只需将文件拖拽至浏览器中即可,但这种方式难免过于粗暴,那么下面具体以html文档为例来进行实现文件的浏览查看及保存。

4.1 实现思路

        具体的实现思路可分为如下三步

  1. 检测浏览器是否支持FileReader
  2. 保存文本数据到json文件,保存canvas数据到图片文件;
  3. 浏览本地json文件进行查看并格式化,加载本地图片文件在img标签和canvas标签同时查看。

4.2 js代码(HTML文件)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.js"></script>
    <!-- https://github.com/eligrey/FileSaver.js -->
    <style>
        #myDiv {
            background-image: url("https://img2.baidu.com/it/u=1721953618,4133119400&fm=253&fmt=auto&app=138&f=JPEG");
            /* background: url("https://img2.baidu.com/it/u=1721953618,4133119400&fm=253&fmt=auto&app=138&f=JPEG"); */
            width: 100%;
            height: 100%;
            z-index: -99;
        }
        
        .btn {
            width: 100px;
            height: 30px;
            background: green;
            border: none;
            color: white;
            margin: 6px 10px;
        }
        
        .btnStyle1 {
            border-radius: 6px;
        }
        
        .btnStyle2 {
            border-radius: 26px 6px;
        }
        
        .btnStyle3 {
            border-radius: 6px 26px 60px;
        }
        
        .btnStyle4 {
            border-radius: 6px 126px 236px 346px;
        }
        
        .bolder {
            border: solid 1px green;
            width: 500px;
            height: 40px;
            border-radius: 10px;
            /* position: absolute;
            left: 30%; */
            position: relative;
            margin-left: 35%;
        }
        
        .readFile {
            width: 100%;
            /* height:750px; */
            background: transparent;
        }
        
        pre {
            background-color: #01080fcc;
            outline: 1px solid #ccc;
            padding: 1px;
            margin: 1px;
            color: wheat;
        }
        
        .string {
            color: greenyellow;
        }
        
        .number {
            color: rgba(29, 191, 202, 0.925);
        }
        
        .boolean {
            color: #cf7814;
        }
        
        .null {
            color: rgba(108, 138, 163, 0.664);
        }
        
        .key {
            color: rgb(225, 97, 129);
        }
    </style>
</head>

<body>
    <div id="myDiv">
        <div>
            <button style="background:rgba(230, 230, 92, 0.5);width:100%;height:50px;color:white" onClick="alert('Question: Is your browser support FileReader ?'+'\n Answer:'+detect_FileReader())">检测浏览器是否支持FileReader</button>
        </div>

        <div style="background:transparent;width:100%;height:40px;font-size:large;margin-top:10px;font-size:30px;margin-bottom:30px;margin-left:800px;">
            <input style="background:transparent;border:2px solid rgb(231, 240, 240);color:rgb(74, 219, 230);height:30px;" id="website" value="http://mars3d.cn/project/vue/jcxm.html" />
            <a id="flyto" href="" target='_blank' onClick="document.getElementById('flyto').href =  document.getElementById('website').value" style="color:rgb(226, 212, 19)">百度一下</a>
        </div>
        <div class="bolder">
            <button class="btn btnStyle1" onClick="tip()">改变canvas</button>
            <button class="btn btnStyle2" onClick="saveTxt()">保存txt文件</button>
            <button class="btn btnStyle3" onClick="saveJSON()">保存json文件</button>
            <button class="btn btnStyle4" onClick="savePicture()">保存图片文件</button>
        </div>
        <div class="readFile">
            <div id="left" style="width:49%;float:left;border: blue 3px;background:transparent;border-right:3px solid salmon;">
                <div style="width:100%;">
                    <!-- json文件浏览 -->
                    <div style="width:100%;color:coral">
                        <input id="json_filename" type="text" style="width:75%;background:transparent;" />
                        <input type="file" id="up_jsonfile" name="jsonfile" style="display:none" required="required" accept="application/json, text/plain" onchange="readJSONData()" />
                        <input type="button" name="submit" style="width:15%;height:30px;border:1px solid #34495E;background-color:#5e3444;color:#ffffff" value="浏览json文件" onclick="select_jsonfile();" />
                    </div>
                    <button type="button" style="width:100%;" onclick="ToJSON()" id="convertjson">将结果转为json格式</button>
                    <div style="width:100%;height:70%;margin-top:10px;color:coral">
                        <div id="leftjsonData" style="width:49%;float:left;">
                            <p id="result" style="color:#b0a117fa;width:100%;height:100%;overflow:auto;"></p>
                        </div>
                        <div id="rightjsonData" style="width:49%;float:left;">
                            <pre id="jsonresult" class="pre" style="float:left;width:100%;height:100%;overflow:auto;"></pre>
                        </div>
                    </div>
                </div>
            </div>
            <div id="right" style="width:49%;float:left;border: red 3px;background:transparent;">
                <div style="width:100%;">
                    <!-- 图片文件浏览 -->
                    <input id="image_filename" type="text" style="width:75%;background:transparent;" />
                    <input type="file" id="up_imagefile" name="imagefile" style="display:none" required="required" accept="image/gif, image/jpg, image/png" onchange="readImageData()" />
                    <input type="button" name="submit" style="width:15%;height:30px;border:1px solid #34495E;background-color:#9eca23;color:#ffffff" value="浏览图片文件" onclick="select_imagefile();" />
                    <div>
                        <table>
                            <tr>
                                <td>
                                    <img src="" class="img-circle" style="width:300px;height:200px;" id="img">
                                </td>
                                <td>
                                    <canvas width="640" height="480" id="tCanvas" style="border:black 1px solid;z-index:999"></canvas>
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    <strong><center>img标签中的图片</center></strong>
                                </td>
                                <td>
                                    <strong><center>canvas标签中的图片</center></strong>
                                </td>
                            </tr>
                        </table>


                    </div>
                </div>
                <br>
            </div>
        </div>
    </div>
    <script>
        let canvas = document.getElementById("tCanvas");
        let context = canvas.getContext('2d');
        context.clearRect(0, 0, canvas.width, canvas.height);
        context.font = 'italic 40pt Calibri';
        context.fillStyle = "red";

        function detect_FileReader() {
            if (window.FileReader) {
                var fr = new FileReader();
                return true;
            } else {
                return false;
            }
        }

        function tip() {
            //window.alert('jjg');
            let width = 800,
                height = 800;
            canvas.width = width;
            canvas.height = height;

            context.fillRect(0, 0, width, height);
            context.globalCompositeOperation = "difference";
            // <!--          context.drawImage(image, 0, 0, width, height);-->
        }

        function saveTxt() {
            var blob = new Blob(["Hello, world!"], {
                type: "text/plain;charset=utf-8"
            });
            saveAs(blob, "hello world.txt");
        }

        function saveJSON() {
            let tableDAta = [{
                "date": "2016-05-02",
                "name": "王小虎",
                "address": "上海市普陀区金沙江路 1518 弄"
            }, {
                "date": "2016-05-04",
                "name": "王小虎",
                "address": "上海市普陀区金沙江路 1517 弄"
            }, {
                "date": "2016-05-01",
                "name": "王小虎",
                "address": "上海市普陀区金沙江路 1519 弄"
            }, {
                "date": "2016-05-03",
                "name": "王小虎",
                "address": "上海市普陀区金沙江路 1516 弄"
            }];
            let data = JSON.stringify(tableDAta, null, 3) //重点 第三参数代表缩进多少个空格
            if (document.getElementById("result").innerText) {
                data = document.getElementById("jsonresult").innerText;
                alert(data);
            }
            let blob = new Blob([data], {
                type: 'application/json;charset=utf-8'
            })
            saveAs(blob, `JJG_json.json`)
        }

        function savePicture() {
            canvas.toBlob(function(blob) {
                saveAs(blob, "pretty image.png");
            });
        }

        function gettime() {
            context.clearRect(0, 0, canvas.width, canvas.height);
            var img = document.getElementById("img");
            if (img.src != "") {
                setInterval(function() {
                    context.drawImage(img, 0, 0, canvas.width, canvas.height); //context.drawImage(img, 0, 0, 300, 200, 0, 0, canvas.width, canvas.height);
                    var a = new Date();
                    var b = a.toLocaleTimeString();
                    var c = a.toLocaleDateString();
                    context.fillText(c + " " + b, 20, 50);
                }, 20);
            } else {
                setInterval(function() {
                    var a = new Date();
                    var b = a.toLocaleTimeString();
                    var c = a.toLocaleDateString();
                    context.fillText(c + " " + b, 20, 50);
                }, 20);
            }
        }

        function select_jsonfile() {
            document.getElementById('up_jsonfile').click();
        }

        function select_imagefile() {
            document.getElementById('up_imagefile').click();
        }

        function readImageData() {
            let _this = this;
            const imageElement = document.getElementById("up_imagefile");
            const imgFile = imageElement.files[0];
            console.log(imgFile);
            let fileinfotext = '文件名:' + imageElement.value + '\n文件类型:' + getFileType(imgFile) + '\n文件大小:' + imgFile.size + 'B';
            document.getElementById('image_filename').value = fileinfotext;
            const fileReader = new FileReader();
            // console.log(imgFile);
            fileReader.readAsDataURL(imgFile);
            fileReader.onload = function() {
                document.getElementById("img").src = fileReader.result;
                console.log(img.getAttribute("src"));
                _this.gettime();
            }
        }

        function readJSONData() {
            const jsonElement = document.getElementById('up_jsonfile');
            const file = jsonElement.files[0];
            if (getFileType(file) === 'json') {
                let fileinfotext = '文件名:' + jsonElement.value + '\n文件类型:' + getFileType(file) + '\n文件大小:' + file.size + 'B';
                document.getElementById('json_filename').value = fileinfotext;
            }
            if (window.FileReader) {
                const fileReader = new FileReader();
                fileReader.readAsText(file);
                fileReader.onload = function() {
                    document.getElementById("result").innerText = fileReader.result;
                    // console.log(JSON.parse(fileReader.result));
                    document.getElementById("jsonresult").innerText = "";
                };
            }
        }

        function getFileType(file) {
            if (file.type === 'image/png' || file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/gif') {
                return 'image';
            } else if (file.type === 'application/json') {
                return 'json';
            } else if (file.type === 'text/plain') {
                return 'txt';
            } else {
                return 'other';
            }
        }
        //对json数据进行高亮的函数
        function syntaxHighlight(json) {
            if (typeof json != 'string') {
                json = JSON.stringify(json, undefined, 3);
            }
            json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
            return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function(match) {
                var cls = 'number';
                if (/^"/.test(match)) {
                    if (/:$/.test(match)) {
                        cls = 'key';
                    } else {
                        cls = 'string';
                    }
                } else if (/true|false/.test(match)) {
                    cls = 'boolean';
                } else if (/null/.test(match)) {
                    cls = 'null';
                }
                return '<span class="' + cls + '">' + match + '</span>';
            });
        }
        //将请求结果转为json格式的函数
        function ToJSON() {
            if (document.getElementById("result").innerText) {
                var Res = JSON.parse(document.getElementById("result").innerText, null, 3);
                document.getElementById("jsonresult").innerHTML = syntaxHighlight(Res);
            }
        }
    </script>
</body>
</html>

5、总结

        简而言之,相关代码和方法皆参考Github,无论是工程师或是开发者,都喜欢用自己追捧的IDE神器,目前除了Visual StudioVS Code感觉还不错外,像PyCharm、Eclipse、MyEclipse、PyCharm、IDEA、WebStorm、Spyder、SublimeText等尽管也还可以,但是对自己而言,个人觉得还是不要过于依赖这些IDE,毕竟这些IDE仅仅是利于编码的文件查看和编辑工具,开发人员万万不要被IDE所束缚,具有想法和充满热情的工程师岂能为IDE折腰?愿大家都能够摆脱集成开发环境的束缚,从而得到轻松高效的编码乐趣,用技术改变世界,拥抱开源生态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值