lostvita/blog

DataTransfer对象完全手册

lostvita opened this issue · 0 comments

前言

说明:请使用该Token:1562835370187访问本文Demo,或者点击访问。

本文通过实现(文本/文件)拖动预览功能全面、深入分析整个过程涉及的对象及其API,增强我们对文件类数据的理解和应用。介绍的内容包括:

  • DataTransfer对象介绍及API应用
  • 拖动事件类型及应用
  • 截取拖动信息
  • FileBlob对象介绍
  • 图片预览功能实现
  • Data URL和Blob URL的区别

DataTransfer对象

DataTransfer对象用来保存在一个拖放操作中被拖动的数据,它可以包含一个或多个数据项,每一个数据项又可以有一种或多种数据类型。

DataTransfer对象的属性分为标准属性和gecko属性。其中,标准属性是所有现代浏览器都支持的,gecko属性则只有gecko内核的浏览器才支持。相应地,DataTransfer的方法也有标准方法和gecko方法之分。

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属性和方法,移步这里了解。

拖动事件

用户在拖动元素或者文本时,每隔几百毫秒就会触发拖动事件。拖动事件类型有:

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对象上,则对整个文档页面的拖动事件生效;但在实际应用中,我们更多是劫持某个输入区域的拖放操作,譬如:

$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。

最后,

参考: