自定义菜单实现复制粘贴的功能

之前有遇到的项目需求是,要在移动端,添加白名单的输入框,要求不能调出系统默认键盘同时还要满足可以复制粘贴的行为。这个到现在我也只是实现了复制功能而已。虽然没有实现全部功能,但在这里还是要记录一下,记录自己这两天的心路历程。觉得没有实现不想看的就此可以关闭该网页了。想看的可以看看我得思路和最终为啥没实现。
我们都知道,输入框一般会用 input 的标签来实现,那么 input type为text的一定会调出系统的默认键盘,若不想调用的话,那该怎么办?
我的两种方法:

1
2
3
4
5
6
7
8
//1. 设置readonly
<input type="text" readonly="readonly"/>
//2. 获取焦点的时候,就令其失焦(注意:我这里是用的jquery)
<input id="txt_input" type="text" readonly="readonly"/>
$("#txt_input").focus(function(){
document.activeElement.blur(); //这个也可以写$(this).blur();一样的效果
});

上面两种方法就能达到用 input 标签而不调出键盘了。那么问题来了,input 失焦或是只读的状态,那么也不能实现长按调出菜单的复制粘贴功能。(只是为了不调出键盘的话,也可以不用 input 标签,可以用 div 或其他标签进行模拟)

好吧,用 input 不能达到我的目的,那我不用好了吧,我自己模拟一个输入框,然后自己模拟一个长按的菜单进行选择好不啦。
首先,来实现长按弹出菜单,上代码:(嘻嘻嘻,样式没有怎么修饰,主要看效果)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>clip复制粘贴测试</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<script src="http://code.jquery.com/jquery-latest.js"></script>
<style>
*{
margin: 0;
padding: 0;
}
html{
font-size: 20px;
}
#clip{
width:50vw;
height: 20vh;
background-color: #abcdef;
}
ul,li{
list-style: none;
}
#menu{
background-color: pink;
width:30vw;
display: none;
}
#menu li{
line-height: 30px;
text-align: center;
}
#menu li:hover{
background-color: rgba(0,0,0,0.4);
color: #fff;
}
</style>
</head>
<body>
<div id="clip">
测试复制
</div>
<ul id="menu">
<li id="copy_btn">复制</li>
<li id="paste_btn">粘贴</li>
</ul>
<textarea name="" id="txt" cols="30" rows="10"></textarea>
</body>
</html>

js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//长按模拟
$.fn.longPress = function(fn) {
var timeout = undefined;
var $this = this;
for(var i = 0;i<$this.length;i++){
$this[i].addEventListener('touchstart', function(event) {
timeout = setTimeout(fn, 800); //长按时间超过800ms,则执行传入的方法
}, false);
$this[i].addEventListener('touchend', function(event) {
clearTimeout(timeout); //长按时间少于800ms,不会执行传入的方法
}, false);
}
}
//阻止系统默认的菜单行为
$(document).bind("contextmenu",function(e){
return false;
});
//模拟长按调出菜单,这里注意要禁掉原本的长按调出菜单的事件
$("#clip").longPress(function(){
console.log("clip");
$("#menu").show(50);
});

上面两段代码的结合就可以实现,长按调出我的自定义菜单。
自定义菜单可以调出来了,那就实现复制粘贴的功能吧。
window.clipboardData (IE 才有) 这个就不说了。
ZeroClipboard (借助Flash) 这个也太胖了,不想说。
clipboard.js (很轻小,min文件11kb)下载地址
其实clipboard.js的核心也是 document.ExecCommand(“copy”) 这个,浏览器支持返回 true,不支持或者没启用返回 false。
clipboard.js 是对 document.ExecCommand(“copy”) 做的封装和兼容。

那用这个来看一下实现过程吧。因为 clipboard.js 只是支持复制和剪切的功能,下面以复制为例。

1
2
3
4
5
6
7
8
//上面的ul列表换成这个
<ul id="menu">
<li id="copy_btn" data-clipboard-target="#clip" data-clipboard-action="copy">复制</li>
<li id="cut_btn" data-clipboard-target="#clip" data-clipboard-action="cut">剪切</li>
<li id="paste_btn">粘贴</li>
</ul>
//data-clipboard-target 要操作的目标对象
//data-clipboard-action 要进行的行为

//实现复制的功能

1
2
3
4
5
6
7
8
//实现长按的复制功能
var clipboard = new Clipboard("#copy_btn"); //copy_btn 按键的操作
clipboard.on("success",function (element) {//复制成功的回调
console.info("复制成功,复制内容: " + element.text);
});
clipboard.on("error",function (element) {//复制失败的回调
console.info(element);
});

到这里就实现了复制的功能。 clipboard.js 不支持 粘贴paste 的功能;而且剪贴板属于系统,浏览器处于安全的考虑是不允许js访问剪贴板的内容的,那你随便去访问不出问题才怪。但是触发粘贴事件(比如“ctrl+v”快捷键)是可以拿到剪切板的内容的,那我就想,ctrl+v 这个快捷键不是可以进行复制粘贴么,那我就模拟快捷键的这个行为来触发粘贴的行为呗。理想是很美好的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//关于键盘模拟,我的[KeyboardEvent对象(键盘事件),更改键值](https://yhzy888.github.io/2017/10/22/keyboard.html/)这篇文章有介绍。
document.my_onkeydown = function(e)
{
var keyCode = e.keyCode;
if(document.fireEvent && typeof document.fireEvent == "function")
{
target.fireEvent("onkeydown");
}
else if(document.createEvent && typeof document.createEvent == "function")
{
//创建 KeyboardEvent 对象
var eventObject = document.createEvent('KeyboardEvent');
//初始化一个 KeyboardEvent 对象
eventObject.initKeyboardEvent(e.type,true, true,document.defaultView,false,false,true,false,e.keyCode,0);
//先删掉原来的 keyCode 的属性
delete eventObject.keyCode;
delete eventObject.which;
delete eventObject.code;
delete eventObject.key;
//重新定义 keyCode 的属性,且属性值为传过来的值
Object.defineProperty(eventObject,"keyCode",{"value":e.keyCode});
Object.defineProperty(eventObject,"which",{"value":e.keyCode});
Object.defineProperty(eventObject,"code",{"value":e.code});
Object.defineProperty(eventObject,"key",{"value":e.key});
//给 document 节点分配一个合成事件
document.dispatchEvent(eventObject);
}
}
document.onkeydown = function(event)
{
console.log(event)
console.log(event.type)
console.log(event.keyCode)
}
$("#paste_btn").on("click",function(){
document.my_onkeydown({type:'keydown',keyCode:17,code:"ControlLeft",key:"Control"});
document.my_onkeydown({type:'keydown',keyCode:86,code:"KeyV",key:"v"});
});
$("#paste_btn").on("paste",function(e){
console.log("paste");//触发了paste事件的话,paste是能打印出来的
var eve = e.originalEvent;//所有js的原生事件都被保存到originalEvent中
var cp = eve.clipboardData;//从originalEvent取出剪切板的事件
var clipboardData = window.clipboardData||e.originalEvent.clipboardData; //兼容ie||chrome
var data = clipboardData.getData('Text');//获取剪切板内容
console.log(data);
// var pasteData = window.clipboard.clipboardAction.selectedText;
// console.log(pasteData);
})

现实很骨感,键值是模拟了,但是并不能触发 paste 事件,这也是一个安全机制的考虑,用户操作按键的行为,KeyboardEvent的isTrusted值为true,表示是用户操作,认为是可信任的,而模拟的键盘事件isTrusted值为false,认为行为不可信任。这也就是为什么不能触发paste事件了。这种处于安全的考虑是无可厚非的,那么我的功能就此也是没有实现了。
如果有什么好办法,欢迎跟我分享下,qq:1790899324。