木灵鱼儿
阅读:1495
Copy 一个复制操作的类
前言
js有一个31k多的star的开源复制库:clipboard.js;但是一些简单复制并不想安装一个库来解决,所以就想自己写一个。
copy所需要的东西
Selection 对象
用于获取被用户选中的部分,通过toString()
方法可以获取被选中的文本内容,以及js操作选中。
MDN文档:Selection
execCommand 对象
用于以命令的形式来操作网页的内容,说白了就是用它来实现复制文本操作,复制的是选中的文本
MDN文档:execCommand
需要注意的是,execCommand在未来将会被遗弃,因为这个api本身是从ie浏览器那边继承的,久而久之各大浏览器都对其做了兼容,虽然说遗弃,但是目前除了它没有别的办法进行复制操作。
暂时没有可用的替代方法。
确切的说这个 API 本来也不是标准 API,而是一个 IE 的私有 API,在 IE9 时被引入,后续的若干年里陆续被 Chrome / Firefix / Opera 等浏览器也做了兼容支持,但始终没有形成标准。
这个 API 被废弃的主要原因第一个就是安全问题,在用户未经授权的情况下就可以执行一些敏感操作,这就很恐怖了;第二个问题是因为这是一个同步方法,而且操作了 DOM 对象,会阻塞页面渲染和脚本执行,因当初还没 Promise,所以没设计成异步,挖坑了。新设计的 API 肯定是要解决这两个问题。
不过 W3C 也正在拟草案,大概率以后会引入一个叫Clipboard
的类型(Chrome 66.0 开始已经有这个类型了,不过还不能用,相关 API 仅存在于文档中),用来处理跟剪贴版相关的操作,不过之后肯定会是像现在获取地理位置啊、麦克风啊什么的,浏览器先会弹出一个对话框让用户授权,你才能读写剪贴板了。
- W3C Clipboard 草案:https://www.w3.org/TR/clipboa...
- Google Clipboard API 文档:https://developers.google.com...
- MDN Clipboard API 文档:https://developer.mozilla.org...
出自: 如果document.execCommand被正式删除,用什么可以实现复制到剪贴板功能呢?
功能要求
传入普通文本,数字,dom元素都能进行复制操作,dom就复制其文本内容,一般用于复制dom内的文本内容。
抛出一个promise对象,方便捕获
Copy类源码
class Copy {
constructor(data) {
return this.init(data);
}
//初始化
init(data) {
return new Promise((resolve, reject) => {
let copyVal = "";
let element = null;
if (this.isNode(data)) {
//元素
if (data.nodeName === 'SELECT') {
copyVal = data.value;
element = this.createElemnt(copyVal);
document.body.appendChild(element);
element.select();
element.setSelectionRange(0, copyVal.length);
} else if (data.nodeName === 'INPUT' || data.nodeName === 'TEXTAREA') {
const isReadOnly = data.hasAttribute('readonly'); //是否存在只读属性(只读可以防止移动端键盘被拉起)
if (!isReadOnly) {
data.setAttribute('readonly', '');
}
data.select();
data.setSelectionRange(0, data.value.length);
if (!isReadOnly) {
data.removeAttribute('readonly', '');
}
copyVal = data.value;
} else {
if (data.hasAttribute('contenteditable')) {
data.focus();
}
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(data);
selection.removeAllRanges();
selection.addRange(range);
copyVal = selection.toString();
}
} else if (typeof data === 'string' || typeof data === 'number') {
//纯文本&数字
copyVal = typeof data === 'number' ? String(data) : data;
element = this.createElemnt(copyVal);
document.body.appendChild(element);
element.select();
element.setSelectionRange(0, copyVal.length);
} else {
return reject({ msg: "不支持复制的参数", copyVal, origin: data });
}
const status = document.execCommand('copy');
element && element.remove(); //移除创建的dom
!element && data.blur(); //取消聚焦
window.getSelection().removeAllRanges(); //取消选中
if (status) {
return resolve({ msg: "复制成功", copyVal, origin: data });
} else {
return reject({ msg: "复制失败", copyVal, origin: data });
}
})
}
//是否node
isNode(value) {
return value !== undefined && value instanceof HTMLElement && value.nodeType === 1;
}
//创建textarea
createElemnt(value) {
const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
const element = document.createElement("textarea");
// Prevent zooming on iOS
element.style.fontSize = '12pt';
// Reset box model
element.style.border = '0';
element.style.padding = '0';
element.style.margin = '0';
// Move element out of screen horizontally
element.style.position = 'absolute';
element.style[isRTL ? 'right' : 'left'] = '-9999px';
// Move element to the same position vertically
const yPosition = window.pageYOffset || document.documentElement.scrollTop;
element.style.top = `${yPosition}px`;
element.setAttribute('readonly', '');
element.value = value;
return element;
}
}
使用
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
new Copy(document.querySelector("code")).then((res) => {
console.log(res)
}).catch(error => console.log(error));
});
一些特别的地方:
ios端复制内容不全的问题
data.select();
data.setSelectionRange(0, data.value.length);
当data是input或者textarea元素时,虽然select()
可以让他选中所有文本,但是在ios端无法选中所有文本,所以还需要使用setSelectionRange
移动端会拉起键盘
data.setAttribute('readonly', '');
通过设置表单元素的readonly
属性,可以解决这个问题
复制指令也是有状态的
document.execCommand('copy'); //true||false
表示复制成功或者失败
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
了解前端的复制功能
前言前端的复制功能第一次使用的时候还记忆犹新,那时根本不知道怎么操作,复制它到底是怎么实现的,该怎么去触发,百度查看了不知道多少所谓的复制功能文档,每篇都有不一样的用法,我总想着有一天能搞清楚这些,毕竟现在复制功能已经web功能开发中离不开了。execCommand API浏览器在document对象上暴露了execCommand方法,该方法允许使用者通过输入“命令”的方式来操作 可编辑内容区域 的元素。可编辑内容区域被官方认为是contenteditable="true"的html元素,但按道理,input这些应该也是算可编辑内容区域的。这些都不重要,我们这次关注的...

Promise失败重试,可指定重试次数
//模拟异步请求 function axiosFn() { return new Promise((resolve, reject) => { const flge = Math.random(); //随机值 setTimeout(() => { //大于0.7就是成功 if (flge > 0.7) { return resolve(flge); } else { return reject(...
animation 动画的三个事件
const div = document.querySelector("div.box"); div.addEventListener("animationstart", function() { //动画开始运行触发 }); div.addEventListener("animationiteration", function() { //动画每执行一次触发一次,适用用多次动画 }); div.addEventListener("animationend", function() { //...
手写Promise
/* * @Author: mulingyuer * @Date: 2021-12-30 22:06:58 * @LastEditTime: 2022-01-03 05:22:30 * @LastEditors: mulingyuer * @Description: 手写promise * @FilePath: \undefinedc:\Users\13219\Desktop\promise.js * 怎么可能会有bug!!! */ /** * @description: 自定义promise * @param {fucntion} executor 执行器函数(同...
深度合并对象的方法
找了很久,现有的库有两个:1. Mergenpm地址: Merge用法:import merge from 'merge'; const obj1 = { name: 2}; const obj2 = { value: 1 }; //合并 merge(obj1,obj2); console.log(obj1); //{name:2,value:1} //克隆合并-与目标对象无关联 const obj3 = merge(true,obj1,obj2); console.log(obj3); //{name:2,value:1} console.log(obj3 === obj1)...
利用JSON过滤对象和数组中指定的key属性
有时候我们在vue中进行for循环,就会涉及到绑定唯一值key的问题,但是并不是任何时候都会存在所谓的唯一值,使用index下标明显是不合适的,官方也不推荐,除非你for循环出来的列表不用变化。所以一般常用的做法就是给for循环的对象添加一个属性,属性的值是随机的uuid或者时间戳。这样前端问题解决了,如果遍历的数据还需要提交到后端,那么不就多了一个属性,这个属性后端不需要的。所以,我们需要在提交数据前,对数据进行过滤。过滤又得for循环删除?那怎么行,有没有那种通用的,简单的方法。过滤方法/** * @description: 过滤对象中指定的属性,也可以拿来浅拷贝 * @para...
对象扁平化
前言后端返回给前端的数据,有时候会是一个多层级对象,但是我们前端使用的时候,for循环遍历渲染时,多层级对象往往需要进行单独处理,因为还需要判断这个key值是否存在,否则会报错。强行让后端改变数据结构又好像不现实,无奈,只有自己处理了。掘金看到一位大佬文章《【算法】JS 实现对象的扁平化》感觉很合适,逻辑清晰。要求将对象中的层级扁平化,改成如下格式:// 实现一个 flatten 函数,实现如下的转换功能 const obj = { a: 1, b: [1, 2, { c: true }], c: { e: 2, f: 3 }, g: null, }; // 转换为 l...
javascript 生成随机密码,指定位数,难度(大小写、数字、特殊字符)
原理也没啥好藏着掖着的,就是为了保证密码难度,开头的字符一定是满足安全需要的,比如要求大小写数字加特殊字符,那么开头四位就一定是:大写一位、小写一位、数字一位、特殊字符一位;然后剩下的随机。/** * @description: 随机密码 * @param {*} len 密码位数 * @param {*} mode 密码难度:hide(大小写数字特殊字符)、medium(大小写数字)、low(小写数字) * @Date: 2021-07-02 15:52:32 * @Author: mulingyuer */ export const randomPass = functi...