I have always wanted to do the function of uploading pictures, and today I finally realized this wish. During the production process, I also reviewed HTML5 File, Drop, Drag, URL, and FileReader, and won all the games
To view the demo
View the source code, welcome star
Let’s review a few ways to upload files
Form form
This is the common file upload method before HTML5 came out, it is through the page form to upload, the code is as follows
<form action='upload.php' enctype='multipart/form-data' method='POST' name='form' id='form'>
<input type='file' name='image' multiple accept='image/png'/>
<button id='upload' type='submit'</button> </form>Copy the code
Note the following attributes:
- Action: URL to accept the request
- Enctype: Request encoding type, default is Application/X-www-form-urlencoded, file upload set to multipart/form-data
- Method: indicates the request method. Set this parameter to POST when uploading files
- Multiple: Allows us to select multiple files at once
- Accept: Sets the file type to be uploaded
In addition, set the type of our input tag to File, click it to open the file manager of the system, and click the upload button to send the selected file to the server
FormData & XHR2
In addition to using form forms to submit data, we can also build our own form data for submission, where FormData is used to create form data, which is HTML5 stuff, and XHR2 is used to send requests to the server
FormData object API:
- append
- delete
- set
- get
- getAll
- has
- keys
- entries
- values
- forEach
That’s how I used it in the demo
const formData = new FormData()
files.forEach((file, index) => {
formData.append(`img${index+1}`, file)
})
Copy the code
XHR2 is used to send requests and ajax implementation is based on it. It is because of XHR2 that it is possible to upload files through Ajax. XHR2 has the following characteristics compared to XHR:
- You can set timeout
- You can use FormData objects to manage data
- Binary files can be uploaded
- You can cross domain
- You can obtain the progress of data transmission
For the use of XHR2, here is a demo, and for more details you can go to MDN, portal
const formData = new FormData()
const xhr = new XMLHttpRequest()
xhr.timeout = 3000
xhr.open('POST'.'upload')
xhr.upload.onprogress = event => {
if (event.lengthComputable) {
const percent = event.loaded / event.total
console.log(percent)
}
}
xhr.onload = () => {
if (xhr.status === 200 && xhr.readyState === 4) {
alert('File uploaded successfully')}else {
alert('File upload failed')
}
}
xhr.send(formData)
Copy the code
Fetch
Fetch is a new HTTP request method, which has replaced XHR2. It is also my favorite one, because it is not too cool to write code with Promise and Async/Await. You can use IT by MDN
var form = new FormData(),
url = 'http://....... 'File = files[0]; form.append('file', file);
fetch(url, {
method: 'POST',
body: form
}).then(function(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
else {
var error = new Error(response.statusText);
error.response = response;
throw error;
}
}).then(function(resp) {
return resp.json();
}).then(function(respData) {
console.log('File uploaded successfully', respData);
}).catch(function(e) {
console.log('File upload failed', e);
});
Copy the code
Back to the topic, let’s first analyze the core functions of the DEMO I made, and then explain how to implement each function in detail. The whole DEMO was written based on React
I have divided the functions:
- Select file upload
- Image thumbnail
- Image deletion
- Drag files to upload
- Drag and drop files to delete
- Preview picture
Select file upload
Here’s how it works: click on an Input, and a file selector pops up. We can select multiple images. When the selection is complete, the change event on the Input tag is triggered
The selected image data is stored in the Input files property. The value of the Files property is an array-like FileList object, so we cannot use the Array instance method directly
The core code is as follows:
handleChange = event => {
event.preventDefault()
const { files } = this.state
Array.prototype.forEach.call(this.fileInput.files, file => {
const src = URL.createObjectURL(file)
file.src = src
this.setState({
files: [...files, file]
})
this.fileInput.value = ' '})}Copy the code
Which I pass an Array. The prototype. The forEach. Call to invoke the forEach method of the Array code one line this. Last the fileInput. Value = ‘is to solve can’t upload a photo with, Because input internally checks to see if the file we uploaded is the same as the last one, the onchange event will not be triggered if it is the same
Image thumbnail
How to upload an image and display it? I’ve looked at some of the information here, one through FileReader, the other through URLS, so let’s talk about them separately
FileReader
What is FileReader, I quote from MDN
The FileReader object lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user’s computer, using File or Blob objects to specify the file or data to read.
File objects may be obtained from a FileList object returned as a result of a user selecting files using the element, from a drag and drop operation’s DataTransfer object, or from the mozGetAsFile() API on an HTMLCanvasElement.
FileReader reads files asynchronously from your computer. You can use File or Blob objects to specify which files to read
The File object can come from a FileList object returned after the input element selects a File, from a DataTransfer object returned after the Drag and Drop operations, or from a call to mozGetAsFile() using HTMLCanvasElement
FileReader attributes, functions, and events are listed here
- attribute
- Error: indicates an error occurred during file reading. Read-only
- ReadState: 0- Data has not been loaded, 1- data is being loaded, 2- All read requests have been completed, read only
- Result: The contents of the file will have a value only after the file has been read. The format of the data depends on the method called
- function
- Abort: Interrupts the read operation and readyState changes to 2 on return
- ReadAsArrayBuffer: Reads the contents of a file and returns the format ArrayBuffer
- ReadAsBinaryString: Reads the contents of a file in binary format
- ReadAsDataURL: reads the file content and returns the format data:URL
- ReadAsText: Reads the contents of a file and returns a format string
- The event
- Onabort: Triggered when the read is interrupted
- Onerror: Triggered when a read error occurs
- Onload: Triggered when the read is complete
- Onloadstart: Triggered when reading starts
- Onloadend: Triggered at the end of the read, success or failure
- Onprogress: Triggered when the read progress changes
Now that we’ve introduced the FileReader API, let’s think about how to display a thumbnail image after uploading a file
After the file is uploaded, we get the uploaded file object and create a FileReader to read the uploaded file. After the file is successfully read, the content of the file will be saved in the Result of the FileReader. Then we create an IMG element to display the contents of the file we read
The core code is as follows:
handleChange = event => { event.preventDefault() const { files } = this.state Array.prototype.forEach.call(this.fileInput.files, file => { const reader = new FileReader() reader.readAsDataURL(file) reader.onload = event => { file.src = reader.result this.setState({ files: [...files, file] }) this.fileInput.value =' '}})}Copy the code
The img element can directly display base64 encoded images, so we call readAsDataURL when reading the file. After the file is read successfully, the result value in fileReader is the file content in data:URL format. We can assign it directly to the SRC attribute of the IMG element. The onload event is triggered when the file is successfully read, so all our operations must be written in its callback function
URL
If you have any questions, I need to read my file locally, convert it to a base64 long string, but cannot provide an address directly to the SRC attribute of the IMG element. The answer is yes, we can do it by using URL objects, and that’s the way I recommend it
Let’s take a look at the URL object, which is described in MDN
The URL interface represents an object providing static methods used for creating object URLs.
When using a user agent where no constructor has been implemented yet, it is possible to access such an object using the Window.URL properties (prefixed with Webkit-based browser as Window.webkitURL).
The URL interface is used to create URL objects and provides static methods
When our environment does not implement the URL constructor, we can return an instance of the URL via window.url (window.webkitURL)
RevokeObjectURL revokeObjectURL createObjectURL revokeObjectURL createObjectURL revokeObjectURL createObjectURL revokeObjectURL
CreateObjectURL passes in a File or Blob object and returns a DOMString that can be used to display our content
RevokeObjectURL destroys the DOMString created by createObjectURL
So how do you do that? Look directly at the code
handleChange = event => {
event.preventDefault()
const { files } = this.state
Array.prototype.forEach.call(this.fileInput.files, file => {
const src = URL.createObjectURL(file)
file.src = src
this.setState({
files: [...files, file]
})
this.fileInput.value = ' '})}Copy the code
Const SRC = url.createObjecturl (file) const SRC = url.createObjecturl (file); const SRC = url.createObjecturl (file)
CreateObjectURL the returned string looks like: a blob: http://localhost:3000/81e8eaa9-3041-4c93-bd16-913f578ece42
Some other attributes and methods about URL are not listed in this demo. Interested students can take MDN to understand the portal
Image deletion
Image delete this function is very simple, click the delete button above the picture, pass in the corresponding index to delete method, and then find the corresponding file in the global files object according to index filter, return a new array
The core code is as follows:
handleDelete = event => {
event.preventDefault()
event.stopPropagation()
const { target: { dataset: { index } } } = event
const { files } = this.state
const newFiles = files.filter((file, index2) => {
if (index2 === +index) {
URL.revokeObjectURL(file.src)
return false
}
return true
})
this.setState({
files: newFiles
})
}
Copy the code
RevokeObjectURL revokeObjectURL revokeObjectURL revokeObjectURL revokeObjectURL revokeObjectURL revokeObjectURL revokeObjectURL
Drag and drop file upload & Drag and drop file delete
Drag file upload and Drag file delete are put together because they both require the Drag and Drop apis provided by HTML5. Let’s take a look at these two apis first
Drag and drop event
Some drag-and-drop events are triggered on the element being dragged, while others are triggered on the drop target
When we drag an element, it fires in sequence:
- dragstart
- drag
- dragend
All three events are triggered on the dragged element. The dragStart event is triggered when the drag starts, and the drag event continues to be triggered during the drag process. The dragend event is triggered when the drag stops (regardless of whether the dragged element has been placed on a valid drop target). These three events are similar to the mouse movement events mouseStart, Mousemove, and mousemove. mouseend
When an element is dragged to the drop target, it fires in sequence:
- dragenter
- dragover
- Dragleave or drop
All three events are triggered on the placement target. A DragEnter event is emitted when an element enters a drop target, a dragover event is emitted continuously when an element moves on a drop target, a Dragleave event is emitted when an element is placed on a drop target, These events (except drop) are similar to the mouse movement events mouseEnter, mouseover, and mouseleave
Block default behavior. Although all elements support drop, these elements are not allowed to be placed by default. In this case, releasing the mouse over the drop target will not trigger the drop, so we can prevent the default by event.preventDefault() :
droptarget.ondragenter = event => {
event.preventDefault()
}
droptarget.ondragover = event => {
event.preventDefault()
}
Copy the code
In some browsers, when we move an image to the placement target, releasing it opens the image, or if we move a hyperlink, the page opens. We sometimes need to prevent this default behavior, and we can do that
droptarget.ondrop = event => {
event.preventDefault()
}
Copy the code
The dataTransfer object
The dataTransfer object, which is used to pass data from the dragged element to the placed target during a drag, has two methods, setData and getData
SetData takes two parameters, the first is the MIME type and the second is the value we want to save
event.dataTransfer.setData('text/plain'.'msg')
event.dataTransfer.setData('text/uri-list'.'http://baidu.com')
Copy the code
GetData has only one argument, which is the first argument we pass in setData
event.dataTransfer.getData('text/plain')
event.dataTransfer.setData('text/uri-list')
Copy the code
Keep in mind that setData is usually used in dragstart and getData is only used in drop events
dropEffect & effectAllowed
DropEffect and Effectalhoward are two attributes of the dataTransfer that determine what actions can be received by the dragged element and by the placed element
DropEffect must be paired with Effectalhoward to be effective, and we must set the values of these two properties in dragstart
Effectalhoward has the following values:
- uninitialized
- none
- copy
- link
- move
- copyLink
- copyMove
- linkMove
- all
The values of dropEffect are as follows:
- none
- move
- copy
- link
draggable
Elements other than images, links, and text are not dragable by default. We need to add the draggable attribute to make this element dragable
Other members
In addition to the above methods and attributes, dataTransfer has the following methods and attributes:
- addElement(element)
- clearData(format)
- setDragImage(element, x, y)
- types
We get dataTransfer in onDrop, store the file in its files property, and then use FormData object and XHR2 to transfer the data to the server. The core code is as follows:
handleDrop = event => {
event.preventDefault()
event.stopPropagation()
const { files } = this.state
Array.prototype.forEach.call(event.dataTransfer.files, file => {
const src = URL.createObjectURL(file)
file.src = src
this.setState({
files: [...files, file]
})
this.fileInput.value = ' '
})
}
handleUpload = event => {
event.preventDefault()
const { files } = this.state
if (files.length === 0) {
this.fileInput.click()
return
}
const formData = new FormData()
files.forEach((file, index) => {
formData.append(`img${index+1}', file)}) // xhr2 upload file or fetch const XHR = new XMLHttpRequest() xhr.timeout = 3000 xhr.open()'POST'.'upload')
xhr.upload.onprogress = event => {
if (event.lengthComputable) {
const percent = event.loaded / event.total
console.log(percent)
}
}
xhr.onload = () => {
if (xhr.status === 200 && xhr.readyState === 4) {
alert('File uploaded successfully')}else {
alert('File upload failed')
}
}
// xhr.send(formData)
alert('File uploaded successfully')
this.setState({
files: []
})
this.fileInput.value = ' '
}
Copy the code
Implementation idea of drag file deletion: in onDragstart, put the index of the dragged file into dataTransfer, and then take out the index in onDrop, and delete it in the global file list according to the index value. The core code is as follows:
handleDustDrop = event => {
event.preventDefault()
const { dataTransfer } = event
const index = dataTransfer.getData('text/plain')
const { files } = this.state
let deleteFile
const newFiles = files.filter((file, index2) => {
if (index2 === +index) {
deleteFile = file.name
URL.revokeObjectURL(file.src)
return false
}
return true
})
this.setState({
files: newFiles,
deleteFile
})
event.currentTarget.style.borderColor = '#cccccc'
}
Copy the code
Preview picture
The image preview function is also very simple, similar to deleting. Click the corresponding image to pass in index, then find the corresponding file from the global files, and assign its SRC attribute value to the SRC attribute of a preview IMG element
The core code is as follows:
showPreview = event => {
const { currentTarget: { dataset: { index } } } = event
const { files } = this.state
this.setState({
preview: true,
previewImg: {
name: files[+index].name,
src: files[+index].src
}
})
}
hidePreview = event => {
this.setState({
preview: false,
previewImg: null
})
}
Copy the code
After the image is displayed, bind a click event to the outer container. Click to hide the preview image
The last
To view the demo
View the source code, welcome star
Your rewards are my motivation to write