前言
最近项目新增需求:用户能够拖拽页面上的图片文件到word文档。
当操作浏览器里拖拽图片至别的程序,在word文档中展示出获取到的只是图片的url地址,而非预期的图片文件。在现有的拖拽事件所提供api无法满足需求的情况下,换一个思路走:尝试将图片复制到剪贴板。
对于原生js的复制操作,已有封装好的库clipboard.js,但是封装得太死,无法满足更多定制化的需求,主要表现在以下两点:
- 只接受click事件,无法绑定其他事件。
- 只复制目标节点的子节点,对于img标签,如果不额外包裹一层父元素,无法实现图片复制。
参考clipboard.js源码,了解了实现原理后(其实非常简单!:) ),我们就能自己动手封装一个复制方法:
概述
Range对象
Range表示包含节点和部分文本节点的文档片段。最常见的就是用户在浏览器拖动鼠标选择的内容(user selection)
比如上图这块蓝色高亮区域。
在现代浏览器中(IE9以上),你可以通过Document.createRange()方法或者new Range()创建一个Range对象;当需要获取user selection时,你应该使用window.getSelection()方法获取Selection对象。
有点懵?
刚了解了Range对象,而Selection对象又是什么?阅读了文档之后,还是疑惑它们之间的区别?
Selection对象表示用户的选择,而Range对象则表示文档的连续部分,与任何视觉表示无关。一个Selection对象几乎可由0到多个Range表示出来,当然,Range对象也能独立于Selection而被完全的创建和修改。
简单的演示代码
html部分:
<p>
这是一段文字
AAAAAAAAAA
BBBBBBBBBB
<span id="range">Range</span>
</p>
<input id="input" type="text">
<button id="button1">选择文字后点击</button>
<button id="button2">点击后将选中指定的节点</button>
js部分:
var btn1 = document.getElementById('button1'),
btn2 = document.getElementById('button2'),
input = document.getElementById('input'),
rangespan = document.getElementById('range');
var selection = window.getSelection(),
range = document.createRange();
btn1.addEventListener("click", function(event) {
input.value = selection.toString();
});
btn2.addEventListener("click", function(event) {
range.selectNode(rangespan);
selection.removeAllRanges(); //删除包含在selection原本的range,也就是取消用户选中的范围
selection.addRange(range); //让选中部分变成我们自己定义的节点内容
});
演示地址:
https://jsfiddle.net/muvhqcnf...
一点就会 :)
兼容陈旧IE版本
Microsoft提供了类似的TextRange接口。
在实际代码部分会展示Microsoft TextRange的基本使用。
execCommand方法
execCommand方法允许运行命令来操纵可编辑区域的内容。该方法的第一个参数是命令的名称,参数类型为DOMString。
在这里,我们将利用execCommand方法的copy命令实现复制选中的内容:
document.execCommand('copy')
execCommand API起源于IE,后来被添加到HTML5(HTML Editing APIs),在各浏览器的表现会有不同。更多请查看文档。
我们回到前面的演示代码,将btn1的点击事件替换成execCommand命令:
btn1.addEventListener("click", function(event) {
//input.value = selection.toString();
document.execCommand('copy');
});
拖动鼠标选择文字,点击按钮后看看能粘贴出什么:)
复制图片功能的具体实现
封装可以兼容ie的getSelect方法
还记得前面的例子里,我们通过range的selectNode(node)方法获取节点, 再使用selection的removeAllRanges()方法和addRange(range)将节点替换我们获取的节点。在这里,我们同样可以这样选中我们目标的img节点:
const getSelect = targetNode => {
if (window.getSelection) {
//chrome等主流浏览器
var selection = window.getSelection();
var range = document.createRange();
range.selectNode(targetNode);
selection.removeAllRanges();
selection.addRange(range);
} else if (document.body.createTextRange) {
//ie
var range = document.body.createTextRange();
range.moveToElementText(targetNode);
range.select();
}
}
派发事件
为了不浪费性能,我们使用事件委托到希望被复制的节点上。这里对传入的nodeName进行处理,方便自由的控制被复制一个或多个节点类型。默认为<img>。
const clipboardHandler = (nodeName, event) => {
event = event || nodeName; //不传参时
const type = Object.prototype.toString.call(nodeName).replace(/\[object\s|\]/g, '');
const target = event.target || event.srcElement;
var result = false;
switch (type) {
case 'String':
result = (target.nodeName.toLowerCase() === nodeName);
break;
case 'Array':
result = nodeName.some(item => target.nodeName.toLowerCase() === item);
break;
case 'Object':
nodeName = null;
default:
result = (target.nodeName === 'IMG');
}
if (result) {
//调用之前封装好的getSelect方法
getSelect(target);
document.execCommand('copy');
}
}
调用:
[element].addEventListener('mousedown', clipboardHandler); //预备拖动图片按下鼠标时执行复制
传递参数(字符串或数组):
var [somename]Handler = clipboardHandler.bind(null, [nodeName]);
[element].addEventListener([eventType],[somename]Handler);
已验证在chrome和ie8上可行(ie8需要对es6语法与bind和addEventListener方法进行pollyfill)
希望能够帮助到你:)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。