• 从0开始做一个的Vue图片/ 文件选择(上传)组件[基础向]


    原文:http://blog.csdn.net/sinat_17775997/article/details/58585142

    之前用Vue做了一个基础的组件 vue-img-inputer ,下面就叫 vii ,记录下在开发过程中遇到的 知识点 (都算比较基础,具体代码不会贴太多,都可以在 项目 仓库里看到)。

    上传文件很多项目都要用到,一些组件库里(ele/iview...) 文件上传组件 都是做成了标配,虽然 vii 和 uploader 定位还是有些差别,但实现上都有几个共同要点:

    1. 样子要好看点

    2. 图片/文件选择后预览

    3. 实现拖拽选择文件

    4. 图片选择后执行某些动作(譬如uploader的上传等)

    先上 demo

    注: 下面有些地方会有些啰嗦,请选择观看

    基础

    首先我们有个文件选择框,恩,长这样:

    好丑啊!!我们来让它变好看点:

    第一个方法:修改自身CSS

    这里有一个 shadowDOM 的概念,简单的来说就是我们经常用到的一些HTML标准组件(例如viedo ,甚至 滚动条 )其实是由若干个更基础的DOM由浏览器封装成的,使得我们调用只要一个标签就够了,这类也就是 WebComponent ,这里具体不展开了。我们先来看下file-input的内部是如何的(chrome devtool不 设置 是看不到的):

    所以呢,直接给file-input修改样式这个按钮会一直存在的!我们要么把按钮移出视线,要么就用这个按钮修改其样式。这里就修改下里面这个type=button的样式,只提供个思路,代码:

    <input type="file"/>
    <style>
        input {
            font-size: 0; /* 为了去掉‘未选择任何文件’这几个字,也可以随便弄到哪里*/
        }
        /* 注意不是直接input > input[type=button] 哦*/
        input::-webkit-file-upload-button {
            background: #efeeee;
            color: #333;
            border: 0;
            padding: 40px 100px;
            border-radius: 5px;
            font-size: 12px;
            box-shadow: 1px 1px 5px rgba(0,0,0,.1), 0 0 10px rgba(0,0,0,.12);
        }
    </style>

    有没有想到chrome修改滚动条样式呢?哈哈,其实是一个道理,现在file-input变这样了:

    好像挺简单!然而我们看到 -webkit- 就应该知道兼容性了,这种方法只有safari和chrome笑笑,其他GG,所以自然不能这么干。

    第二个方法:给file-input找个替身

    是这样,我们可以可以把file-input整个移出视线,再找个找几个元素,通过点击这些个元素来代理原file-input的点击,呼出文件选择框呢?

    自然是可以的, label 标注标签, 给label一个 for 属性指向input的唯一 id ,这样点击label就相当于点击input, 所以我们可以这么写:

    <div class="box">
      <input id="id" type="file" />
      <label for="id"></label>
      <!-- other element-->
    </div>
    .box {
        position: relative;
    }
    input {
        position: absolute;
        left: -9999px;
    }    
    /* 使label充满整个box*/
    label {
        position: absolute;
        top: 0;left: 0;right: 0;bottom: 0;
        z-index: 10; /* 这个z-index之后说到*/
    }

    这样子做之后,就有一个组件的影子了,其中

    • 因为 label 充满了整个box,所以点击box就可以选择文件

    • 同时有了box,可以往里面填充任何元素,譬如一个icon

    <div class="box">
      <input id="id" type="file" />
      <label for="id"></label>
      <i class="iconfont">:)</i>
      <!--  ...发挥你的想象力-->
    </div>

    好了,基础基本上啰嗦完了,正式进入vue的实现(Vue 2.x):

    文件选择的处理

    这块讲 文件数据 的获取和处理:

    v-model

    如果问你vue里你想要组件绑定一个输入值的最粗暴的方式是什么? v-model 啊!但是这条指令其实是一个语法糖:

    <imgInputer v-model="target"></imgInputer>
    <!-- 默认等同于下面几行-->
    <imgInputer ref="x" :value="target"></imgInputer>  
    <script>
        ...
        // 默认给这个组件对象绑定input事件!
        this.$refs.x.$on('input', value => {this.target = value})
        ...
    </script>

    所以文件选择传值的实现方式:

    <template>
        <div>
          <input @change="handleFileChange" ref="inputer" .../>
          ...
        </div>
    </template>
    <script>
        ...
        props: {
            value: {
                // 绑定默认的value prop
                default: undefined
            },
        },
        ...
        // input的change回调第一个参数是event对象
        methods: {
            handleFileChange (e) {
                let inputDOM = this.$refs.inputer;
                // 通过DOM取文件数据
                this.file    = inputDOM.files[0];
                this.errText = '';
        
                let size = Math.floor(this.file.size / 1024);
                if (size > ...) {
                    // 这里可以加个文件大小控制
                    return false
                }
        
                // 触发这个组件对象的input事件
                this.$emit('input', this.file);
                
                // 这里就可以获取到文件的名字了
                this.fileName = this.file.name;
                
                // 这里加个回调也是可以的
                this.onChange && this.onChange(this.file, inputDOM.value);
          
            },
        }
        ...
    </script>
    <!-- 调用-->
    <imgInputer v-model="target"></imgInputer>

    这样选中的文件就会传给target了,接着说图片预览

    图片预览

    思路有两种:

    1. 选择文件后直上传然后得到网络url

    2. 用HTML5的 File API 的 FileReader 图片本地转成base64格式的url

    然后将url赋值给一个img标签

    我们这里肯定选择第二种,所以先介绍下:

    FileReader

    照例贴 MDN文档先 ,然后是代码:

    <template>
        <div ref="box">
          ...
          <input ... />
          // 给个img来承担预览工作就行了
          <img :src="dataUrl" />
          ...
        </div>
    </template>
    <sctipt>
        data () {
            return {
                // 转base64码后的data字段
                dataUrl: ''
            }
        },
        methods: {,
            imgPreview (file) {
                let self = this;
                // 看支持不支持FileReader
                if (!file || !window.FileReader) return;
        
                if (/^image/.test(file.type)) {
                    // 创建一个reader
                    var reader = new FileReader();
                    // 将图片将转成 base64 格式
                    reader.readAsDataURL(file);
                    // 读取成功后的回调
                    reader.onloadend = function () {
                        self.dataUrl = this.result;
                    }
                }
            },
            handleFileChange (e) {
                ...
                this.file = inputDOM.files[0];
                ...
                // 在获取到文件对象进行预览就行了!
                this.imgPreview(this.file);
                ...
            }
        }
    </script>

    当然了,这东西的兼容性有点捉鸡: IE10+, 移动端可以快乐的使用。

    预览就这么完成了,下一个我们来说拖拽!

    拖拽选择

    浏览器拖拽事件

    首先,放 DragEVent 的 MDN文档 ,重点是下面四个事件及解释:

    • dragenter 
      当拖动的元素或选择文本输入有效的放置目标时,会触发此事件。

    • dragleave 
      当拖动的元素或文本选择离开有效的放置目标时,会触发此事件。

    • dragover 
      当将元素或文本选择拖动到有效放置目标(每几百毫秒)上时,会触发此事件。

    • drop 
      当在有效放置目标上放置元素或选择文本时触发此事件。

    以及dataTransfer对象:在拖放交互期间传输的数据。

    获取方法: event.dataTransfer

    为什么要关注着几个呢?因为 浏览器是自身监听这几个拖放事件的 !!譬如你把图片或者pdf拖进浏览器里。浏览器是会试图打开这个文件的,所以我们要干掉默认行为,很简单 e.preventDefault() :

    ...
    methods: {
        preventDefaultEvent (eventName) {
            document.addEventListener(eventName, function (e) {
                e.preventDefault();
            }, false)
        },
    },
    mounted () {
        // 阻止浏览器默认的拖拽时事件,测试阻止这几个就够了,不放心就全阻止一遍吧
        ['dragleave', 'drop', 'dragenter', 'dragover'].forEach(e => {
            this.preventDefaultEvent(e);
        });
    }
    ...

    做完这一步,我们只需监听目标上的 drop 事件就行了,稍微改造下代码:

    <template>
        <div ref="box">
          ...
        </div>
    </template>
    <script>
        ...
        addDropSupport () {
            let BOX = this.$refs.box;
            BOX.addEventListener('drop', (e) => {
                e.preventDefault();
                this.errText = '';
                // 上面给的MDN文档里有讲到dataTransfer承载拖拽数据
                let fileList = e.dataTransfer.files; // 其实这就是文件对象列表了
                // 总得拖一个文件吧
                if (fileList.length === 0) {
                    return false
                }
                // 格式限制
                if (fileList[0].type.indexOf('image') === -1) {
                    this.errText = '请选择图片文件';
                    return false;
                }
                // 这次限制下只能拖一个文件
                if (fileList.length > 1) {
                    this.errText = '暂不支持多文件';
                    return false
                }
                this.handleFileChange(null, fileList[0]);
            })
        },
        // 加入第二个参数
        handleFileChange (e, FILE) { 
            // 数据赋值改动,这样就兼容两种选择方式了
            this.file = FILE || inputDOM.files[0];
        }
        ...
    </script>

    其实到这里重要的点都讲了,接下来说些其他的

    上传

    • uploader 的话选择完图片在 handleFileChange 里直接执行个请求上传

    • 在父组件里获取值该怎么传怎么传

    其他一些东西

    • 当页面中需要多个 inputer 时,同一个input的id会冲突,所以不指定的情况下需要个唯一id:

    <template>
        ...vue
        <input :id="inputId" ... />
        ...
    </template>
    <script>
    ...
    methods: {
        gengerateID () {
            let nonstr = Math.random().toString(36).substring(3, 8);
            if (!document.getElementById(nonstr)) {
                return nonstr
            } else {
                return this.gengerateID()
            }
        },
    },
    mounted () {
        this.inputId = this.id || this.gengerateID();
    }    
    ...
    </script>
    • input原本可以指定接收的文件格式,会在选择框出来的时候默认无法选择非指定格式的文件

    <!-- accept属性-->
    <input accept="image/*,video/*;" .../>
    • 移动端允许拍照选择

    <!-- capture属性-->
    <input capture="video" .../>

    最后

    • 暂时就这么多了,完整的 源码在这里

    • 有任何讲的不对不好的地方请大力指正!

  • 相关阅读:
    Maven命令行使用:mvn clean package(打包)
    快速构建 Spring Boot 应用
    Spring MVC 表单处理
    Spring Web Hello World 例子
    Spring 事务管理
    开发环境搭建
    考研计算机专业课练习题
    考研计算机自测练习
    考研计算机自测练习答案
    考研计算机专业常见术语
  • 原文地址:https://www.cnblogs.com/zzcit/p/7743841.html
Copyright © 2020-2023  润新知