图片上传总结(IE9拜拜)

增加图片发送功能

一、需求

(1)点击图片 Icon,出现文件上传框,选择图片;
(2)验证图片类型及大小,本地预览的同时上传到服务器;
(3)上传完之后进行发送,同消息发送(本文不涉及)。

二、DOM节点预备

  • 图片Icon(展示,用于点击)
  • 文件上传标签 <input type="file" />(隐藏)

可选项:

  • form 表单(隐藏):如果不使用 formData,可通过表单上传
  • iframe(隐藏):如果不使用 ajax,可用于存放图片上传之后返回的数据(被称为隐藏 iframe 模拟 Ajax 跨域上传,注意 form 的 target 要写 iframe 的 name 属性)
render() {
    return (
        <div>
            <span className="picture" onClick={this.handlePicClick}></span>
            <iframe name="post_frame" style={{display: 'none'}} onLoad={this.loadIframe}></iframe>
            <form action="/upload.action" method="post" target="post_frame" encType="multipart/form-data" ref={ node => this.imgForm= node }>
                <input type="file" 
                    name="imgUpload" 
                    accept="image/gif, image/jpeg, image/png, image/bmp"
                    onChange={this.handleImgChange} 
                    style={{display: 'none'}} 
                    ref={node => this.imgUploader = node} />
            </form>
        </div>
    );
}

三、操作实现

1. 点击 图片 Icon,触发文件上传标签,打开文件上传框:
handlePicClick = () => {
    this.imgUploader.click();  // 相当于点击<input type="file />,调出文件框
}
2. 监听 <input type="file" />onchange 事件,获取上传的文件内容:
handleImgChange(e) {
    const { file, url } = this.getFile(e);  // 获取文件内容

    if (this.imgValidator(file)) {  // 验证文件内容
        this.uploadImg(file);  // 本地预览的同时上传文件
    }

    // 允许多次上传同一张图片
    e.target.value = '';
}

【注意】:如果没有最后一行代码,那么在重复上传同一张图片时,不会触发 onchange 事件,因此需要最后一行代码来保证能够允许多次上传同一张图片,解决方案来自:图片上传以及允许连续上传同一图片

3. 获取文件内容(路径,名称,类型,大小)
getFile(e) {
    let fileEle = e.target;
    let fileObj = null;
    let filePath = '';

    if (fileEle.files) {
        fileObj = fileEle.files[0];
        filePath = window.URL.createObjectURL(fileObj);
    } else {
        // 通过网搜各种兼容 IE9 的方法,尝试之后,没效果,果断放弃了。。。
        // 深深地体会到了兼容 IE9 的绝望(╥╯^╰╥)
        try {
            fileEle.select();
            fileEle.blur();
            filePath = document.selection.createRange().text;
            // filePath = fileEle.value;  // 只在本地有效
            let fileSystem = new ActiveXObject("Scripting.FileSystemObject");

            if(fileSystem.FileExists(filePath)){
                fileObj = fileSystem.GetFile(filePath);
                /* console.info("文件类型:" + fileObj.type);
                console.info("文件名称:" + fileObj.name);
                console.info("文件大小:" + fileObj.size); */
            }
        } catch (e) {
            console.log('GetFile Error:', e);
        }
    }

    return {
        file: fileObj,
        url: filePath
    };
}

非 IE9 下可以通过 e.target.files 来获取到选择的图片内容,然后生成相应的文件路径;而 IE9 获取不到 e.target.files 对象,需要特殊兼容下(谁曾想兼容之路漫漫,爬过一座山,又遇一道坎༼༎ຶᴗ༎ຶ༽)。

(1)对象 URL:性能比较好

对象 URL 也被称为 blob URL,指的是引用保存在 File 或 Blob 中数据的 URL。通过 window.URL.createObjectURL(file) 方法创建一个对象 URL,它的返回值是一个字符串,指向一块内存地址。因为这个字符串是 URL,因此能够在 DOM 中(如img标签)进行使用。

使用对象 URL 的好处是可以不必把文件内容读取到 JavaScript 中而直接引用文件内容。页面卸载时会自动释放对象 URL 占用的内存。

fileObj 和 filePath

(2)FileReader

FileReader 实现的是一种异步文件读取机制。可以把 FileReader 想象成 XMLHttpRequest,区别只是它读取的是文件系统,而不是远程服务器。为了读取文件中的数据,FileReader 提供了4个方法,这里需要用到的方法如下:

  • readAsDataURL(file):读取文件并将文件以数据 URI 的形式保存在 result 属性中

由于读取过程是异步的,FileReader 也提供了几个事件,包括:progresserrorload

【测试代码】:

let file = e.target.files[0];
let reader  = new FileReader();
reader.onload = (e) => {
    let url = e.target.reasult;
    // 可以构造img对象:
    // let img = `<img src="${url}" className="upload-img" />`;
};
// 读取file对象并存放为data: URL格式的字符串
reader.readAsDataURL(file);
e.target.result

话说一张图片转成 base64 还是挺大的,不建议进行ws传输。
FileReader 的其它方法、 progress的信息(属性) 和 error 事件的错误码,具体请参考《JavaScript高级程序设计》。

(3)IE9:在假设能获取到 input 中 text 的情况下(本地测试可以,联调环境各种问题),so,还是别在这上面浪费时间了╮(╯﹏╰)╭,跟产品求个绕,果断放弃 IE9

文件内容
4. 验证文件内容

通过步骤 3,我们可以知道上传的图片文件所包含的信息,包括 路径(url)、名称(name)、类型(type)、大小(size),因此可以对这几项进行业务上的验证。

5. 上传至服务器

(1)form 表单上传

在不使用 formData 的情况下,form 表单上传直接调用 submit 方法即可:

this.imgForm.submit();

当数据返回之后,就是 iframe 派上用场的时候啦:

// 上传图片后返回结果处理
loadIframe(e) {
    let imgInfo = this.localInfo; // 本地图片信息
    let isUpload;

    try {
        let response = e.target.contentDocument.body.textContent;
        if (!response) {
            isUpload = false;   // 上传失败
        } else {
            // 处理返回结果
            // 如果上传成功,则获取新的 imgInfo
        }
        this.handleUploadRes(isUpload, imgInfo);  // 处理图片结果
    } catch(e) {
        console.log('loadIframe Error:', e);    // iframe内容读取受限,如浏览器兼容性问题
    }
}

(2) formData 上传(IE9以下不支持)

创建 formData:

// 方法一:直接将 form 对象装载到 formData 中
let fd = new FormData(this.imgFrom);

// 方法二:选择性添加信息
let fd = new FormData();
fd.append('uploadImg', file);

发送 Ajax:

// 以下代码来自参考文章:[上传图片攻略全解],见文末参考
let xhr = new XMLHttpRequest();
xhr.open('POST','/upload.action');
xhr.onreadystatechange = function(){};
xhr.send(fd);

// 获取上传的进度
xhr.upload.onprogress = function(e){
    var loaded = e.loaded;//已经上传大小情况
    var tot = e.total;  //附件总大小
    var per = Math.floor(100*loaded/tot);  //已经上传的百分比  
}

// 或者使用框架的话:
/*ajax({
    url: '/upload.action',
    data: fd,
    type: 'post',
    success: () => {}
});*/

四、补充

  1. 考虑到真实的业务场景中是需要将图片进行发送的,这时候需要考虑到的情况是:如果图片过大,上传需要一定的时间,这时候就不能阻塞后续文本消息的发送。
上传不阻塞消息发送
  1. 论写 try...catch(e)... 的重要性,在Chrome浏览器影响不大,但是一旦调试 IE 的时候,就会明白这个语句有多重要了,它可以在产生错误的时候不让 IE 进入调试状态,也能直接在控制台看到错误结果,可以大大提高效率。

至此,聊天室的又一道难题解决啦~蟹蟹阅读哟!ヾ(✿゚▽゚)ノ

五、参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容