你可能还不知道的复制(Copy) Clipboard API

你可能还不知道的复制(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那张图片,然后进行处理


1.png

微信截图粘贴(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:一个类似数组的对象,包含了所有剪贴项,不过通常只有一个剪贴项。

参考资料:
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容