FileReader详解
2021-02-07 15:20:50

我在Google Chrome Web Store上发布了一个案例hahaOCR,该扩展程序可以帮助用户识别出图片中的文字信息,并以文本形式显示,该扩展程序支持用户通过文件上传按钮上传图片、也支持拖拽上传,请看演示效果:

图1 - 测试图片

图2 - 测试效果

接下来,我们将在本文中详细讲解该案例所涉及的文件(图片)处理模块。

参考资料

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

1. FileReader概述

该对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用FileBlob对象指定要读取的文件或数据。其中File对象可以是来自用户在一个<input/>元素上选择文件后返回的FileList对象,也可以是来自拖拽操作生成的DataTransfer对象,还可以是来自一个HTMLCanvasElement上执行 mozGetAsFile()方法后返回的结果;Blob 对象表示一个不可变、原始数据的类文件对象,它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream来用于数据操作,Blob 表示的不一定是JavaScript原生格式的数据,File 接口基于Blob,继承了 Blob 的功能并将其扩展使其支持用户系统上的文件。

2. FileReader属性

为了更加直观的解释FileReader的三个属性,我们来看一段简单的代码:

<input type="file" id="file" onchange="readFile(this.files[0])">
window.readFile = (file) => {
   console.log(file)
   console.log(file.type)
   var reader = new FileReader()
   console.log(reader)
}

上述代码中,我们定义了一个文件上传按钮,我们只对用户上传的第一个文件进行处理,当用户完成上传操作之后,我们来看一下控制台输出的信息:

图4 - FileReader属性

我们在上述代码中指定了三个console,第一个console输出了用户所上传文件的相关信息;第二个console输出了用户所上传文件的type;第三个console输出了FileReader实例的相关信息,请看解释:

  • FileReader.error(只读属性)
    该属性表示在读取文件时发生的错误,图1中显示error值为null,表示在读取用户所上传文件时没有出错。

  • FileReader.readyState(只读属性)
    该属性表示FileReader状态的数字,请看取值:

常量名 描述
EMPTY 0 还没有加载任何数据
LOADING 1 数据正在被加载
DONE 2 已完成全部的读取请求

图1中显示readyState值为0,因为我们并没有加载任何数据,尽管我们完成了图片的上传,但我们并没有指定任何方法来读取该图片文件。

  • FileReader.result(只读属性)
    该属性表示文件的内容,仅在读取操作完成之后才有效,数据的格式取决于使用哪个方法来启动读取操作,图1中result值为null,因为我们并没有指定读取当前所上传文件的方法,接下来,我们通过FileReader.readAsDataURL()方法来读取一下该图片,请看代码:
    window.readFile = (file) => {
      var reader = new FileReader()
      console.log(reader)
      reader.readAsDataURL(file)
      reader.onload = (res) => {
          console.log(res)
      }
    }
    请看控制台信息:
图5 - readAsDataURL方法

上述代码中,我们通过FileReader.readDataAsURL()方法读取用户所上传的图片文件,当读取完成后,其result属性中将包含一个data:URL格式的Base64字符串以表示所读取文件的内容,关于读取用户所上传文件的更多方法,请参考第三小节。

3. FileReader方法

接下来,我们来看一下FileReader接口的几个方法:

方法 描述
FileReader.abort() 终止读取操作,在返回时,该属性的值为DONE
FileReader.readAsArrayBuffer() 开始读取指定的Blob中的内容,一旦完成,result 属性中保存的是被读取文件的ArrayBuffer数据对象
FileReader.readAsBinaryString() 开始读取指定的Blob中的内容,一旦完成,result属性中将包含所读取文件的原始二进制数据
FileReader.readAsDataURL() 开始读取指定的Blob中的内容,一旦完成,result属性中将包含一个data:URL格式的Base64字符串以表示所读取文件的内容
FileReader.readAsText() 开始读取指定的Blob中的内容,一旦完成,result属性中将包含一个字符串以表示所读取的文件内容

如上表所示,对于不同的文件我们可以采取不同的方法来完成读取操作,接下来,我们通过FileReader.readAsText()方法来读取一个文本,请看代码:

图6 - 文本内容
window.readFile = (file) => {
   var reader = new FileReader()
   console.log(reader)
   reader.readAsText(file)
   reader.onload = (res) => {
       console.log(res)
   }
}

请看演示效果:

图7 - readAsText方法

4. FileReader事件

我们继续来看FileReader接口的几个事件处理方法:

方法 描述
FileReader.onabort() 处理abort事件,该事件在读取操作被中断时触发
FileReader.onerror() 处理error事件,该事件在读取操作发生错误时触发
FileReader.onload 处理load事件,该事件在读取操作完成时触发
FileReader.onloadstart 处理loadstart事件,该事件在读取操作开始时触发
FileReader.onloadend 处理loadend事件,该事件在读取操作结束时(成功或失败)触发
FileReader.onprogress 处理progress事件,该事件在读取Blob时触发

如上表所示,我们可以在用户上传文件的不同阶段指定不同的处理逻辑,请看代码:

<div class="example">

    <div class="file-select">
        <label for="avatar">Choose a profile picture:</label>
        <input type="file"
               id="avatar" name="avatar"
               accept="image/png, image/jpeg">
    </div>

    <img src="" class="preview" height="200" alt="Image preview...">

    <div class="event-log">
        <label>Event log:</label>
        <textarea readonly class="event-log-contents" rows="20" cols="50"></textarea>
    </div>

</div>

上述代码中,我们定义了一个文件上传按钮,具体来说是图片上传按钮;接着我们还定义了一个<img/>标签,其src属性值为空;最后,我们定义了一个文本输入框,用来显示不同事件处理程序读取图片文件的相关信息。

const fileInput = document.querySelector('input[type="file"]');
const preview = document.querySelector('img.preview');
const eventLog = document.querySelector('.event-log-contents');
const reader = new FileReader();

function handleEvent(event) {
    console.log(event)
    eventLog.textContent = eventLog.textContent + `${event.type}: ${event.loaded} bytes transferred\n`;

    if (event.type === "load") {
        preview.src = reader.result;
    }
}

function addListeners(reader) {
    reader.addEventListener('loadstart', handleEvent);
    reader.addEventListener('load', handleEvent);
    reader.addEventListener('loadend', handleEvent);
    reader.addEventListener('progress', handleEvent);
    reader.addEventListener('error', handleEvent);
    reader.addEventListener('abort', handleEvent);
}

function handleSelected(e) {
    eventLog.textContent = '';
    const selectedFile = fileInput.files[0];
    if (selectedFile) {
        addListeners(reader);
        reader.readAsDataURL(selectedFile);
    }
}

fileInput.addEventListener('change', handleSelected);

上述代码中,我们仅对用户上传的第一个图片文件进行处理(通过readAsDataURL方法读取);我们还通过EventTarget.addEventListener方法将FileReader指定的6种监听器(事件处理程序)注册到reader上;最后当触发load事件时,将读取成功之后的内容,即data:URL格式的Base64字符串赋值给空<img/>标签的src属性,并最终显示在页面中,请看演示效果:

图8 - FileReader事件处理程序演示

5. 案例实战

最后,我们来看一下本文最开始提到的hahaOCR案例中的文件处理模块,实现该逻辑功能的思路其实很简单:用户通过按钮或拖拽操作上传图片,上传成功之后调用接口将图片中的文字信息显示在<textarea/>中,接下来,我们来看一下实现该功能的代码:

//此处是手动选择文件
$('#input').change(function() {
    event.preventDefault();
    var filesToUpload = document.getElementById('input').files;
    var img_file = [];
    for (var i = 0; i < filesToUpload.length; i++) {
        var file = filesToUpload[i];
        if (/image\/\w+/.test(file.type) && file != "undefined") {
            img_file.push(file);
        }
    }
    var reader = new FileReader();
    var AllowImgFileSize = 2100000; //上传图片最大值(单位字节)( 2 M = 2097152 B )超过2M上传失败
    var imgUrlBase64;
    if (img_file) {
        //将文件以Data URL形式读入页面
        imgUrlBase64 = reader.readAsDataURL(file);
        reader.onload = function (e) {
            if (AllowImgFileSize != 0 && AllowImgFileSize < reader.result.length) {
                alert( '上传失败,请上传不大于2M的图片!');
                return;
            }else{
                $.ajax({
                    type: 'post',
                    url: '***', //这里是数据请求接口
                    dataType: 'json',
                    data: {
                        "showapi_appid": '***', //这里需要改成自己的appid
                        "showapi_sign": '***',  //这里需要改成自己的应用的密钥secret
                        "base64":reader.result,
                    },
                    error: function(XmlHttpRequest, textStatus, errorThrown) {
                        alert("操作失败!");
                    },
                    success: function(result) {
                        var res = result.showapi_res_body.texts[0];
                        console.log(res)
                        $("#exampleFormControlTextarea1").val(res)
                    }
                });
            }
        }
    }
    hahaOCR.prototype.getImageFile(img_file, filesToUpload.length);
});

上述代码中,我们限定了用户所上传图片文件的大小,并在文件上传成功时调用接口来对所上传的图片文件进行识别,识别成功之后,将图片中文字信息显示在<textarea/>中。

6. 文章最后

以上就是本文的所有内容,小伙伴们学会了嘛?快去实践一下吧!更多详情请关注我的更多开源作品:
1. 微信公众号(hahaCoder)

图9 - 微信公众号

2. 微信小程序(hahaAI)

图10 - 微信小程序

3. Github
链接地址:https://github.com/TURBO1002

西安建筑科技大学硕士研究生,师从吴萌教授,人民邮电出版社签约作者,www.shipudong.com网站站长,hahaAI微信小程序开发者,hahaCoder微信公众号号主