基于Vue3的全屏拖拽上傳組件
本文主要介紹了基于Vue3的全屏拖拽上傳組件,分享給大家,具體如下:
知識點
- 瀏覽器拖拽 api
- fetch 請求
- vue3
說來話長,長話短說,關于 html5 的拖拽 api 也只是做過一些拖拽排序的例子.其實思路上與其他拖拽上傳組件基本一樣,都是指定一個區(qū)域可拖拽,然后讀取文件在上傳
先說說拖拽 api,這個是 html5 新增的一個 api,給一個元素設置 draggable = true 屬性時,該元素就會支持拖拽
拖拽元素事件如下
1. ondrag 當拖動元素的時候運行腳本
2. ondragstart 當拖動操作開始時候運行腳本
3. ondragend 當拖動操作結束的時候運行腳本
目標元素的事件如下:
1. ondragover 當元素被拖動至有效拖放目標上方時執(zhí)行腳本
2. ondragenter 當元素被拖動至有效拖動目標時執(zhí)行腳本
3. ondragleave 當元素離開至有效拖放目標是運行腳本
4. ondrop 當被拖動元素正在被放下的時候運行腳本
比如我們想監(jiān)聽 body 的拖拽:
const ele = document.querySelector('body') ele.addEventListener('dragenter', (e) => { // do something })
而當我們想要阻止默認事件的時候我們可以用 e.preventDefault()
組件
先看一下效果,此時我這里是設置的僅能上傳 png 與 jpg
使用:
<upload accept=".jpg,.png,.ico" // 設置文件類型 @onChange="change" // 文件上傳事件 action="http://localhost:3001/upload" // 上傳地址 :header="header" // 上傳的header autoUpload // 是否自動上傳 name="file"http:// 上傳的字段名 @onSuccess="onSuccess" // 上傳成功回調 ></upload>
最開始的時候我想獲取拖拽元素的時候莫名發(fā)現盡管加了監(jiān)聽事件,可還是會打開新的窗口去預覽文件,所以我們第一步就是先把默認事件都給禁用掉
// 禁用默認拖拽事件 function disableDefaultEvents() { const doc = document.documentElement doc.addEventListener('dragleave', (e) => e.preventDefault()) //拖離 doc.addEventListener('drop', (e) => e.preventDefault()) //拖后放 doc.addEventListener('dragenter', (e) => e.preventDefault()) //拖進 doc.addEventListener('dragover', (e) => e.preventDefault()) //拖來拖去 }
直接獲取根元素,阻止拖拽的默認事件
第二步就是我們給 body 或是其他元素加上我們想要監(jiān)聽的事件,這里有一個注意的是 body 的高度一定是窗口的高度,這樣才會全屏拖拽,在拖離的時候我們還要判斷一下文件是否被拖出區(qū)域
這里一共有這么判斷,
e.target.nodeName === 'HTML',這個用來判斷根元素是不是 html e.target === e.explicitOriginalTarget 這個是火狐特有的一個 api,判斷這兩個觸發(fā)事件的目標是否一致 (!e.fromElement && (e.clientX <= 0 || e.clientY <= 0 || e.clientX >= window.innerWidth || e.clientY >= window.innerHeight))
這個是用來判斷鼠標當前的位置的,是否還在區(qū)域內
// 初始化拖入事件 function init() { // 獲取body元素 const ele = document.querySelector('body') // 添加事件 //拖后放 ele.addEventListener('dragenter', () => { show.value = true }) // 這里判斷鼠標拖離 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 } }) //拖進 ele.addEventListener('drop', (e) => { show.value = false e.preventDefault() onDrop(e) // 拖入處理文件的方法 }) }
第三步是處理拖入的文件,此時 accept 是我們定義的文件類型,此時我們用e.dataTransfer.files這個屬性可以獲得拖入的文件,
然后我們把拖入的文件用 filter 做一個過濾,只保留我們需要的文件類型
checkType(file,accept)就是用來判斷文件類型的,這一個函數是借鑒了 element ui 里面的上傳組件的篩選,當時我也是寫蒙了我 😂
// 檢查文件類型 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 } }) }
這個方法是文件拖入之后的處理,當我們獲得需要的文件之后就是根據autoUpload來判斷一下是否上傳
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) }) // 觸發(fā)事件 onChange() if (props.autoUpload) { if (props.action === '') { onError() throw 'need action' return } list.forEach((file) => { post(file) // 上傳文件 }) } }
源碼如下:
<template> <div class="mask" v-show="show" id="mask"> <h3>拖拽到這里上傳</h3> </div> </template> <script setup> import { ref, reactive, onMounted } from 'vue' // import ajax from './ajax' const props = defineProps({ name: String, // 上傳的字段名 header: { Object, Number, String }, // 上傳的文件頭 // 驗證的文件類型,有值的時候只會拖入所有的文件只會保留設置過濾后的文件 accept: { type: String, default: '', }, // 是否開啟自動上傳 autoUpload: { type: Boolean, default: false, }, // 上傳地址 action: { type: String, default: '#', }, }) const emit = defineEmits(['onError', 'onProgress', 'onSuccess', 'onChange']) // 默認emit事件 let show = ref(false) // 是否展示遮罩 let fileList = reactive([]) // 文件列表 let tempIndex = 0 // 做一個標記 onMounted(() => { disableDefaultEvents() init() }) // 初始化拖入事件 function init() { const ele = document.querySelector('body') ele.addEventListener('dragenter', () => { show.value = true }) //拖后放 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 } }) //拖離 ele.addEventListener('drop', (e) => { show.value = false e.preventDefault() onDrop(e) }) //拖進 } // 禁用默認拖拽事件 function disableDefaultEvents() { const doc = document.documentElement doc.addEventListener('dragleave', (e) => e.preventDefault()) //拖離 doc.addEventListener('drop', (e) => e.preventDefault()) //拖后放 doc.addEventListener('dragenter', (e) => e.preventDefault()) //拖進 doc.addEventListener('dragover', (e) => e.preventDefault()) //拖來拖去 } // 拖入時的事件 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) }) } } // 檢查文件類型 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 } }) } // 處理文件列表返回值 function handleStart(rawFile) { rawFile.uid = Date.now() + tempIndex++ return { status: 'ready', name: rawFile.name, size: rawFile.size, percentage: 0, uid: rawFile.uid, raw: rawFile, } } // 上傳的事件 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) }) } // 文件上傳方法 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', }) } // 拖拽進去獲取文件列表的事件 function onChange() { emit('onChange', fileList) } // 上傳中的事件 function onProgress(e, file) { emit('onProgress', e, file, fileList) } // 上傳成功事件 function onSuccess(res, file) { emit('onProgress', res, file, fileList) } // 上傳失敗事件 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.5em 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>
到此這篇關于基于Vue3的全屏拖拽上傳組件的文章就介紹到這了,更多相關Vue3 全屏拖拽上傳內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue+elementUI組件遞歸實現可折疊動態(tài)渲染多級側邊欄導航
這篇文章主要介紹了vue+elementUI組件遞歸實現可折疊動態(tài)渲染多級側邊欄導航,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04淺談vue 組件中的setInterval方法和window的不同
這篇文章主要介紹了淺談vue 組件中的setInterval方法和window的不同,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07解決vue-seamless-scroll滾動加點贊銜接處數據不同步問題
這篇文章主要介紹了解決vue-seamless-scroll滾動加點贊銜接處數據不同步問題,初步判斷可能是因為下方懸接vue-seamless-scroll是靜態(tài)的,沒同步DOM,本文給大家分享解決方法,感興趣的朋友一起看看吧2021-11-11vue3 組合式API defineEmits() 與 emits 組
在Vue中,defineEmits()是Vue3組合式API中用于聲明自定義事件的,而emits選項則用于Vue2和Vue3的選項式API中,defineEmits()允許使用字符串數組或對象形式聲明事件,emits選項也支持這兩種形式,且驗證函數可以驗證事件參數,這兩種方法都是為了更規(guī)范地在組件間通信2024-09-09