I used Vue to make a basic component vuE-IMg-inputer, which is called VII below, and recorded the knowledge points encountered in the development process (all are relatively basic, and the specific codes will not be posted too much, which can be seen in the project warehouse).

Upload file many projects need to use, some component library (ele/iview…) The file upload components are standard, and while VII and Uploader are somewhat different in orientation, they both share a few common features:

  1. Look good

  2. Preview images/files after selection

  3. Implement drag and drop to select files

  4. Perform some action after image selection (upload to Uploader, etc.)

On the firstdemo

Note: Some places below will be a little wordy, please choose to watch

basis

First we have a file selection box that looks like this:

How ugly!! Let’s make it nice:

The first method: modify your CSS

There is a concept of shadowDOM here, which simply means that some of the standard HTML components we often use (such as viedo and even scrollbars) are actually made up of several more basic DOM wrapped by the browser, which allows us to call only one tag, namely WebComponent. I’m not going to do that. Let’s take a look at what file-input looks like inside (not visible without chrome devTool) :

So, styling file-Input directly will always be there! We can either move the button out of sight or use it to style it. Type =button type=button type=button



<input type="file"/>
<style>
    input {
        font-size: 0; /* To remove the word 'not selected for any file', can also be arbitrarily placed */
    }
    /* 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>Copy the code

Have you ever thought of chrome changing scrollbar styles? Now file-input looks like this:

Sounds simple! However, when we look at – WebKit -we should know about compatibility. This method is only available in Safari and Chrome, but not in other areas.

The second method: find a proxy for file-input

Well, what if we move file-input out of sight, find a few more elements, and click on those elements to substitute the original file-input click-to-call file selection box?

Label labels the label, give label a for attribute pointing to the unique ID of the input, so clicking on label is the same as clicking on input, so we can say:



<div class="box">
  <input id="id" type="file" />
  <label for="id"></label>
  <! -- other element-->
</div>
Copy the code


.box {
    position: relative;
}
input {
    position: absolute;
    left: -9999px;
}    
/* Make the label fill the entire box*/
label {
    position: absolute;
    top: 0;left: 0;right: 0;bottom: 0;
    z-index: 10; /* The z-index comes after */
}Copy the code

When you do that, you have the shadow of a component where

  • Because label fills the entire box, clicking on box selects the file

  • And with box, you can fill it with any element, like an icon



<div class="box">
  <input id="id" type="file" />
  <label for="id"></label>
  <i class="iconfont">:)</i>
  <! -... Use your imagination -->
</div>
Copy the code

Vue 2.x (vue 2.x)

File selection processing

This section talks about the acquisition and processing of file data:

v-model

What is the roughest way you can bind a value to a component in VUE? V – model! This command is actually a syntactic sugar:



<imgInputer v-model="target"></imgInputer>
<! -- defaults to the following lines -->
<imgInputer ref="x" :value="target"></imgInputer>  
<script>.// Bind the input event to this component object by default!
    this.$refs.x.$on('input', value => {this.target = value})
    ...
</script>Copy the code

So the file selects the implementation of the value transfer:



<template>
    <div>
      <input @change="handleFileChange" ref="inputer" ./>.</div>
</template>
<script>. props: { value: {// Bind the default value prop
            default: undefined}},...// The first argument to the input change callback is an event object
    methods: {
        handleFileChange (e) {
            let inputDOM = this.$refs.inputer;
            // Retrieve file data via DOM
            this.file    = inputDOM.files[0];
            this.errText = ' ';
    
            let size = Math.floor(this.file.size / 1024);
            if (size > ...) {
                // Add a file size control
                return false
            }
    
            // Fires the input event of this component object
            this.$emit('input'.this.file);
            
            // Get the name of the file
            this.fileName = this.file.name;
            
            // Add a callback here
            this.onChange && this.onChange(this.file, inputDOM.value); }}...</script>Copy the code


<! - call - >
<imgInputer v-model="target"></imgInputer>Copy the code

The selected file will then be passed to Target. Image preview

Preview picture

There are two ideas:

  1. Select the file and upload it directly to get the web URL

  2. FileReader images using HTML5’s File API are locally converted to Base64 urls

The URL is then assigned to an IMG tag

Definitely choose the second option

FileReader

As usual, paste the MDN document first, then the code:



<template>
    <div ref="box">... <input ... />// Give an img to do the preview work
      <img :src="dataUrl" />
      ...
    </div>
</template>
<sctipt>
    data () {
        return {
            // Convert base64 code to data field
            dataUrl: ' '
        }
    },
    methods: {,
        imgPreview (file) {
            let self = this;
            // Check whether FileReader is supported
            if(! file || ! window.FileReader)return;
    
            if (/^image/.test(file.type)) {
                // Create a reader
                var reader = new FileReader();
                // Convert the image to base64 format
                reader.readAsDataURL(file);
                // Callback after successful reading
                reader.onloadend = function () {
                    self.dataUrl = this.result;
                }
            }
        },
        handleFileChange (e) {
            ...
            this.file = inputDOM.files[0]; .// Get the file object and preview it!
            this.imgPreview(this.file); . } } </script>Copy the code

Of course, the compatibility of this stuff is a bit tricky: IE10+, mobile can be used happily.

That’s the preview done. Next, drag and drop!

Drag to select

Browser drag events

First, put the MDN document of DragEVent, focusing on the following four events and their explanation:

  • Dragenter Raises this event when the dragged element or selection text enters a valid drop target.

  • Dragleave This event is emitted when the dragged element or text chooses to leave a valid drop target.

  • Dragover This event is triggered when an element or text selection is dragged to a valid drop target every few hundred milliseconds.

  • Drop Triggers this event when an element is placed or text is selected on a valid drop target.


And dataTransfer objects: data transferred during drag-and-drop interactions. Obtain this method: event.datatransfer

Why focus on a few? Because the browser itself is listening for several drag-and-drop events!! Say you drag an image or PDF into your browser. Browsers will try to open this file, so we’ll disable the default behavior, which is very simple e.preventDefault() :



. methods: { preventDefaultEvent (eventName) {document.addEventListener(eventName, function (e) {
            e.preventDefault();
        }, false)
    },
},
mounted () {
    // Block the browser's default drag-and-drop events
    ['dragleave'.'drop'.'dragenter'.'dragover'].forEach(e => {
        this.preventDefaultEvent(e); }); }...Copy the code

To do this, we just need to listen for the drop event on the target and modify the code slightly:



<template>
    <div ref="box">.</div>
</template>
<script>. addDropSupport () {let BOX = this.$refs.box;
        BOX.addEventListener('drop', (e) => {
            e.preventDefault();
            this.errText = ' ';
            // dataTransfer carries drag data
            let fileList = e.dataTransfer.files; // This is a list of file objects
            // You have to drag a file
            if (fileList.length === 0) {
                return false
            }
            // Format restriction
            if (fileList[0].type.indexOf('image') = = = -1) {
                this.errText = 'Please select image file';
                return false;
            }
            // Only one file can be dragged this time
            if (fileList.length > 1) {
                this.errText = 'Multiple files are not supported yet';
                return false
            }
            this.handleFileChange(null, fileList[0]); })},// Add a second argument
    handleFileChange (e, FILE) { 
        // Data copy changes, so that both options are compatible
        this.file = FILE || inputDOM.files[0]; }...</script>Copy the code

I’ve actually covered all the important points here, but let’s move on to the others

upload

  • Uploader to select the image in the handleFileChange directly perform a request upload

  • How do I pass values from the parent component

Something else

  • When multiple inputers are required on a page, the id of the same input will conflict, so a unique ID is required if no input id is specified:



<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>Copy the code
  • Input can specify the format of the file to be received, but the selection box will default to a non-specified file format



<! -- Accept attribute -->
<input accept="image/*,video/*;" ./>Copy the code
  • Mobile allows photo selection



<! -- Capture attribute -->
<input capture="video" ./>Copy the code

The last

  • That’s all for now, the complete source code is here

  • Have any say wrong bad place please point out energetically!