你可能还不知道的复制(Copy) -- Js
<font color=red>!!! PC & 客户端<font color=#000>
PS:你有没有被复制、粘贴图片或文字而感到烦恼, sparrow 来解决你终身疾病,赶快报名吧, 人员有限 饥渴难耐啊 Bro!
其实今天的主题就是要围绕着js里面的“复制”这一专业名词来概述全文。
「引言」PM: 我们这期来阐述一下1.6客户端需求, 巴拉巴拉... 在输入框里面粘贴图片、文字、文件功能,下期我们还会增加“复制”图片、文字功能, 再送给你一个拖拽文件图片文字到输入框功能... 我懵 PM: 哦 对了 还要兼容微信等其他粘贴图片的情况.... mmpmmpmmp
就是这个不到一炷香的时间,引发了思考, 咋做咋做咋做??粘贴倒是还有点眉目,paste
事件嘛 再根据里面的类型做处理就好了, 拖拽也还行drag
而已了 我在送给你一个进来时候背景变色, 但是 复制怎么做? 文字好像用上古年代的execCommand
应该就是没多大问题吧,但是鬼图片 还要兼容微信的粘贴功能 甚至还有wps 浏览器各种情况下的兼容, 这咋搞 ?? 听我潺潺道来 (三天两夜几百根头发换来的)
Overview
- 粘贴图片格式、规则
- 微信聊天图片粘贴(Rule)
- 微信截图粘贴(Rule)
- 拖拽
- 复制
- 复制文字
- 复制图片
粘贴图片格式、规则
粘贴图片所监听的event
里会有不同的类型格式,在某个软件里粘贴出来,可能要遵循人家内部复制图片所谓的规则,目前我遇见的只有微信这个 78(路人的眼光)。
微信聊天图片粘贴(Rule)
这就是微信聊天页面里面的图片粘贴, (随便在微信找个带有图片的聊天窗口,然后右键复制)然后... 撸代码啊
// 这里我主要是应用富文本来实现的输入框正常操作
<div
class={["el-textarea", this.changeInputBackground ? 'background' : '']}
ref={'messageContent'}
spellcheck="false"
vScrollbar
contentEditable="true"
onInput={e => this.messageInput(e)}
on-keydown={(e) => this.keydown(e)}
placeholder=""
on-focus={e => this.keepLastIndex(e.target)}
on-drop={e => this.drop(e)}
on-paste={e => this.paste(e)}
on-blur={e => this.textareaBlur(e)}
on-dragenter={e => this.dragEnter(e)}
on-dragleave={e => this.dragLeave(e)}
domPropsInnerHTML={this.vmodelMessage}
></div>
/**
* 粘贴事件
*/
paste(event) {
event.preventDefault()
let * = this.$refs.messageContent // 输入框内容 * 随便写的
var clipboardData = event.clipboardData || window.clipboardData
const items = clipboardData.items
const text = clipboardData.getData('text/plain') // 获取文本
let apptext = document.createTextNode(text)
let i = 0, tl = items.length;
let imagesblob = [] // 这里写了几种类型 bolb加密、 base64 、 formdata 这个可以上传时候和服务端做协商
let imagesbase64 = []
// var fd = new FormData(document.forms[0]);
for (; i < tl;) {
if (items[i].kind == 'file') {
let file = items[i].getAsFile();
if (file.type.indexOf('image') > -1) {
let blob = URL.createObjectURL(file) // 创建bolb
let el = document.createElement('img')
el.title = file.name
el.src = blob
el.dataset.name = file.name
if (file.path.length == 0 && tl == 1) { // 剪切板情况
var reader = new FileReader();
reader.onload = function (event) {
el.dataset.path = event.target.result
el.dataset.type = '2' // 2是没有path时候发送
}
reader.readAsDataURL(file);
imagesblob.push(el)
} else {
if (file.path.length !== 0) {
el.dataset.path = file.path
el.dataset.type = '1' // 1是正常发送
imagesblob.push(el)
}
}
// var reader = new FileReader();
// reader.onload = function (event) {
// var base64_str = event.target.result;
// imagesbase64.push(base64_str);
// }
// reader.readAsDataURL(file);
// fd.append('file' + i, file)
}
}
i++
}
*.append(apptext) // 添加文本
if (imagesblob.length > 0) {
for (let j = 0; j < imagesblob.length; j++) {
*.append(imagesblob[j]) // 添加图片
}
}
this.keepLastIndex(*) // 光标滞后
},
其实这个还好 但是你永远不知道会有什么魔鬼出现 比如微信输入框里面复制的时候会出现两个图片, 然后第一张图解析出来之后没有path, 第二张会有path, 这样一来你肯定会判断没有path那张图片,然后进行处理
微信截图粘贴(Rule)
还有一种情况, 是屏幕截图的时候, 你会发现这个图片使用二进制存在这个剪切板里面的 没有路径储存, 他就是一串乱码,然后 会发现有冲突, 跟刚才的情况有很大的冲突... 上边代码解决了都 我也忘记咋写的了(好几个月前写的了)
拖拽
献上一份垃圾代码
drop(event) {
event.preventDefault()
let imgTypeFn = file => file.type.indexOf('image') > -1
let items = event.dataTransfer.files // 获取拖拽的所有文件
// event.dataTransfer.getData('text/plain') //获取拖拽进来的文字
let * = this.$refs.messageContent
const text = event.dataTransfer.getData('text/plain')
let apptext = document.createTextNode(text)
let i = 0, tl = items.length;
let imagesblob = []
for (; i < tl;) {
if (imgTypeFn(items[i])) {
let blob = URL.createObjectURL(items[i])
let el = document.createElement('img')
el.title = items[i].name
el.src = blob
el.dataset.name = items[i].name
el.dataset.path = items[i].path
imagesblob.push(el)
}
i++
}
*.append(apptext)
if (imagesblob.length > 0) {
for (let j = 0; j < imagesblob.length; j++) {
*.append(imagesblob[j])
}
}
this.keepLastIndex(*)
},
好了 主角上场了
复制
其实在此之前,还是查了一下网上的各种资料, 也有很多很好的工具库实现了这个功能;比如 clipboard.js; 还有原生方法 document.execCommand('copy')
、event.clipboardData (copy) (paste)
、Navigator.clipboard
前面那两个我们应该不是很陌生,今天主要说后面那个新增的。PS:其实我很想更细化些搞出点东西,但是时间节点不允许我这样做,so... 搓搓手凑活看吧
额... 我这里直接说今天的重点吧 Navigator.clipboard
剪贴板 这是新版本的剪切、复制、粘贴
剪贴板API
浏览器允许 JavaScript 脚本读写剪贴板,自动复制或粘贴内容。
一般来说,脚本不应该改动用户的剪贴板,以免不符合用户的预期。但是,有些时候这样做确实能够带来方便,比如"一键复制"功能,用户点击一下按钮,指定的内容就自动进入剪贴板。
Document.execCommand() 方法 (阮一峰老师的日志)
Document.execCommand()是操作剪贴板的传统方法,
各种浏览器都支持。
它支持复制、剪切和粘贴这三个操作。
-
document.execCommand('copy')(复制)
// 这种选择方式就要创建input去赋值内容然后select 然后复制 const inputElement = document.querySelector('#input'); inputElement.select(); document.execCommand('copy');
document.execCommand('cut')(剪切)
-
document.execCommand('paste')(粘贴)
// 同样原理 const pasteText = document.querySelector('#output'); pasteText.focus(); document.execCommand('paste');
缺点
Document.execCommand()方法虽然方便,但是有一些缺点。
首先,它只能将选中的内容复制到剪贴板,无法向剪贴板任意写入内容。
其次,它是同步操作,如果复制/粘贴大量数据,页面会出现卡顿。有些浏览器还会跳出提示框,要求用户许可,这时在用户做出选择前,页面会失去响应。
为了解决这些问题,浏览器厂商提出了异步的 Clipboard API。
Clipboard API
Clipboard API 是下一代的剪贴板操作方法,比传统的document.execCommand()方法更强大、更合理;
它的所有操作都是异步的,返回 Promise 对象,不会造成页面卡顿。而且,它可以将任意内容(比如图片)放入剪贴板;
navigator.clipboard属性返回 Clipboard 对象,所有操作都通过这个对象进行;
const clipboardObj = navigator.clipboard;
如果navigator.clipboard属性返回undefined,就说明当前浏览器不支持这个 API。
Clipboard 对象提供的方法:
-
read()
从剪贴板读取数据(比如图片),返回一个 Promise 对象。When the data has been retrieved, the promise is resolved with a DataTransfer object that provides the data。async function getClipboardContents() { try { const clipboardItems = await navigator.clipboard.read(); for (const clipboardItem of clipboardItems) { for (const type of clipboardItem.types) { const blob = await clipboardItem.getType(type); console.log(URL.createObjectURL(blob)); } } } catch (err) { console.error(err.name, err.message); } }
-
readText()
从操作系统读取文本;returns a Promise which is resolved with a DOMString containing the clipboard's text once it's available。document.body.addEventListener( 'click', async (e) => { const text = await navigator.clipboard.readText(); console.log(text); } )
-
write()
写入任意数据至操作系统剪贴板。This asynchronous operation signals that it's finished by resolving the returned Promise。try { const imgURL = 'https://dummyimage.com/300.png'; const data = await fetch(imgURL); const blob = await data.blob(); await navigator.clipboard.write([ new ClipboardItem({ [blob.type]: blob }) ]); console.log('Image copied.'); } catch (err) { console.error(err.name, err.message); }
-
writeText()
写入文本至操作系统剪贴板。returning a Promise which is resolved once the text is fully copied into the clipboard。document.body.addEventListener( 'click', async (e) => { await navigator.clipboard.writeText('Yo') } )
!!! 上面在使用write写入图片的时候, 可能会出现点小问题, 比如blob格式不符合然后就很难达到我们的预期, 就很烦是吧
比如:Cannot read properties of undefined (reading 'substring')
、 Failed to read or decode Blob for clipboard item type image/png.
这种错误的来源就是图片格式不正确或者是 在解析的为blob格式时候出现了问题。 额.. 那就手动解析一下吧
先把图片解析成base64在解析成blob在传入ClipboardItem在传入write复制。 嗯 就这样
//先解析成base64
function imageBase64(img) {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width, img.height);
var dataURL = canvas.toDataURL("image/png");
return dataURL;
}
// 再转成blod // 注意base64里面的特殊符号 在atob的时候不能识别“,”
function base64ToBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = window.atob(b64Data);
// var byteCharacters = b64Data;
//该atob函数将base64编码的字符串解码为一个新字符串,其中包含二进制数据每个字节的字符。
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
//通过使用.charCodeAt字符串中每个字符的方法应用它来创建一个字节值数组。
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
//将此字节值数组转换为实际类型的字节数组,方法是将其传递给Uint8Array构造函数。
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
console.log(byteArrays)
//创建一个blob:包含此数据的URL,并将其显示给用户。
var blob = new Blob(byteArrays, { type: contentType });
return blob;
}
// 最后复制
copy_img.onclick = async _ => {
let base64 = imageBase64(img)
let blob = base64ToBlob(base64.replace('data:image/png;base64,', ''), 'image/png')
clipboardObj.write([
new ClipboardItem({
'image/png': blob
})
])
}
// Nice
Event.clipboardData
- Event.clipboardData.setData(type, data):修改剪贴板数据,需要指定数据类型。
- Event.clipboardData.getData(type):获取剪贴板数据,需要指定数据类型。
- Event.clipboardData.clearData([type]):清除剪贴板数据,可以指定数据类型。如果不指定类型,将清除所有类型的数据。
- Event.clipboardData.items:一个类似数组的对象,包含了所有剪贴项,不过通常只有一个剪贴项。