文件点击上传和拖拽上传

我们前端在写业务的时候,都应该都能遇到的一个场景——文件上传。

上传的话肯定是使用 Form 表单来提交的,但表单上传只能同步,即点击提交按钮之后立刻跳转到新的页面,而且提交按钮的样式也太丑了,所以工作中常用的是虚拟表单上传,即 H5 中的 FormData API,然后使用事件代理来替代提交按钮。

一、文件点击上传(事件代理)

异步提交的核心思想:就是模拟鼠标点击事件。 <input type="file" />还是文件上传的渠道,只不过我们需要加个 hidden 属性把它给隐藏掉,例: <input type="file" hidden/> 。效果就是标签仍然存在,但是我们看不见了,现在我们只需要实现,点击页面的加号图标来唤醒文件上传框。

核心代码:

onClick = {()=>{
    let evt = document.createEvent("mouseEvents");
    evt.initMouseEvent("click",false,false);
    this.refs.file.dispatchEvent(evt);
}}
模拟表单提交

源代码展示:

import React, { Component } from 'react';
import { Progress , Icon , Button} from 'antd';
import classnames from "classnames";
import axios from "axios";
export default class S1 extends Component {
    constructor(){
        super();
        this.state = {
            base64 : "",
            progress : 0
        }
    }
    render() {
        return (
            <div>   
                    <div className = "img" style = {{
                        width: "400px",
                        height: "300px",
                        textAlign:"center",
                        border: "1px dashed #457791",
                        backgroundImage:`url(${this.state.base64})`
                    }}>
                        <div>
                            <Icon type="plus-circle" style={{ fontSize: '150px', color: '#08c' , marginTop:'20px'}} onClick = {()=>{
                                let evt = document.createEvent("mouseEvents");
                                evt.initMouseEvent("click",false,false);
                                this.refs.file.dispatchEvent(evt);
                            }}/>
                        </div>
                            <Button style={{ marginTop:'40px'}} onClick = {()=>{
                            // 点击提交创建虚拟表单
                            let vform = new FormData();
                            // 追加文件
                            vform.append('file',this.refs.file.files[0]);
                            axios({
                                method: 'post',
                                url: '/api/uppic',
                                data: vform,
                                onUploadProgress: (progressEvent)=> {
                                    // ~~()取整
                                    this.setState({
                                        progress : ~~(progressEvent.loaded / progressEvent.total * 100)
                                    })
                                }
                            }).then(data=>{
                                this.setState({
                                    filename : data.data.filename
                                })
                            });
                        }}>提交</Button>
                        {/*上传进度条*/}
                        <div className = "img2">
                            <Progress className = {classnames({
                                    isHidden:this.state.progress <= 0 || this.state.progress >= 100
                                })} type="line" strokeColor = "primary" percent={this.state.progress} width = {100} />
                        </div>
                        <input type = "file" ref = "file" hidden multiple onChange = {(e)=>{
                            let file = e.target.files[0];
                            // HTML的新特性可以把图片变成base64地址
                            let fr = new FileReader();
                            fr.readAsDataURL(file);
                            fr.onload = (_e)=>{
                                this.setState({
                                    base64 : _e.target.result
                                })
                            }
                        }}/>
                    </div>
            </div>
        )
    }
}
五、文件拖拽上传

拖拽上传的思路:

  • 阻止浏览器的默认事件,拖拽图片到浏览器页面当我们松手的时候,浏览器会默认跳转打开这个图片,第一步就是阻止这步的发生,这个过程涉及到了两个事件,我们只需要阻止这两个事件就行了:
// 移动拖着不放事件
document.addEventListener("dragover",function(e){
    console.log("拖着不放事件!");
    e.preventDefault();
});
// 移动拖着放下事件
document.addEventListener("drop",function(e){
    console.log("拖着放下事件!");
    e.preventDefault();
});

阻止这两个事件,就发现拖拽图片到浏览器(其实任何文件都行)已经没有任何效果了。


阻止浏览器默认事件
  • 能召唤出文件夹,这步虚拟事件
let evt = document.createEvent("mouseEvents");
evt.initMouseEvent("click",false,false);
this.refs.file.dispatchEvent(evt);
  • 拿到放到浏览器上图片的地址
    这步使用一个事件完成。onDrop 事件, e.dataTransfer.files 是一个对象,里面包含所有拖拽上来的图片信息。

比如下面我拖拽上来的八张图片信息打印如下:


最终的实验效果:


图片拖拽效果

完整代码及注释:

import React, { Component } from 'react';

import {Icon} from "antd";

export default class S2 extends Component {
    constructor(){
        super();
        this.state = {
            arr : [],
            base64:[]
        }
    }
    componentDidMount(){
        // 移动拖着不放事件
        document.addEventListener("dragover",function(e){
            console.log("拖着不放事件!");
            e.preventDefault();
        });
        // 移动拖着放下事件
        document.addEventListener("drop",function(e){
            console.log("拖着放下事件!");
            e.preventDefault();
        });
    }
    render() {
        return (
            <div className = "box" onDrop = {(e)=>{
                // 这是个对象e.dataTransfer.files
                console.log(e.dataTransfer.files.length)
                // 拿到所有图片的地址,把他们放到数组里面好进行下步操作
                this.setState({
                    arr : [...e.dataTransfer.files]
                },()=>{
                    console.log(this.state.arr);
                    // 这步把所有的图片全部变成base64地址格式
                    // 为图片预览提供准备
                    this.state.arr.map(item => {
                        let file = item;
                        // HTML的新特性可以把图片变成base64地址
                        let fr = new FileReader();
                        fr.readAsDataURL(file);
                        fr.onload = (_e)=>{
                            this.setState({
                                base64 : [...this.state.base64,_e.target.result]
                            })
                        }
                    })
                })
            }}>
            {/*这是个加号*/}
            <Icon  onClick = {()=>{
                // 虚拟表单事件,用来打开文件夹
                let evt = document.createEvent("mouseEvents");
                evt.initMouseEvent("click",false,false);
                this.refs.file.dispatchEvent(evt);
            }} type="plus-circle" style={{ fontSize: '150px',position:"absolute",left:"50%",top:"50%",transform:"translate(-50%,-50%)"}} />
            {/*这是需要被代理的事件*/}
            <input type = "file" ref = "file" hidden multiple />
            {/*base64位的提供的地址进行预览*/}
            {
                this.state.base64.map((item,index) => <img  className = "cur" key = {index} src = {item}/>)
            }
            </div>
        )
    }
}

还有对应的 css 样式:

.box{
    width: 500px;
    height: 300px;
    background-color: #f6f6f6;
    border:1px dashed #343;
    position: relative;
}
.cur{
    width: 100px;
    margin:10px;
}
三、修复一个 bug

第二次修改心得:

上面写的拖拽效果是非常的好。但是有个 bug 很直观如下。



解决办法也很简单,去除图片的默认点击事件:

{
    this.state.base64.map((item,index) => <img onMouseDown = {(e)=>{
        e.preventDefault()
    }} className = "cur" key = {index} src = {item}/>)
}

如此,现在放在页面上的图片已经没了鼠标按下的事件,现在任你怎么拖都没效果了。

这是第三次修改:

第二次修改的方向错了,再次修正错误,我发现不是因为图片没去掉默认事件的问题。而是因为代码段多了个...this.state.arr 所以才会导致上面那个 bug 。数组应该时刻保持是新的。

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

推荐阅读更多精彩内容