• JavaScript系列:JS实现复制粘贴文字以及图片


    一. 基于 Clipboard API 复制文字(推荐)

    基本概念

    Clipboard API 是一组用于在浏览器中操作剪贴板的 JavaScript API,它允许开发者在网页上读取和写入剪贴板内容,实现复制、剪切和粘贴等功能。Clipboard API 提供了一种在网页上读取和写入剪贴板内容的方式,包括文本、图像和其他类型的数据。Clipboard API 适用于需要与用户剪贴板进行交互的网页应用,如实现一键复制、粘贴功能,或者在用户复制特定内容时自动添加额外信息等。

    https://developer.mozilla.org/zh-CN/docs/Web/API/Clipboard_API

    主要方法

    Clipboard API 提供了几个关键的方法来实现剪贴板的读写操作:

    1. navigator.clipboard.writeText(text):将给定的文本复制到剪贴板。这是一个异步方法,会返回一个 Promise 对象,成功时 Promise 会被解析,失败时会被拒绝。
    2. navigator.clipboard.readText():从剪贴板读取文本内容。这也是一个异步方法,返回一个 Promise 对象,解析后提供剪贴板中的文本内容。
    3. navigator.clipboard.write(data):写入更复杂的数据类型到剪贴板,如文件、图像等。data 参数是一个包含 ClipboardItem 对象的数组,每个 ClipboardItem 对象代表剪贴板中的一项数据。这也是一个异步方法,返回一个 Promise 对象。
    4. navigator.clipboard.read():从剪贴板读取更复杂的数据类型,如文件、图像等。这个方法会返回一个 Promise 对象,解析后提供一个包含 ClipboardItem 对象的数组。

    使用限制

    • 用户授权:由于安全和隐私的考虑,浏览器在使用 Clipboard API 时通常需要用户授权。例如,在尝试从剪贴板读取或写入数据时,浏览器可能会要求用户明确允许。
    • 安全上下文:Clipboard API 只能在安全的环境中操作剪贴板,如 HTTPS 页面、localhost本机下。
    • 浏览器兼容性:虽然大多数现代浏览器都支持 Clipboard API,但仍有部分旧版浏览器可能不支持。因此,在使用时需要考虑浏览器的兼容性。

    实际应用示例

    
    <script setup>
    import { ref } from 'vue'
    import { ElMessage } from 'element-plus'
    
    const message = ref('复制的内容')
    
    const handleCopy = () => {
        navigator.clipboard
            .writeText(message.value)
            .then(() => {
                ElMessage({
                    message: '复制成功',
                    type: 'success',
                })
            })
            .catch((err) => {
                console.error('复制失败:', err)
                ElMessage({
                    message: '复制失败',
                    type: 'error',
                })
            })
    }
    script>
    

    二、基于 document.execCommand('copy')

    document.execCommand('copy') 是一个在网页上执行复制操作的旧式API,属于 Web API 的一部分,用于在不需要用户交互(如点击或按键)的情况下,通过脚本复制文本到剪贴板。然而,这个API在现代Web开发中已经被视为过时(deprecated),并在许多现代浏览器中受到限制或不再支持,尤其是在没有用户明确交互的情况下。

    https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand

    缺陷

    • 只能操作input, textarea或具有contenteditable属性的元素
    • execCommand 是同步操作,如果复制/粘贴大量数据,可能会导致页面出现卡顿现象,影响用户体验。
    • 它只能将选中的内容复制到剪贴板,无法向剪贴板任意写入内容
    • 有些浏览器还会跳出提示框,要求用户许可,这时在用户做出选择前,页面会失去响应。

    实际应用示例

    
    <script setup>
    import {
        copyText,
        copyImage,
        imageUrlToBase64,
        parseBase64,
    } from './common/copy'
    import { ref } from 'vue'
    import { ElMessage } from 'element-plus'
    const message = ref('复制的内容')
    
    const handleCopy2 = () => {
        // 动态创建 textarea 标签
        const textarea = document.createElement('textarea')
        // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
        textarea.readOnly = 'readonly'
        textarea.style.position = 'absolute'
        textarea.style.left = '-9999px'
        textarea.style.opacity = '0'
        // 将要 copy 的值赋给 textarea 标签的 value 属性
        textarea.value = message.value
        // 将 textarea 插入到 body 中
        document.body.appendChild(textarea)
        // 选中值并复制
        textarea.select()
        const result = document.execCommand('Copy')
        if (result) {
            ElMessage({
                message: '复制成功',
                type: 'success',
            })
        }
        document.body.removeChild(textarea)
    }
    script>
    

    说明

    clipboard.js 底层也是基于 document.execCommand去实现的

    function createFakeElement(value) {
      var isRTL = document.documentElement.getAttribute('dir') === 'rtl';
      var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS
    
      fakeElement.style.fontSize = '12pt'; // Reset box model
    
      fakeElement.style.border = '0';
      fakeElement.style.padding = '0';
      fakeElement.style.margin = '0'; // Move element out of screen horizontally
    
      fakeElement.style.position = 'absolute';
      fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically
    
      var yPosition = window.pageYOffset || document.documentElement.scrollTop;
      fakeElement.style.top = "".concat(yPosition, "px");
      fakeElement.setAttribute('readonly', '');
      fakeElement.value = value;
      return fakeElement;
    }
    var fakeCopyAction = function fakeCopyAction(value, options) {
      var fakeElement = createFakeElement(value);
      options.container.appendChild(fakeElement);
      var selectedText = select_default()(fakeElement);
      command('copy');
      fakeElement.remove();
      return selectedText;
    };
    

    三、复制图片功能

    
    <script setup>
    import { ref } from 'vue'
    import { ElMessage } from 'element-plus'
    const message = ref('复制的内容')
    
    const handleCopyImage = async () => {
        //具体看下面的封装
        await copyImage('https://cn.vitejs.dev/logo-with-shadow.png')
        ElMessage({
            message: '复制成功',
            type: 'success',
        })
    }
    script>
    

    四、封装

    /**
     * 图片转base64
     * @param {string} 图片地址
     * @returns
     */
    export const imageUrlToBase64 = (imageUrl) => {
        return new Promise((resolve, reject) => {
            let image = new Image()
            image.setAttribute('crossOrigin', 'Anonymous')
            image.src = imageUrl
            image.onload = function () {
                const canvas = document.createElement('canvas')
                canvas.width = image.width
                canvas.height = image.height
                const context = canvas.getContext('2d')
                context.drawImage(image, 0, 0, image.width, image.height)
                const base64Str = canvas.toDataURL('image/png')
                resolve(base64Str)
            }
            image.onerror = function (e) {
                reject(e)
            }
        })
    }
    
    /**
     * 转换base64
     * @param {string} base64
     * @returns
     */
    export function parseBase64(base64) {
        let re = new RegExp('data:(?.*?);base64,(?.*)')
        let res = re.exec(base64)
        if (res) {
            return {
                type: res.groups.type,
                ext: res.groups.type.split('/').slice(-1)[0],
                data: res.groups.data,
            }
        }
    }
    
    /**
     * 复制文字
     * @param {string} text  要复制的文本
     * @returns {boolean} true/false
     */
    export const copyText = async (text) => {
        if (navigator && navigator.clipboard) {
            await navigator.clipboard.writeText(text)
            return true
        }
        // 动态创建 textarea 标签
        const textarea = document.createElement('textarea')
        // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
        textarea.readOnly = 'readonly'
        textarea.style.position = 'absolute'
        textarea.style.left = '-9999px'
        textarea.style.opacity = '0'
        // 将要 copy 的值赋给 textarea 标签的 value 属性
        textarea.value = text
        // 将 textarea 插入到 body 中
        document.body.appendChild(textarea)
        // 选中值并复制
        textarea.select()
        const result = document.execCommand('Copy')
        document.body.removeChild(textarea)
        return result
    }
    
    /**
     * 复制图片
     * @param {string} imageUrl 图片地址
     * @param {boolean} isBase64 是否是base64
     */
    export const copyImage = async (imageUrl, isBase64 = false) => {
        let base64Url = ''
        if (!isBase64) {
            base64Url = await imageUrlToBase64(imageUrl)
        } else base64Url = imageUrl
        const parsedBase64 = parseBase64(base64Url)
        let type = parsedBase64.type
        //将base64转为Blob类型
        let bytes = atob(parsedBase64.data)
        let ab = new ArrayBuffer(bytes.length)
        let ua = new Uint8Array(ab)
        for (let i = 0; i < bytes.length; i++) {
            ua[i] = bytes.charCodeAt(i)
        }
        let blob = new Blob([ab], { type })
        navigator.clipboard.write([new ClipboardItem({ [type]: blob })])
    }
    

    __EOF__

  • 本文作者: 书山有路勤为径,学海无涯苦作舟
  • 本文链接: https://www.cnblogs.com/vic-tory/p/18304227
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    CocosCreator-常见问题和解决-持续更新
    JUC_Future异步回调
    网络安全学习--入侵检测和紧急响应
    优先队列的实现原理
    【LeetCode】48. 旋转图像
    [033量化交易] python获取每股收益 净利润 总股本
    入门Python,看完这篇就行了!
    网络基础之路由详解
    Gitlab使用
    大语言模型(LLM)综述(六):大型语言模型的基准和评估
  • 原文地址:https://www.cnblogs.com/vic-tory/p/18304227