JS拖動對象那些事

前言

說明:請使用該Token:1562835370187訪問本文Demo,或者點擊訪問。javascript

本文經過實現(文本/文件)拖動預覽功能全面、深刻分析整個過程涉及的對象及其API,加強咱們對文件類數據的理解和應用。介紹的內容包括:html

  • DataTransfer對象介紹及API應用
  • 拖動事件類型及應用
  • 截取拖動信息
  • FileBlob對象介紹
  • 圖片預覽功能實現
  • Data URL和Blob URL的區別

DataTransfer對象

DataTransfer對象用來保存在一個拖放操做中被拖動的數據,它能夠包含一個或多個數據項,每個數據項又能夠有一種或多種數據類型。java

DataTransfer對象的屬性分爲標準屬性gecko屬性。其中,標準屬性是全部現代瀏覽器都支持的,gecko屬性則只有gecko內核的瀏覽器才支持。相應地,DataTransfer的方法也有標準方法和gecko方法之分。git

DataTransfer的標準屬性

屬性名 取值 備註
dropEffect none, copy, link, move 獲取/設置當前的拖放操做類型
effectAllowed copyLink, copyMove, link, linkMove, move, all or uninitialized(默認值) 獲取/設置容許的全部拖動操做類型
files Arrray 被拖動的文件信息列表
items DataTransferItemList對象 只讀,被拖動的數據列表
types Arrray 只讀,在dragStart事件中設置(經過setData API)的數據格式的列表

DataTransfer的標準方法

方法名 參數 備註
clearData([format]) forma: [可選]數據類型 清空全部指定類型的拖動數據
getData(format) format: [必須]數據類型 獲取指定類型的拖動數據,無數據則返回空字符串
setData(format, data) format: [必須]數據類型,data: [必須]添加到拖動對象中的數據 設置給定類型的數據。若是該類型的數據不存在,則添加在列表末尾;若是存在,則替換。
setDragImage(img, xOffset, yOffset) img: [必須]圖像元素;xOffset: [必須]X軸偏移值;yOffset: [必須]Y軸偏移值 自定義拖動圖像

關於DataTransfer對象的Gecko屬性和方法,移步這裏瞭解。angularjs

拖動事件

用戶在拖動元素或者文本時,每隔幾百毫秒就會觸發拖動事件。拖動事件類型有:github

document.addEventListener('dragstart', evt => {
    console.log('dragstart');    
});
document.addEventListener('dragenter', evt => {
    console.log('dragenter');    
});
document.addEventListener('dragleave', evt => {
    console.log('dragleave');    
});
document.addEventListener('dragover', evt => {
    event.preventDefault();
    console.log('dragover');    
});
document.addEventListener('drop', evt => {
    console.log('drop');    
});
document.addEventListener('dragend', evt => {
    console.log('dragend');    
});
複製代碼

拖動事件是綁定在元素身上的,上述示例代碼綁定在了document對象上,則對整個文檔頁面的拖動事件生效;但在實際應用中,咱們更可能是劫持某個輸入區域的拖放操做,譬如:web

$inputBox.addEventListener'drop', evt) => {
  evt.preventDefault()
  console.log('drop')
}, {
  capture: false,
  passive: false
})
複製代碼

特別地,拖動事件有嚴格的觸發次序: 數組

假定有一個拖放區域,拖動外部元素/文本到該區域,可能觸發的拖動事件次序有以下兩種類型:
或是

拖動效果

DataTransfer對象提供了dropEffecteffectAllowed兩個屬性,容許咱們自定義拖動過程當中鼠標的類型,其中,dropEffect的值受effectAllowed制約,只能設置effectAllowed容許設置的值。瀏覽器

effectAllowed

effectAllowed只能在dragstart事件中設置,在其餘事件中設置不會生效。不一樣取值表明的含義:異步

  • none:項目被禁止拖動
  • copy:能夠在新位置複製源項目
  • copyLink:容許複製和連接操做
  • copyMove:容許複製和移動操做
  • link:能夠在新位置創建源項目的連接
  • linkMove:容許連接和移動操做
  • move:能夠把項目移動到新位置
  • all:容許全部的操做
  • uninitialized:默認值,效果與all相同
document.addEventListener('dragstart', (e) => {
    e.dataTransfer.effectAllowed = 'none'
}, {
    capture: false,
    passive: false
})
複製代碼

如上設置後,文檔上的元素/文本將被禁止拖放。但仍然能夠觸發除drop之外的一系列的拖動事件。

dropEffect

dropEffect的取值受effectAllowed的制約,只容許被設置effectAllowed指定的值。可能的取值有:movecopylinknonedropEffect通常在dragenterdragover事件中設置,在其餘事件中設置也不會生效。

$Ele.addEventListener('dragover', (e) => {
    e.preventDefault()
    e.dataTransfer.dropEffect = 'move|move|copy|link'
}, {
    capture: false,
    passive: false
})
複製代碼

點擊這裏玩一玩:拖動操做鼠標類型demo

setDragImage

經過該API能夠自定義拖動操做中鼠標的背景圖片,這個方法必須在dragstart中調用:

const img = new Image()
img.src = './bg.png'
document.addEventListener('dragstart', (e) => {
    e.dataTransfer.setDragImage(img, 5, 5)
}, {
    capture: false,
    passive: false
})
複製代碼

以下圖,將文本拖動到輸入框,在鼠標底下會緊跟一張背景圖片:

dataTransferItem對象

在介紹截取拖動數據以前,有必要先了解下dataTransferItem對象。dataTransferItem對象表示一個拖動數據項,好比拖動的文本、圖片等數據。它有兩個用於描述數據類型的只讀屬性,還有用於將dataTransferItem數據轉換爲對應類型的數據(字符串、文件等)的一系列方法。以下表:

屬性/方法 參數 描述
kind 只讀 拖動項數據的性質:string/file
type 只讀 拖動項數據的MIME類型:image/png等
getAsFile() / 以文件的形式讀取拖動項數據,返回一個File對象,非file性質的數據則返回null。
getAsString(call) call:(str) => str 以字符串的形式讀取拖動數據,在回調函數中獲取字符串數據。

在一個拖動操做中,可能包含了多項數據(好比同時拖動了多個文件),這個時候就須要一個列表去描述這一組拖動數據,這個列表,就是DataTransferItemList對象,這是一個類數組的對象(有一個length屬性,只讀,還有add()remove()clear()等API)。

截取拖動數據

對拖動數據進行劫持通常在dragstartdrop階段處理,由於這兩個階段的事件都是一次性,不會屢次觸發,不會形成性能問題。例如,用setData設置固定的文案去覆蓋被拖動的文本內容:

document.addEventListener('dragstart', e => {
  e.dataTransfer.setData('text/plain', '指望覆蓋的文案')
}, {
  capture: false,
  passive: false
})
複製代碼

drop階段咱們能夠截取到拖動數據,而後作二次處理。但對於不一樣類型的數據截取,會有一些差別。

字符串類型數據

對於字符串類型數據,最簡單的是經過getData方法獲取(該方法沒法獲取文件類數據)。例如,獲取拖動的純文本內容:

$Ele.addEventListener('drop', (e) => {
  e.preventDefault()
  console.log(e.dataTransfer.getData('text'))
}, {
  capture: false,
  passive: false
})
複製代碼

這種方式獲取的是拖動的純文本內容:

固然,經過指定字符串的格式,還能夠獲取富文本內容:

console.log(e.dataTransfer.getData('text/plain'))
複製代碼

這種方式獲取的是拖動文案的DOM節點及其祖先節點內容:

另一種是經過 dataTransferItem對象獲取,稍後再介紹。

文件類型數據

DataTransfer對象有一個files屬性,用於存儲拖動文件的列表數據。數據項就是一個File對象數據,能夠直接取出。

$Ele.addEventListener('drop', (e) => {
  e.preventDefault()
  const files = e.dataTransfer.files || []
  if(files.length) {
    // do something...
  }
}, false)
複製代碼

從items屬性中截取

在前面dataTransfer對象的標準屬性中介紹過,items屬性的值是一個DataTransferItemList對象,經過for循環從裏面逐個取出數據,並判斷dataTransferItemkind屬性值做不一樣的操做:

const transferItems = e.dataTransfer.items
for(let i = 0; i < transferItems.length; i++) {
  const item = transferItems[i]
  if(item.kind === 'string') { // 處理字符串數據
    item.getAsString(str => console.log(str))
  } else if (item.kind === 'file') { // 處理文件數據
    const file = item.getAsFile()
    // ...
  }
}
複製代碼

經過上面的方式截取到拖動數據以後,咱們能夠根據須要對文件數據進行預覽或發送。

File&Blob概述

根據上文可知,經過getAsFile API咱們獲取到了拖動的文件,返回的是一個File對象。裏面包含了文件類型(image/png等)、名字和大小等信息。File對象是特殊類型的Blob,繼承了Blob的屬性和方法,File裏面主要使用的屬性有:

屬性 備註
name 文件名字
size 文件大小
type 文件MIME類型
lastModified 文件的最後修改時間

Blob是一個不可變、表示原始數據的類文件對象,擁有typesize兩個只讀屬性,能夠經過FileReaderResponse讀取Blob裏面的數據。譬如,一個圖片文件,被FileReader以不一樣的數據格式讀取:

  • 以Data URL格式讀取
const reader = new FileReader()
  reader.addEventListener("load", () => {
      console.log(reader.result)
  }, false)
reader.readAsDataURL(file)
// reader.readAsArrayBuffer(file)
// reader.readAsText(file[, encoding])
複製代碼

返回一串data:[MIME type];開頭的base64編碼串,讀取的data URL數據通常用在圖片預覽上:

  • ArrayBuffer格式讀取,返回一個ArrayBuffer對象
    讀取的ArrayBuffer數據通常用於圖片的二次處理,譬如轉換jepg格式圖片爲png。
  • 以Text格式讀取,默認以utf-8的編碼格式讀取文本內容(圖片這麼讀下去,會亂碼的)

實現圖片預覽

如今,根據截取的數據,咱們須要繼續實現:若是是圖片,則對其進行預覽。預覽的方式有Data URL和Blob URL兩種

Data URL的方式在 File&Blob概述章節已經介紹了,使用 FileReader.readAsDataURL()方法便可。

createObjectURL

URL對象能夠用來構造、解析和編碼url,它提供了一個靜態方法createObjectURL(),方法的參數僅限於FileBlobMediaSource這三種類型的對象,用於將類文件對象轉化成URL字符串(形如:blob:http://localhost:8080/c61a0313-2e45-4adc-a40e-fbbffd4c84ba),該字符串指向對應(文件)對象的引用。

// 前面咱們經過截取拿到了file對象數據
const { name, size } = file
const url = URL.createObjectURL(file)
this.previewObj = {
    name,
    size,
    src: url
}
複製代碼

調用createObjectURL()方法有兩個特色:

  • document卸載以前,URL對象實例會一直保持源對象的引用;
  • 對同一個對象執行屢次createObjectURL方法,會生成不一樣的URL對象實例。

從規範和內存管理的角度講,每一次執行createObjectURL,咱們須要手動釋放內存(但實際應用中影響並不大):

// 在文件加載就緒以後卸載URL實例的引用
img.onload = function() {
  URL.revokeObjectURL(this.src);
}
複製代碼

Data URL與Blob URL的區別

  • Data URL表示的是base64格式編碼的圖片內容,Blob URL表示的是(內存中/本地)圖片文件的引用(這是本質區別);
  • Data URL的轉換過程是異步的,Blob URL是同步的。在大文件或者批量處理的狀況下,Blob URL的讀取速度優於Data URL
  • Data URL(幾千個字符)串遠遠長於BlobURL串(100個字符之內),從網頁性能上講,Blob URL優於Data URL
  • Data URL能夠跨設備(瀏覽器)訪問,Blob URL只在當前web應用中有效,跨應用無效(但在document未卸載的狀況下,複製Blob URL到瀏覽器地址欄仍然能夠訪問)。從可移植性來將,Data URL優於Blob URL

最後,

參考:

相關文章
相關標籤/搜索