JavaScript 拖拉时间之drag案例详解
拖拉(drag)指的是,用户在某个对象上按下鼠标键不放,拖动它到另一个位置,然后释放鼠标键,将该对象放在那里。
拖拉的对象有好几种,包括元素节点、图片、链接、选中的文字等等。在网页中,除了元素节点默认不可以拖拉,其他(图片、链接、选中的文字)都是可以直接拖拉的。为了让元素节点可拖拉,可以将该节点的draggable
属性设为true
。
<div draggable="true"> 此区域可拖拉 </div>
draggable
属性可用于任何元素节点,但是图片(<img>
)和链接(<a>
)不加这个属性,就可以拖拉。对于它们,用到这个属性的时候,往往是将其设为false
,防止拖拉这两种元素。
注意,一旦某个元素节点的draggable
属性设为true
,就无法再用鼠标选中该节点内部的文字或子节点了。
当元素节点或选中的文本被拖拉时,就会持续触发拖拉事件,包括以下一些事件。
-
drag
:拖拉过程中,在被拖拉的节点上持续触发(相隔几百毫秒)。 -
dragstart
:用户开始拖拉时,在被拖拉的节点上触发,该事件的target
属性是被拖拉的节点。通常应该在这个事件的监听函数中,指定拖拉的数据。 -
dragend
:拖拉结束时(释放鼠标键或按下 esc 键)在被拖拉的节点上触发,该事件的target
属性是被拖拉的节点。它与dragstart
事件,在同一个节点上触发。不管拖拉是否跨窗口,或者中途被取消,dragend
事件总是会触发的。 -
dragenter
:拖拉进入当前节点时,在当前节点上触发一次,该事件的target
属性是当前节点。通常应该在这个事件的监听函数中,指定是否允许在当前节点放下(drop)拖拉的数据。如果当前节点没有该事件的监听函数,或者监听函数不执行任何操作,就意味着不允许在当前节点放下数据。在视觉上显示拖拉进入当前节点,也是在这个事件的监听函数中设置。 -
dragover
:拖拉到当前节点上方时,在当前节点上持续触发(相隔几百毫秒),该事件的target
属性是当前节点。该事件与dragenter
事件的区别是,dragenter
事件在进入该节点时触发,然后只要没有离开这个节点,dragover
事件会持续触发。 -
dragleave
:拖拉操作离开当前节点范围时,在当前节点上触发,该事件的target
属性是当前节点。如果要在视觉上显示拖拉离开操作当前节点,就在这个事件的监听函数中设置。 -
drop
:被拖拉的节点或选中的文本,释放到目标节点时,在目标节点上触发。注意,如果当前节点不允许drop
,即使在该节点上方松开鼠标键,也不会触发该事件。如果用户按下 esc 键,取消这个操作,也不会触发该事件。该事件的监听函数负责取出拖拉数据,并进行相关处理。
下面的例子展示,如何动态改变被拖动节点的背景色。
div.addeventlistener('dragstart', function (e) { this.style.backgroundcolor = 'red'; }, false); div.addeventlistener('dragend', function (e) { this.style.backgroundcolor = 'green'; }, false);
上面代码中,div
节点被拖动时,背景色会变为红色,拖动结束,又变回绿色。
下面是一个例子,展示如何实现将一个节点从当前父节点,拖拉到另一个父节点中。
/* html 代码如下 <div class="dropzone"> <div id="draggable" draggable="true"> 该节点可拖拉 </div> </div> <div class="dropzone"></div> <div class="dropzone"></div> <div class="dropzone"></div> */ // 被拖拉节点 var dragged; document.addeventlistener('dragstart', function (event) { // 保存被拖拉节点 dragged = event.target; // 被拖拉节点的背景色变透明 event.target.style.opacity = 0.5; }, false); document.addeventlistener('dragend', function (event) { // 被拖拉节点的背景色恢复正常 event.target.style.opacity = ''; }, false); document.addeventlistener('dragover', function (event) { // 防止拖拉效果被重置,允许被拖拉的节点放入目标节点 event.preventdefault(); }, false); document.addeventlistener('dragenter', function (event) { // 目标节点的背景色变紫色 // 由于该事件会冒泡,所以要过滤节点 if (event.target.classname === 'dropzone') { event.target.style.background = 'purple'; } }, false); document.addeventlistener('dragleave', function( event ) { // 目标节点的背景色恢复原样 if (event.target.classname === 'dropzone') { event.target.style.background = ''; } }, false); document.addeventlistener('drop', function( event ) { // 防止事件默认行为(比如某些元素节点上可以打开链接), event.preventdefault(); if (event.target.classname === 'dropzone') { // 恢复目标节点背景色 event.target.style.background = ''; // 将被拖拉节点插入目标节点 dragged.parentnode.removechild(dragged); event.target.appendchild( dragged ); } }, false);
关于拖拉事件,有以下几个注意点。
- 拖拉过程只触发以上这些拖拉事件,尽管鼠标在移动,但是鼠标事件不会触发。
- 将文件从操作系统拖拉进浏览器,不会触发
dragstart
和dragend
事件。 -
dragenter
和dragover
事件的监听函数,用来取出拖拉的数据(即允许放下被拖拉的元素)。由于网页的大部分区域不适合作为放下拖拉元素的目标节点,所以这两个事件的默认设置为当前节点不允许接受被拖拉的元素。如果想要在目标节点上放下的数据,首先必须阻止这两个事件的默认行为。
<div ondragover="return false"> <div ondragover="event.preventdefault()">
上面代码中,如果不取消拖拉事件或者阻止默认行为,就不能在div
节点上放下被拖拉的节点。
dragevent 接口
拖拉事件都继承了dragevent
接口,这个接口又继承了mouseevent
接口和event
接口。
浏览器原生提供一个dragevent()
构造函数,用来生成拖拉事件的实例对象。
new dragevent(type, options)
dragevent()
构造函数接受两个参数,第一个参数是字符串,表示事件的类型,该参数必须;第二个参数是事件的配置对象,用来设置事件的属性,该参数可选。配置对象除了接受mouseevent
接口和event
接口的配置属性,还可以设置datatransfer
属性要么是null
,要么是一个datatransfer
接口的实例。
datatransfer
的实例对象用来读写拖拉事件中传输的数据,详见下文《datatransfer 接口》的部分。
datatransfer 接口概述
所有拖拉事件的实例都有一个dragevent.datatransfer
属性,用来读写需要传递的数据。这个属性的值是一个datatransfer
接口的实例。
浏览器原生提供一个datatransfer()
构造函数,用来生成datatransfer
实例对象。
var datatrans = new datatransfer();
datatransfer()
构造函数不接受参数。
拖拉的数据分成两方面:数据的种类(又称格式)和数据的值。数据的种类是一个 mime 字符串(比如text/plain
、image/jpeg
),数据的值是一个字符串。一般来说,如果拖拉一段文本,则数据默认就是那段文本;如果拖拉一个链接,则数据默认就是链接的 url。
拖拉事件开始时,开发者可以提供数据类型和数据值。拖拉过程中,开发者通过dragenter
和dragover
事件的监听函数,检查数据类型,以确定是否允许放下(drop)被拖拉的对象。比如,在只允许放下链接的区域,检查拖拉的数据类型是否为text/uri-list
。
发生drop
事件时,监听函数取出拖拉的数据,对其进行处理。
datatransfer 的实例属性
datatransfer.dropeffect
datatransfer.dropeffect
属性用来设置放下(drop)被拖拉节点时的效果,会影响到拖拉经过相关区域时鼠标的形状。它可能取下面的值。
- copy:复制被拖拉的节点
- move:移动被拖拉的节点
- link:创建指向被拖拉的节点的链接
- none:无法放下被拖拉的节点
除了上面这些值,设置其他的值都是无效的。
target.addeventlistener('dragover', function (e) { e.preventdefault(); e.stoppropagation(); e.datatransfer.dropeffect = 'copy'; });
上面代码中,被拖拉元素一旦drop
,接受的区域会复制该节点。
dropeffect
属性一般在dragenter
和dragover
事件的监听函数中设置,对于dragstart
、drag
、dragleave
这三个事件,该属性不起作用。因为该属性只对接受被拖拉的节点的区域有效,对被拖拉的节点本身是无效的。进入目标区域后,拖拉行为会初始化成设定的效果。
datatransfer.effectallowed
datatransfer.effectallowed
属性设置本次拖拉中允许的效果。它可能取下面的值。
- copy:复制被拖拉的节点
- move:移动被拖拉的节点link:创建指向被拖拉节点的链接
- copylink:允许
copy
或link
- copymove:允许
copy
或move
- linkmove:允许
link
或move
- all:允许所有效果
- none:无法放下被拖拉的节点
- uninitialized:默认值,等同于
all
如果某种效果是不允许的,用户就无法在目标节点中达成这种效果。
这个属性与dropeffect
属性是同一件事的两个方面。前者设置被拖拉的节点允许的效果,后者设置接受拖拉的区域的效果,它们往往配合使用。
dragstart
事件的监听函数,可以用来设置这个属性。其他事件的监听函数里面设置这个属性是无效的。
source.addeventlistener('dragstart', function (e) { e.datatransfer.effectallowed = 'move'; }); target.addeventlistener('dragover', function (e) { ev.datatransfer.dropeffect = 'move'; });
只要dropeffect
属性和effectallowed
属性之中,有一个为none
,就无法在目标节点上完成drop
操作。
datatransfer.files
datatransfer.files
属性是一个 filelist 对象,包含一组本地文件,可以用来在拖拉操作中传送。如果本次拖拉不涉及文件,则该属性为空的 filelist 对象。
下面就是一个接收拖拉文件的例子。
// html 代码如下 // <div id="output" style="min-height: 200px;border: 1px solid black;"> // 文件拖拉到这里 // </div> var div = document.getelementbyid('output'); div.addeventlistener("dragenter", function( event ) { div.textcontent = ''; event.stoppropagation(); event.preventdefault(); }, false); div.addeventlistener("dragover", function( event ) { event.stoppropagation(); event.preventdefault(); }, false); div.addeventlistener("drop", function( event ) { event.stoppropagation(); event.preventdefault(); var files = event.datatransfer.files; for (var i = 0; i < files.length; i++) { div.textcontent += files[i].name + ' ' + files[i].size + '字节\n'; } }, false);
上面代码中,通过datatransfer.files
属性读取被拖拉的文件的信息。如果想要读取文件内容,就要使用filereader
对象。
div.addeventlistener('drop', function(e) { e.preventdefault(); e.stoppropagation(); var filelist = e.datatransfer.files; if (filelist.length > 0) { var file = filelist[0]; var reader = new filereader(); reader.onloadend = function(e) { if (e.target.readystate === filereader.done) { var content = reader.result; div.innerhtml = 'file: ' + file.name + '\n\n' + content; } } reader.readasbinarystring(file); } });
datatransfer.types
datatransfer.types
属性是一个只读的数组,每个成员是一个字符串,里面是拖拉的数据格式(通常是 mime 值)。比如,如果拖拉的是文字,对应的成员就是text/plain
。
下面是一个例子,通过检查datatransfer
属性的类型,决定是否允许在当前节点执行drop
操作。
function contains(list, value){ for (var i = 0; i < list.length; ++i) { if(list[i] === value) return true; } return false; } function dodragover(event) { var islink = contains(event.datatransfer.types, 'text/uri-list'); if (islink) event.preventdefault(); }
上面代码中,只有当被拖拉的节点是一个链接时,才允许在当前节点放下。
datatransfer.items
datatransfer.items
属性返回一个类似数组的只读对象(datatransferitemlist 实例),每个成员就是本次拖拉的一个对象(datatransferitem 实例)。如果本次拖拉不包含对象,则返回一个空对象。
datatransferitemlist 实例具有以下的属性和方法。
-
length
:返回成员的数量 -
add(data, type)
:增加一个指定内容和类型(比如text/html
和text/plain
)的字符串作为成员 -
add(file)
:add
方法的另一种用法,增加一个文件作为成员 -
remove(index)
:移除指定位置的成员 -
clear()
:移除所有的成员
datatransferitem 实例具有以下的属性和方法。
-
kind
:返回成员的种类(string
还是file
)。 -
type
:返回成员的类型(通常是 mime 值)。 -
getasfile()
:如果被拖拉是文件,返回该文件,否则返回null
。 -
getasstring(callback)
:如果被拖拉的是字符串,将该字符传入指定的回调函数处理。该方法是异步的,所以需要传入回调函数。
下面是一个例子。
div.addeventlistener('drop', function (e) { e.preventdefault(); if (e.datatransfer.items != null) { for (var i = 0; i < e.datatransfer.items.length; i++) { console.log(e.datatransfer.items[i].kind + ': ' + e.datatransfer.items[i].type); } } });
datatransfer 的实例方法
datatransfer.setdata()
datatransfer.setdata()
方法用来设置拖拉事件所带有的数据。该方法没有返回值。
event.datatransfer.setdata('text/plain', 'text to drag');
上面代码为当前的拖拉事件加入纯文本数据。
该方法接受两个参数,都是字符串。第一个参数表示数据类型(比如text/plain
),第二个参数是具体数据。如果指定类型的数据在datatransfer
属性不存在,那么这些数据将被加入,否则原有的数据将被新数据替换。
如果是拖拉文本框或者拖拉选中的文本,会默认将对应的文本数据,添加到datatransfer
属性,不用手动指定。
<div draggable="true"> aaa </div>
上面代码中,拖拉这个<div>
元素会自动带上文本数据aaa
。
使用setdata
方法,可以替换到原有数据。
<div draggable="true" ondragstart="event.datatransfer.setdata('text/plain', 'bbb')" > aaa </div>
上面代码中,拖拉数据实际上是bbb
,而不是aaa
。
下面是添加其他类型的数据。由于text/plain
是最普遍支持的格式,为了保证兼容性,建议最后总是保存一份纯文本格式的数据。
var dt = event.datatransfer; // 添加链接 dt.setdata('text/uri-list', 'http://www.example.com'); dt.setdata('text/plain', 'http://www.example.com'); // 添加 html 代码 dt.setdata('text/html', 'hello there, <strong>stranger</strong>'); dt.setdata('text/plain', 'hello there, <strong>stranger</strong>'); // 添加图像的 url dt.setdata('text/uri-list', imageurl); dt.setdata('text/plain', imageurl);
可以一次提供多种格式的数据。
var dt = event.datatransfer; dt.setdata('application/x-bookmark', bookmarkstring); dt.setdata('text/uri-list', 'http://www.example.com'); dt.setdata('text/plain', 'http://www.example.com');
上面代码中,通过在同一个事件上面,存放三种类型的数据,使得拖拉事件可以在不同的对象上面,drop
不同的值。注意,第一种格式是一个自定义格式,浏览器默认无法读取,这意味着,只有某个部署了特定代码的节点,才可能drop
(读取到)这个数据。
datatransfer.getdata()
datatransfer.getdata()
方法接受一个字符串(表示数据类型)作为参数,返回事件所带的指定类型的数据(通常是用setdata
方法添加的数据)。如果指定类型的数据不存在,则返回空字符串。通常只有drop
事件触发后,才能取出数据。
下面是一个drop
事件的监听函数,用来取出指定类型的数据。
function ondrop(event) { var data = event.datatransfer.getdata('text/plain'); event.target.textcontent = data; event.preventdefault(); }
上面代码取出拖拉事件的文本数据,将其替换成当前节点的文本内容。注意,这时还必须取消浏览器的默认行为,因为假如用户拖拉的是一个链接,浏览器默认会在当前窗口打开这个链接。
getdata
方法返回的是一个字符串,如果其中包含多项数据,就必须手动解析。
function dodrop(event) { var lines = event.datatransfer.getdata('text/uri-list').split('\n'); for (let line of lines) { let link = document.createelement('a'); link.href = line; link.textcontent = line; event.target.appendchild(link); } event.preventdefault(); }
上面代码中,getdata
方法返回的是一组链接,就必须自行解析。
类型值指定为url
,可以取出第一个有效链接。
var link = event.datatransfer.getdata('url');
下面的例子是从多种类型的数据里面取出数据。
function dodrop(event) { var types = event.datatransfer.types; var supportedtypes = ['text/uri-list', 'text/plain']; types = supportedtypes.filter(function (value) { types.includes(value) }); if (types.length) { var data = event.datatransfer.getdata(types[0]); } event.preventdefault(); }
datatransfer.cleardata()
datatransfer.cleardata()
方法接受一个字符串(表示数据类型)作为参数,删除事件所带的指定类型的数据。如果没有指定类型,则删除所有数据。如果指定类型不存在,则调用该方法不会产生任何效果。
event.datatransfer.cleardata('text/uri-list');
上面代码清除事件所带的text/uri-list
类型的数据。
该方法不会移除拖拉的文件,因此调用该方法后,datatransfer.types
属性可能依然会返回files
类型(前提是存在文件拖拉)。
注意,该方法只能在dragstart
事件的监听函数之中使用,因为这是拖拉操作的数据唯一可写的时机。
datatransfer.setdragimage()
拖动过程中(dragstart
事件触发后),浏览器会显示一张图片跟随鼠标一起移动,表示被拖动的节点。这张图片是自动创造的,通常显示为被拖动节点的外观,不需要自己动手设置。
datatransfer.setdragimage()
方法可以自定义这张图片。它接受三个参数。第一个是<img>
节点或者<canvas>
节点,如果省略或为null
,则使用被拖动的节点的外观;第二个和第三个参数为鼠标相对于该图片左上角的横坐标和右坐标。
下面是一个例子。
/* html 代码如下 <div id="drag-with-image" class="dragdemo" draggable="true"> drag me </div> */ var div = document.getelementbyid('drag-with-image'); div.addeventlistener('dragstart', function (e) { var img = document.createelement('img'); img.src = 'http://path/to/img'; e.datatransfer.setdragimage(img, 0, 0); }, false);
到此这篇关于javascript 拖拉时间之drag案例详解的文章就介绍到这了,更多相关js 拖拉时间之drag内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
上一篇: 怎么解决option设置display:none不兼容ie的问题?
下一篇: 童颜无忌