To write a Vue3 based full screen drag upload component
File source at the end of the article
knowledge
- Browser drag and drop API
- Fetch request
- vue3
Long story, long story short, the html5 drag and drop API has only done a few examples of drag and drop sorting. In fact, the idea is basically the same as other drag upload components, are to specify a region can drag, and then read the file in the upload
Let’s start with the drag API, which is a new API in HTML5. When you set an element to Draggable = true, the element will support drag and drop events like the following
Ondrag Runs the script when dragging elements. Ondragstart runs the script when dragging starts. Ondragend runs the script when dragging endsCopy the code
The events for the target element are as follows:
Ondragover executes the script when the element is dragged over a valid drag target. Ondragenter executes the script when the element is dragged over a valid drag target. Ondragleave executes the script when the element is pulled over a valid drag target Run the script while the dragged element is being droppedCopy the code
Let’s say we want to listen for body drag:
const ele = document.querySelector('body')
ele.addEventListener('dragenter'.(e) = > {
// do something
})
Copy the code
And when we want to block the default event we can use e.preventDefault()
component
Let’s have a look at the effect. At this point, I have set only PNG and JPG uploads
Use:
<upload
accept=".jpg,.png,.ico" // Set the file type
@onChange="change" // File upload event
action="http://localhost:3001/upload" // Upload the address
:header="header" // Upload the header
autoUpload // Whether to upload automatically
name="file"// The name of the uploaded field
@onSuccess="onSuccess" // Upload success callback
></upload>
Copy the code
In the beginning, when I tried to get drag elements, I found that even though I added listening events, I still opened a new window to preview the file, so our first step was to disable all default events
// Disable the default drag event
function disableDefaultEvents() {
const doc = document.documentElement
doc.addEventListener('dragleave'.(e) = > e.preventDefault()) / / off
doc.addEventListener('drop'.(e) = > e.preventDefault()) / / drag after discharge
doc.addEventListener('dragenter'.(e) = > e.preventDefault()) / / in
doc.addEventListener('dragover'.(e) = > e.preventDefault()) // Drag around
}
Copy the code
Gets the root element directly, preventing the drag default event
The second step is to add the event that we want to listen for to the body or any other element. One thing to note here is that the height of the body must be the height of the window so that it will be full-screen drag, and we also need to determine if the file is being dragged out of the area when we drag it away
There’s a general judgment here, E.target. NodeName === ‘HTML’, this is used to determine whether the root element is HTML. E.target === e.xplicitOriginalTarget this is a Firefox API that determines whether the target of the two triggered events is the same
(! e.fromElement && (e.clientX <=0 ||
e.clientY <= 0 ||
e.clientX >= window.innerWidth ||
e.clientY >= window.innerHeight))
Copy the code
This is used to determine the current position of the mouse, whether it is still in the area
// Initializes the drag event
function init() {
// Get the body element
const ele = document.querySelector('body')
// Add events
/ / drag after discharge
ele.addEventListener('dragenter'.() = > {
show.value = true
})
// The mouse is dragged away
ele.addEventListener('dragleave'.(e) = > {
if (
e.target.nodeName === 'HTML'|| e.target === e.explicitOriginalTarget || (! e.fromElement && (e.clientX <=0 ||
e.clientY <= 0 ||
e.clientX >= window.innerWidth ||
e.clientY >= window.innerHeight))
) {
show.value = false}})/ / in
ele.addEventListener('drop'.(e) = > {
show.value = false
e.preventDefault()
onDrop(e) // Drag in a method to process files})}Copy the code
The third step is to process the file dragged in. Now accept is the file type defined by us. At this time, we can obtain the file dragged in by using the attribute e.datatransfer. files, and then filter the file dragged in with filter to keep only the file type we need
CheckType (File,accept) is used to determine the file type. This function is based on the filter of the upload component in element UI. At that time, I was also confused by 😂
// Check the file type
function checkType(file, accept = ' ') {
const { type, name } = file
if (accept.length === 0) return true
const extension = name.indexOf('. ') > -1 ? `.${name.split('. ').pop()}` : ' '
const baseType = type.replace(/ / /. * $/.' ')
return accept
.split(', ')
.map((type) = > type.trim())
.filter((type) = > type)
.some((acceptedType) = > {
if (/ \.. + $/.test(acceptedType)) {
return extension === acceptedType
}
if ($/ / \ \ *.test(acceptedType)) {
return baseType === acceptedType.replace($/ / \ \ *.' ')}if (/ ^ ^ /] + \ [^ /] + $/.test(acceptedType)) {
return type === acceptedType
}
})
}
Copy the code
This method is after the file dragin processing, when we get the required file is according to autoUpload to determine whether to upload
function onDrop(e) {
const accept = props.accept
const list = [].slice.call(e.dataTransfer.files).filter((file) = > {
if (accept) {
return checkType(file, accept)
}
return true
})
fileList = list.map((p) = > {
return handleStart(p)
})
// Trigger the event
onChange()
if (props.autoUpload) {
if (props.action === ' ') {
onError()
throw 'need action'
return
}
list.forEach((file) = > {
post(file) // Upload the file}}})Copy the code
The source code is as follows:
<template>
<div class="mask" v-show="show" id="mask">
<h3>Drag it here and upload it</h3>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
// import ajax from './ajax'
const props = defineProps({
name: String.// The name of the uploaded field
header: { Object.Number.String }, // The uploaded file header
// Check the file type, if there is a value, only all files will be dragged, only the filtered files will be retained
accept: {
type: String.default: ' ',},// Whether to enable automatic upload
autoUpload: {
type: Boolean.default: false,},// Upload the address
action: {
type: String.default: The '#',}})const emit = defineEmits(['onError'.'onProgress'.'onSuccess'.'onChange']) // Emit event by default
let show = ref(false) // Whether to display masks
let fileList = reactive([]) // List of files
let tempIndex = 0 // Make a mark
onMounted(() = > {
disableDefaultEvents()
init()
})
// Initializes the drag event
function init() {
const ele = document.querySelector('body')
ele.addEventListener('dragenter'.() = > {
show.value = true
}) / / drag after discharge
ele.addEventListener('dragleave'.(e) = > {
if (
e.target.nodeName === 'HTML'|| e.target === e.explicitOriginalTarget || (! e.fromElement && (e.clientX <=0 ||
e.clientY <= 0 ||
e.clientX >= window.innerWidth ||
e.clientY >= window.innerHeight))
) {
show.value = false}})/ / off
ele.addEventListener('drop'.(e) = > {
show.value = false
e.preventDefault()
onDrop(e)
}) / / in
}
// Disable the default drag event
function disableDefaultEvents() {
const doc = document.documentElement
doc.addEventListener('dragleave'.(e) = > e.preventDefault()) / / off
doc.addEventListener('drop'.(e) = > e.preventDefault()) / / drag after discharge
doc.addEventListener('dragenter'.(e) = > e.preventDefault()) / / in
doc.addEventListener('dragover'.(e) = > e.preventDefault()) // Drag around
}
// The drag event
function onDrop(e) {
const accept = props.accept
const list = [].slice.call(e.dataTransfer.files).filter((file) = > {
if (accept) {
return checkType(file, accept)
}
return true
})
fileList = list.map((p) = > {
return handleStart(p)
})
onChange()
if (props.autoUpload) {
if (props.action === ' ') {
onError()
throw 'need action'
return
}
list.forEach((file) = > {
post(file)
})
}
}
// Check the file type
function checkType(file, accept = ' ') {
const { type, name } = file
if (accept.length === 0) return true
const extension = name.indexOf('. ') > -1 ? `.${name.split('. ').pop()}` : ' '
const baseType = type.replace(/ / /. * $/.' ')
return accept
.split(', ')
.map((type) = > type.trim())
.filter((type) = > type)
.some((acceptedType) = > {
if (/ \.. + $/.test(acceptedType)) {
return extension === acceptedType
}
if ($/ / \ \ *.test(acceptedType)) {
return baseType === acceptedType.replace($/ / \ \ *.' ')}if (/ ^ ^ /] + \ [^ /] + $/.test(acceptedType)) {
return type === acceptedType
}
})
}
// Process the file list return value
function handleStart(rawFile) {
rawFile.uid = Date.now() + tempIndex++
return {
status: 'ready'.name: rawFile.name,
size: rawFile.size,
percentage: 0.uid: rawFile.uid,
raw: rawFile,
}
}
// Upload event
function post(rawFile) {
const options = {
headers: props.header,
file: rawFile,
data: props.data || ' '.filename: props.name || 'file'.action: props.action,
}
upload(options)
.then((res) = > {
res.json()
})
.then((json) = > {
onSuccess(json, rawFile)
})
.catch((err) = > {
onError(err, rawFile)
})
}
// File upload method
function upload(option) {
const action = option.action
const formData = new FormData()
if (option.data) {
Object.keys(option.data).forEach((key) = > {
formData.append(key, option.data[key])
})
}
formData.append(option.filename, option.file, option.file.name)
const headers = new Headers()
for (let item in headers) {
if(headers.hasOwnProperty(item) && headers[item] ! = =null) {
headers.append(i, option.headers[i])
}
}
return fetch(action, {
mode: 'no-cors'.body: formData,
headers: headers,
method: 'post'})},// Drag and drop to get the file list event
function onChange() {
emit('onChange', fileList)
}
// Events in upload
function onProgress(e, file) {
emit('onProgress', e, file, fileList)
}
// Upload succeeded
function onSuccess(res, file) {
emit('onProgress', res, file, fileList)
}
// Failed to upload
function onError() {
emit('onError')}</script>
<style scoped>
.mask {
top: 0;
bottom: 0;
right: 0;
left: 0;
position: fixed;
z-index: 9999;
opacity: 0.6;
text-align: center;
background: # 000;
}
h3 {
margin: -0.5 em 0 0;
position: absolute;
top: 50%;
left: 0;
right: 0;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
font-size: 40px;
color: #fff;
padding: 0;
}
</style>
Copy the code