Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DataTransfer对象完全手册 #18

Open
lostvita opened this issue Jul 23, 2019 · 0 comments
Open

DataTransfer对象完全手册 #18

lostvita opened this issue Jul 23, 2019 · 0 comments

Comments

@lostvita
Copy link
Owner

前言

说明:请使用该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。

最后,

参考:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant