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