拖拽献祭中的黑山羊-DataTransfer对象

这篇文章发布于 2018年09月30日,星期日,22:10,归类于 JS API。 阅读 46800 次, 今日 4 次 20 条评论

 

一、超位魔法-任意拖拽

解构datatransfer对象

伟大的安兹·乌尔·恭,纳萨里克地下大坟墓的主人,四十一位无上至尊的统合者,不死者之王,魔导国魔导王,异世界overlord,在这花好月圆之夜,释放了一个超位魔法——任意拖拽!

凡是不能手写页面元素拖拽效果者,即死,10日后才能复活!顿时,无数前端开发还没意识到发生了什么,眼前一黑,全部都倒下了。

“哈哈,实在是精彩,足足秒了7万零1人,破了我昨天的记录!”恭大人兴奋地大声疾呼。

然后,很快,天空出现巨大的黑球,然后5只巨大的黑山羊幼崽出现了。

祭祀的黑山羊幼崽

大如小山,除了怪物已经无法用其他词形容了。

剩下的开发人员手足无措,惊恐万分,顿时仓皇而逃,但都难逃一死。不死者之王,果然是至高无上的存在!

二、解构魔法-DataTransfer对象

大家好,我是一个小白前端,因为昨天陪女朋友去现充世界赏月,没有参加这场战役,所以幸免于难。但现在还是心有余悸,因为自己也不会手写拖拽功能,如果自己在场,怕是女朋友就是别人的了。

虽庆幸,仍不安。要是下次伟大的安兹·乌尔·恭,不死者之王再释放这个“任意拖拽”超位魔法,那我岂不是死翘翘了?不行,为了活命,我必须对这个超位魔法进行解构,了解之学习之破解之。

皇天不负有心人,经过七七四十九天废寝忘食的研究,终于解构出了这个超位魔法的关键所在——DataTransfer对象,只要弄动DataTransfer对象,什么献祭中的黑山羊的幼崽都只是可爱的小羊而已,不足为惧。

为了让前台前端同僚也可以幸免于难,于是,我冒着被暴露的风险向大家传授DataTransfer对象相关知识。

DataTransfer对象的出现

DataTransfer对象出现在拖拽事件中,具体包括开始拖拽dragstart事件,拖拽进入dragenter事件,拖拽离开dragleave事件,拖拽经过dragover事件,拖拽释放drop事件以及拖拽结束dragend事件。

示意代码如下:

document.addEventListener('dragstart', function (event) {
    console.log('dragstart: ' + event.dataTransfer);    
});
document.addEventListener('dragenter', function (event) {
    console.log('dragenter: ' + event.dataTransfer);    
});
document.addEventListener('dragleave', function (event) {
    console.log('dragleave: ' + event.dataTransfer);    
});
document.addEventListener('dragover', function (event) {
    event.preventDefault();
    console.log('dragover: ' + event.dataTransfer);    
});
document.addEventListener('drop', function (event) {
    console.log('drop: ' + event.dataTransfer);    
});
document.addEventListener('dragend', function (event) {
    console.log('dragend: ' + event.dataTransfer);    
});

假设包含上面JavaScript代码的页面有一张图片,拖动这张图片,就可以在控制台控制看到类似下面截图的输出:

输出事件event对象中的DataTransfer对象

若有兴趣亲自看看控制台输出内容,可以狠狠地点击这里:拖拽相关事件中的DataTransfer对象demo

这个DataTransfer对象包含诸多属性和方法,我们可以用来改变拖拽效果,获取和设置拖拽内容,熟练掌握,必定可以抵御献祭中的黑山羊魔法。

三、DataTransfer对象属性和方法详解

DataTransfer对象包含下面5个标准属性和4个标准方法。

标准属性

DataTransfer.dropEffect
获取当前所选拖放操作的类型,或将拖拽操作设置为新类型。值必须为nonecopylinkmove中的一个。
DataTransfer.effectAllowed
提供可能的所有类型的操作。必须是nonecopycopyLinkcopyMovelinklinkMovemovealluninitialized中的一个。
DataTransfer.files
拖拽的本地文件列表。如果拖动操作不涉及拖动文件,则此属性为空列表。
DataTransfer.items (只读)
提供DataTransferItemList对象,该对象是所有拖动数据的列表。
DataTransfer.types (只读)
dragstart事件中设置数据格式,返回的是一个字符串数组。

标准方法

DataTransfer.clearData([format])
删除与给定类型关联的数据。format参数是可选的。如果类型为空或未指定,则删除所有关联的数据。如果指定类型的数据不存在,或者数据传输不包含任何数据,则此方法无效。
DataTransfer.getData(format)
返回给定类型的数据,如果该类型的数据不存在或数据传输不包含数据,则返回空字符串。
DataTransfer.setData(format, data)
设置给定类型的数据。如果该类型的数据不存在,则在末尾添加,以使列表中的最后一项成为新格式类型。如果该类型的数据已存在,则在相同位置把现有数据替换掉。
DataTransfer.setDragImage(img, xOffset, yOffset)
设置用于拖动的自定义图像。

下面一个一个详细展开。

四、DataTransfer.dropEffect

dropEffect属性顾名思意拖拽效果,在PC web端主要表现在鼠标手形上。不同的dropEffect值,鼠标的手形效果是不一样的。

举个例子,假设有个box元素,当dragover的时候,我们设置其dropEffect值分别为movecopylinknone,如下代码示意:

// 拖拽元素经过box
box.addEventListener('dragover', function (event) {
    event.preventDefault();
    // 设置dropEffect值为move, copy, link, none
    event.dataTransfer.dropEffect = 'move';
});

则4个不同属性值效果如下如下截图示意:

move手形 copy手形 link手形 none手形

可以看到,设置不同的dropEffect属性值,鼠标的手形就会有不一样的表现。

眼见为实,如果您是桌面浏览器浏览,则可以狠狠地点击这里:DataTransfer.dropEffect效果演示demo

选择不同的单选选项,然后拖动图片到该容器上方,即可看到不一样的鼠标手形表现,这就是dropEffect目前的表现。

其他信息
dropEffect属性的设置主要用在dragenterdragover事件中,同时受effectAllowed属性影响。

例如,我们在dragstart的时候设置effectAllowed属性值为'none',则dropEffect只能表现为'none',而不会出现其他手形,即使设置了其他手形对应的属性值。

五、DataTransfer.effectAllowed

表示拖拽允许的效果。支持的属性值较多,如下:

uninitialized
默认值,表示未初始化。从效果上来讲,和all是一样滴。
none
不允许拖拽。鼠标保持禁止状态。
copy
可以在新位置复制元素。
copyLink
允许复制和链接操作。
copyMove
允许复制和移动操作。
link
可以在新位置建立链接。
linkMove
允许链接和移动操作。
move
可以把元素移动到新位置。
all
什么操作都允许。

如果effectAllowed属性值设置为上面列表以外的其它值则没有任何效果。另外在IE浏览器下所有的字都会小写,因此类似linkMove会变成linkmove

effectAllowed和dropEffect的关系

effectAlloweddropEffect通常应用的事件方法名不一样,effectAllowed多用在dragstart事件中,而dropEffect属性的设置主要用在dragenterdragover事件中。

effectAlloweddropEffect的彼此间是有制约关系,当我们给effectAllowed设置了对应的属性值,则dropEffect只能设置为effectAllowed允许的值,否则是无效的。

举个例子,如果我们设置effectAllowed值为'copyMove',则dropEffect只有'copy''move'这两个属性值才有效。

effectAllowed看上去很屌,但实际应用的时候相当鸡肋。我们通常的拖拽应用是用不到这个的。只要下面这个场景,那就是当我们有很多个元素需要拖拽,但是需要像垃圾一样分门别类的时候,effectAllowed就有用了。

原生拖拽事件有这样一种行为,那就是如果effectAllowed值和dropEffect值不一致,则无法响应drop事件。我们可以想象一下,假设我们在网页中放三个垃圾箱,分别回收move元素,copy元素和link元素,由于DOM元素的转移或者复制我们都是在drop事件中完成的,则effectAllowed包含copy元素的才能拖拽进入copy垃圾箱(可以触发drop事件)。

平常开发都是简单的拖拽,哪里需要用到分门别类啊,因此effectAllowed也就无用武之地了。

//zxx: 这里有个CodePen,可以感受下。

link作为属性值案例一则

这里插播一个案例,实现的是Chrome浏览器下拖拽链接也能新窗口打开的实现。在Firefox和IE浏览器下,链接元素你按住一拖拽,就可以在新的浏览器标签页中打开,Chrome浏览器则需要拖动到地址栏才可以。如果我们想要实现Chrome浏览器下拖拽也能标签页中打开,就可以看看下面这个案例。

您可以狠狠地点击这里:拖拽链接新标签页打开demo

拖拽链接到demo页面一个灰色盒子中,如下图:

拖拽到盒子中

释放鼠标,此时这个链接就会在新标签页中打开。

相关代码如下:

<a href="#" id="link">拖拽我到下面框框试试</a>
<p id="box" class="box"></p>
var isOpenLink = null;
link.addEventListener('dragstart', function (event) {
    event.dataTransfer.effectAllowed = 'link';    
});
box.addEventListener('dragover', function (event) {
    event.preventDefault();
    // 检测是否需要新窗口打开链接
    if (event.dataTransfer.dropEffect != 'link') {
        isOpenLink = true;
    }
    event.dataTransfer.dropEffect = 'link';
});
box.addEventListener('drop', function (event) {
    event.preventDefault();
    if (isOpenLink) {
        window.open(link.href);
    }
});

通过检测dataTransfer.dropEffect,有效避免和原本支持拖拽新标签页打开链接的浏览器冲突。

六、DataTransfer.files

当我们从桌面往网页浏览器中拖文件的时候,DataTransfer.files就派上用场了,其对应的列表只就是我们拖进去的文件列表。

目前在实际开发中应用的比较多的是拖拽上传,具体可参见我11年写的一个ajax图片上传demo

这里我又写了一个更精简的demo,演示如何使用DataTransfer.files获得桌面拖拽进入的文件信息。

您可以狠狠的点击这里:DataTransfer.files获取桌面文件信息demo

例如,我框选两个文本文件和两个快捷方式,拖到demo页面的框框里面,结果效果如下图:

拖动查看文件类型

拖动输出文件类型

可以看到,两个txt文件识别为了text/plain,而快捷方式文件没有mime-type,因此,输出内容是空。

相关JS代码如下:

box.addEventListener('drop', function (event) {
    event.preventDefault();
    // 遍历文件信息
    var files = event.dataTransfer.files || [];
    var length = files.length;
    if (length == 0) {
        this.innerHTML = '<p>没有文件</p>';
        return;
    }
    var html = '';
    for (var index = 0; index < length; index++) {
        html += '<p>类型:'+ files[index].type +'</p>';    
    }
    this.innerHTML = html;
});

什么时候会出现“没有文件”的提示呢?比方说demo页面你框选几个文字,拖拽到方框框里面,就会提示“没有文件”了,因为你拖拽进去的本来就不是文件内容。不过,这个拖拽内容我们是可以使用DataTransfer.items获取的。

七、DataTransfer.items

DataTransfer.items可以用来获取拖拽的数据信息,只读。

DataTransfer.items为DataTransferItem类型的数据列表集合,而DataTransferItem又包含多个属性和方法,属性包括kindtype,方法包括getAsString()getAsFile(),这个和剪切板item对象方法是一致的。

我们可以通过一个小案例快速了解一下这些属性含义和作用。

您可以狠狠地点击这里:DataTransferItem属性和方法信息输出demo

log信息打印测试代码如下:

console.log('kind: ' + DataTransferItem.kind + ', type: ' + DataTransferItem.type + '\n');
DataTransferItem.getAsString(function (str) {
    console.log('\ngetAsString: ' + str);    
});

主要测试了kindtype属性和getAsString()方法。

拖动图片,此时就可以得到DataTransfer.items具体都是些什么信息,如下截图:

拖拽一个图片显示的item信息

其中,包含3个item,kind全部都是stringtype指的是mimeType类型,包含3中不同类型,为:text/plaintext/htmltext/uri-list

如果我们拖动页面上文字信息到输入框上显示的就只有2中类型项:

文本拖拽进去显示的文本信息

如果从浏览器地址栏拖文字到输入框,就会只有text/plain这1种类型。

//zxx: 浏览器不同的输入容器会自动甄别显示内容,例如:,对于文本框,显示内容为text/plain纯文本(如本demo),如果是富文本输入框(如HTML元素设置contenteditable属性),则显示内容为text/html富文本。


实际上,子item的kind还有可能是'file',例如把微博个人主页头像拖进来:

拖头像

file类型的kind

此时,可以调用getAsFile()方法将其转换成二进制文件对象,然后可以ajax上传等处理。

实际开发的处理模板

有个处理模板,无论何种数据类型,大家可以找到对应位置,进行相应的处理,省掉不少自己写逻辑判断的麻烦。

handleDataTransferItems = function (items) {
  for (var i = 0; i < items.length; i += 1) {
    var kind = items[i].kind;
    var type = items[i].type;
    // 逻辑开始
    if (kind == 'string') {
      if (type.indexOf('text/plain') != -1) {
        items[i].getAsString(function (str) {
          // str是纯文本,该怎么处理,就在这里处理
        });   
      } else if (type.indexOf('text/html') != -1) {
        items[i].getAsString(function (str) {
          // str是富文本,就在这里处理
        });
      } else if (type.indexOf('text/uri-list') != -1) {
        items[i].getAsString(function (str) {
          // str是uri地址,在这里进行处理
        });
      }
    } else if (kind == 'file') {
      // 如果是图片
      if (type.indexOf('image/') != -1) {
        var file = items[i].getAsFile();
        // file就是图片文件对象,可以上传,或者其他处理
      }
    }
  }
};

使用示例:

document.addEventListener('drop', function (event) {
    var items = event.dataTransfer.items || [];
    handleDataTransferItems(items);
});

近亲clipboardData.items

剪切板对象也有items属性,数据类型以及item子项的属性和方法和DataTransfer对象是一样的,学会了一个自然也就学会了另外一个,不展开。

八、DataTransfer.types

DataTransfer.types用在dragstart事件中,包含拖拽内容的包含的mimeType类型们,可以遍历出具体的type类型。假设页面有如下全局JS代码:

document.addEventListener('dragstart', function (event) {
    // 遍历并输出拖拽内容的类型
    var types = event.dataTransfer.types || [];
    [].slice.call(types).forEach(function (type) {
        console.log(type);
    });
});

则拖动纯图片元素输出内容如下:
拖动图片的输出信息

text/uri-list
text/html
Files

拖动文字如下:

拖拽文字的输出

text/html
text/plain

眼见为实,您可以狠狠地点击这里:DataTransfer.types使用测试demo

DataTransfer.types有什么用呢?如果我们希望页面某些元素可以拖拽,有些不允许,则可以使用types属性进行检测,例如,如果是包含Files,则执行event.preventDefault(),拖拽行为就会被禁止。

九、DataTransfer.getData()

DataTransfer.getData(format)可以快捷获取拖拽的内容。

format可以理解为就是DataTransfer.items中的type值,例如,我们想要获取拖拽内容的富文本格式,可以:

document.addEventListener('drop', function (event) {
    // 获取拖拽富文本内容
    var html = event.dataTransfer.getData('text/html');
});

如果是获取纯文本,可以text/plain,或者有时候text/uri-list,实际开发时候,我们会直接使用'text',例如:

document.addEventListener('drop', function (event) {
    // 获取拖拽纯文本内容
    var html = event.dataTransfer.getData('text');
});

优化输入框拖拽输入体验

本案例类似于“利用剪切板JS API优化输入框的粘贴体验”这篇文章,当我们拖拽本文进入输入框的时候,文本信息可能包含不需要的信息,例如手机号中的334分隔符,借助getData()方法我们可以进行过滤和优化。

您可以狠狠地点击这里:DataTransfer.getData()优化手机号拖拽输入体验demo

可以看到(如下GIF),手机号明明是132-0803-3621,但拖到输入框后自动变成了13208033621,省掉了再编辑的烦恼:

手机号拖拽优化

相关JavaScript代码如下:

input.addEventListener('drop', function (event) {
    // 获取拖拽文本内容
    var text = event.dataTransfer.getData('text');
    if (this.value == '' && text.match(/\d/g) && text.match(/\d/g).length == 11) {
        event.preventDefault();
        input.value = text.replace(/\D/g, '');
        input.select();
    }
});

十、DataTransfer.setData()

DataTransfer.setData(format, data)可以自定义拖拽的内容信息。可以重置原生的拖拽内容,或者用来参数传递。

例如如果我们页面运行了如下JS代码:

document.addEventListener('dragstart', function (event) {
    // 重置拖拽文本内容
    event.dataTransfer.setData('text', '张鑫旭-鑫空间-鑫生活');
});

则所有内容拖拽进入输入框内容都是“张鑫旭-鑫空间-鑫生活”,如下GIF示意:

拖拽内容被替换

您可以狠狠地点击这里:DataTransfer.setData()重置拖拽文本内容demo


我们实际开发基本上都是拖拽具体模块元素,此时setData()方法多用来传递拖拽元素的id

十一、DataTransfer.clearData()

DataTransfer.clearData()只能用在dragstart事件中,会清除所有的数据。也就是执行了此方法后,DataTransfer.getData()方法只能获得空字符串。

使用场景不是很多。

当我们需要使用setData()方法自定义拖拽数据的时候,为了避免原生拖拽数据干扰,可以先执行一次clearData()方法。这样可以避免text/html类型数据干扰(如果我们自定义的是纯文本数据)。

十二、DataTransfer.setDragImage()

DataTransfer.setDragImage(img, offsetX, offsetY)用在dragstart事件中,可以设置拖拽时候有个图片跟在后面。

其中,img表示图片DOM元素对象,offsetX表示距离鼠标的水平偏移距离,offsetY表示距离鼠标的垂直偏移距离。

直接看一个例子:

var image = new Image();
image.src = './1f603.svg';
document.addEventListener('dragstart', function (event) {
    // 设置拖拽图片
    event.dataTransfer.setDragImage(image, 10, 10);
});

此时,页面上拖拽任意内容,就会看到一个笑脸跟在后面了,例如:

拖拽笑脸图片跟随

您可以狠狠地点击这里:DataTransfer.setDragImage()设置拖动跟随图片demo

十三、最后的献祭魔法

果然是超位魔法,解构下来洋洋洒洒这么多内容,虽然学得辛苦,但是若能保命,拿一切都值得的。

国庆假期到来,于是回乡去探亲,没想到好死不活,遇到伟大的安兹·乌尔·恭正好对家乡的前端再次发动了超位魔法——拖拽献祭中的黑山羊,无数前端开发沉浸在假期的愉悦之中,根本没有意识到危险的到来,似乎过去的梦魇又将重新。我虽然是个小白前端,但是为了整个行业的兴盛不衰,我毅然站在了魔法的最前面,抵御魔法的攻击。

这攻势凶猛异常,我忙拿出分析解构DataTransfer对象学到的知识作为武器抵御,哇哦哦哦,身体开始剧痛,剧烈的魔法风暴从身体擦过,想刀片一样,双脚开始站立不稳,意识开始模糊,我连忙回想没日没月解构DataTransfer对象的经历,是不是遗漏了什么细节,Bing地一声,我意识到了什么,身体中间出现一道金光,护住的最重要的心脉,原来是由此及彼,解构DataTransfer联想到了其他对象,让自己同时学会了其他魔法,相互融合,威力大增,虽然依旧遍体鳞伤,但是主要心脉护住了,就不会有生命危险,只要顶住即可!

啊啊啊啊,输出全靠吼,我用尽力气,“解构全开,魔法破!!!”

轰隆一声巨响,瞬间眼前的黑暗烟消云散,我实在只撑不住,单腿跪地,大口喘气!

“你是什么人?居然能够借助我的超位祭祀魔法!”

“我……我……就是一个……”我咽了下喉咙里的血水,继续说道:“一个普通的小白前端……”

“搜噶!你叫什么名字?”

“我的名字不值得安兹大人挂齿……”

“莫非你要拒绝回答我的问题?!!”

“不敢不敢!我的名字是————————————空气!”

“好!空气,我记住你了!我今天放你一条生路,你好好提高你的魔法技能,等以后足够强大了,我特许你可以和我一战!哈哈哈哈!”

“好……多谢安兹大人!”

说完,安兹大人就通过时空门离开了。

而我“空气”也从此成了安兹大人最重视的敌人!

(本篇完)

分享到:


发表评论(目前20 条评论)

  1. skoll说道:

    h5拖拽,拖拽物体的那个鼠标禁止按钮有办法自定义样式吗?原来那个按钮简直不忍直视

  2. AICC说道:

    引入拖拽功能后,input输入的内容无法选中,尝试了一翻没有结论,求大佬支援
    具体是引入这个vue拖拽库https://github.com/hilongjw/vue-dragging/blob/master/vue-dragging.js,然后原生 input内容不可用鼠标的方式选中,只能用双击选中

  3. 王小飞说道:

    ?受教,看看评论按钮的效果

  4. 半夜盗贼说道:

    DataTransfer.setData() 和 DataTransfer.getData() 是不是只可以传输字符串呀?想要传输对象的话只能转换成字符串传输吗?

  5. ant说道:

    强势围观

  6. 小明说道:

    如何更改被拖拽浮沉的样式额,拖拽之后是一个不透明的,字体黑车的,是否能更改字体白色,背景颜色为黑色的?

  7. wqe说道:

    强势围观

  8. blossom说道:

    哈哈哈~~?了

  9. 千寻而见说道:

    拖拽这个非常有用

  10. 嘻嘻说道:

    超链接下的波浪线一直动是怎么做的啊

  11. 骨王麾下小迪说道:

    吼吼 看在你是吾王欣赏的人 是否考虑加入大坟墓

  12. 大屌萌妹说道:

    献祭中的黑山羊幼崽 懂的人自然懂

  13. meepo说道:

    一股中二气息。哈哈

  14. 广建说道:

    整理的很详细,棒棒的

  15. nicholasurey说道:

    换新妹纸了?

  16. 魏义齐说道:

    提交评论的按钮效果怎么做的?