wangEditor粘贴从word复制的带图片内容的最佳实践

  • 原创
  • 作者:程序员三丰
  • 发布时间:2022-07-28 14:29
  • 浏览量:1530
wangEdior 对于粘贴从 word 复制的带图片内容支持并不是很好,尤其是会丢掉图片。最近项目中有这个需求,经过查阅大量资料,和代码调试,基本上实现了需求,过程中发现网上这方面的资料比较少,借此花点时间整理出来,一方面个人备忘,一方面分享出来,抛砖引玉,期待更好的解决方案!

为什么要写这篇文章

  • 首先源自于实际项目的客户需求,真实且刚需。
  • 本人在网上查找了很多相关资料,也对比和参考了其他类似的文本编辑器,才实现到本文实现的效果。提前声明,本文没有做到百分百粘贴前后同样的效果,介意者慎入!!以免浪费您的宝贵时间。
  • 基于 wangEditor 免费开源的前提下实现,没有任何需要付费或使用限制。
  • 出于整理收藏、个人积累,分享出来,抛砖引玉。

基于 Layui 本地安装 wangEditor 最新版本

不建议使用官网的 CDN,亲测不是很稳定。官网安装文档

下载 JS 和 CSS 文件

  • 在任意位置新建一个 test1 文件夹,打开控制台,目录定位到该文件夹,执行 npm install @wangeditor/editor 或 yarn add @wangeditor/editor;
  • 安装完成,打开 node_modules/@wangeditor/editor/dist 文件夹,即可找到 JS CSS 文件:
    • index.js
    • css/style.css
  • 把上面两个文件拷贝到你的项目中。

在 Layui 中创建 wangEditor

  • 新建一个引入 Layui 的 HTML 文档
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <title>Page title</title>
    <link href="/lib/layui-2.7.0/css/layui.css" rel="stylesheet">
    <style>
    </style>
</head>

<body>
    <script src="/lib/layui-2.7.0/layui.js"></script>
    <script>
    </script>
</body>

</html>
  • 定义编辑器 html 结构,并引入 css 和 js
    编辑器和工具栏是强制分离的,所以需要定义两个 div。此时代码结构如下。
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <title>Page title</title>
    <link href="/lib/layui-2.7.0/css/layui.css" rel="stylesheet">
    <link href="/lib/wangeditor/style.css" rel="stylesheet">
    <style>
    </style>
</head>

<body>
    <div>
        <!-- 工具栏 -->
        <div id="toolbar-container"></div>
        <!-- 编辑器 -->
        <div id="editor-container"></div>
    </div>
    <script src="/lib/layui-2.7.0/layui.js"></script>
    <script src="/lib/wangeditor/index.js"></script>
    <script>
    </script>
</body>

</html>
  • 创建编辑器(主要是 JS 代码实现)
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <title>Page title</title>
    <link href="/lib/layui-2.7.0/css/layui.css" rel="stylesheet">
    <link href="/lib/wangeditor/style.css" rel="stylesheet">
    <style>
        body {
            padding: 20px;
        }
    </style>
</head>

<body>
    <div style="border: 1px solid #e1e1e1;">
        <!-- 工具栏 -->
        <div id="toolbar-container" style="border-bottom: 1px solid #e1e1e1;"></div>
        <!-- 编辑器 -->
        <div id="editor-container" style="height: 400px;"></div>
    </div>
    <script src="/lib/layui-2.7.0/layui.js"></script>
    <script src="/lib/wangeditor/index.js"></script>
    <script>
        const {
            createEditor,
            createToolbar
        } = window.wangEditor;

        // 编辑器配置
        const editorConfig = {};
        editorConfig.placeholder = '请输入内容';

        // 工具栏配置
        const toolbarConfig = {};

        // 创建编辑器
        const editor = createEditor({
            selector: '#editor-container',
            config: editorConfig,
            mode: 'default'
        });

        // 创建工具栏
        const toolbar = createToolbar({
            editor,
            selector: '#toolbar-container',
            config: toolbarConfig,
            mode: 'default'
        });
    </script>
</body>

</html>

至此 wangEditor 编辑器已经创建成功了。刷新页面后,就可以看到和官网一样界面效果了。

wangEditor 实现 word 带图片格式内容粘贴

场景描述

  • 新建一个 word 文档,并输入常规内容,如带样式的文本内容、列表、表格,并插入图片,然后选中复制;
  • 打开上面创建的编辑器页面,点击编辑器输入区域,然后 ctrl+v 粘贴。

结果: 文字样式部分丢失,表格尚可,列表溢出,图片直接丢失。

分析思路

针对上面的实践结果,逐个寻找解决方案:

  • 样式丢失

    • 体现:如文本样式部分丢失、列表溢出等。
    • 为什么会丢失呢?

      • 这个问题其实很容易回答,word 中的文本样式肯定与我们平时写的 HTML 样式有差异,会导致样式丢失。

      • 另外一方面,我们平时写的 HTML 格式非常灵活,但是 wangEditor 无法兼容所有的 HTML 格式,这一点官方文档有特别标红说明。也就是说,我们在编辑器输入内容时,wangEdior 会做一些处理(过滤,筛选,转换等)。

      例如,wangEditor 可以识别 <strong>hello</strong> 为加粗,但无法识别 <span style="font-weight: bold;">hello</span> 等其他加粗方式。

    • 解决措施

      • 了解差异,对内容样式做额外的处理,使其尽量符合 wangEditor 支持的格式。
      • 代码实践(这只是我的思路,你还可以有更好的实现思路~~)
      // 例如缩进会超出边框,直接过滤掉相关样式即可
      html = html.replace(/text\-indent:\-(.*?)pt/gi, '')
      
  • 图片丢失

    • 为什么丢失呢?
      这个问题比较复杂,需要我们先了解复制粘贴的原理,真是情况和我们想的完全不一样,它不是简单的把 A 的内容作为一个整体一次性复制到编辑器中,而是将 A 的内容中图片的标签和图片的内容(数据)分成两部分分别以不同方式传输。此处简单说明一下,下面关键点部分做详细介绍。
    • 解决措施
      其实思路很清晰,就是我们将图片的内容复制到编辑器中,而且还可以正常显示。这块比较复杂,此处只做个概念介绍,,下面关键点部分做详细介绍。

关键点:图片如何粘贴

【开门见山】

通过 wangEditor 的编辑器配置 API 中的 customPaste 自定义粘贴

通过该 API 可以阻止编辑器的默认粘贴处理逻辑,可以实现自己的粘贴逻辑。

即:这里的自己的粘贴逻辑,就是解决图片粘贴的关键所在。

~~原来,其实道理就这么简单!!

最终实现代码(因为仅涉及 JS 代码,所以只提供 JS 代码)

  • 自定义 wangEditor 粘贴
// 其他代码
....

<script>
    const {
        createEditor,
        createToolbar
    } = window.wangEditor;

    // 编辑器配置
    const editorConfig = {};
    editorConfig.placeholder = '请输入内容';

    // 自定义粘贴
    editorConfig.customPaste = (editor, event) => {

        // 获取粘贴的html部分(??没错粘贴word时候,一部分内容就是html),该部分包含了图片img标签
        let html = event.clipboardData.getData('text/html');

        // 获取rtf数据(从word、wps复制粘贴时有),复制粘贴过程中图片的数据就保存在rtf中
        const rtf = event.clipboardData.getData('text/rtf');

        if (html && rtf) { // 该条件分支即表示要自定义word粘贴

            // 列表缩进会超出边框,直接过滤掉
            html = html.replace(/text\-indent:\-(.*?)pt/gi, '')

            // 从html内容中查找粘贴内容中是否有图片元素,并返回img标签的属性src值的集合
            const imgSrcs = findAllImgSrcsFromHtml(html);

            // 如果有
            if (imgSrcs && Array.isArray(imgSrcs) && imgSrcs.length) {

                // 从rtf内容中查找图片数据
                const rtfImageData = extractImageDataFromRtf(rtf);

                // 如果找到
                if (rtfImageData.length) {

                    // TODO:此处可以将图片上传到自己的服务器上

                    // 执行替换:将html内容中的img标签的src替换成ref中的图片数据,如果上面上传了则为图片路径
                    html = replaceImagesFileSourceWithInlineRepresentation(html, imgSrcs, rtfImageData)
                    editor.dangerouslyInsertHtml(html);

                }
            }

            // 阻止默认的粘贴行为
            event.preventDefault();
            return false;
        } else {
            return true;
        }
    }

    // 工具栏配置
    const toolbarConfig = {};

    // 创建编辑器
    const editor = createEditor({
        selector: '#editor-container',
        config: editorConfig,
        mode: 'default'
    });

    // 创建工具栏
    const toolbar = createToolbar({
        editor,
        selector: '#toolbar-container',
        config: toolbarConfig,
        mode: 'default'
    });
</script>
.....
// 其他代码
  • 工具函数(上面的代码中有用到)
<script>
/**
 * 从html代码中匹配返回图片标签img的属性src的值的集合
 * @param htmlData
 * @return Array
 */
function findAllImgSrcsFromHtml(htmlData) {

    let imgReg = /<img.*?(?:>|\/>)/gi; //匹配图片中的img标签
    let srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i; // 匹配图片中的src

    let arr = htmlData.match(imgReg); //筛选出所有的img
    if (!arr || (Array.isArray(arr) && !arr.length)) {
        return false;
    }


    let srcArr = [];
    for (let i = 0; i < arr.length; i++) {
        let src = arr[i].match(srcReg);
        // 获取图片地址
        srcArr.push(src[1]);
    }

    return srcArr;
}

/**
 * 从rtf内容中匹配返回图片数据的集合
 * @param rtfData
 * @return Array
 */
function extractImageDataFromRtf(rtfData) {
    if (!rtfData) {
        return [];
    }

    const regexPictureHeader = /{\\pict[\s\S]+?({\\\*\\blipuid\s?[\da-fA-F]+)[\s}]*/
    const regexPicture = new RegExp('(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}', 'g');
    const images = rtfData.match(regexPicture);
    const result = [];

    if (images) {
        for (const image of images) {
            let imageType = false;

            if (image.includes('\\pngblip')) {
                imageType = 'image/png';
            } else if (image.includes('\\jpegblip')) {
                imageType = 'image/jpeg';
            }

            if (imageType) {
                result.push({
                    hex: image.replace(regexPictureHeader, '').replace(/[^\da-fA-F]/g, ''),
                    type: imageType
                });
            }
        }
    }

    return result;
}

/**
 * 将html内容中img标签的属性值替换
 * @param htmlData html内容
 * @param imageSrcs html中img的属性src的值的集合
 * @param imagesHexSources rtf中图片数据的集合,与html内容中的img标签对应
 * @param isBase64Data 是否是Base64的图片数据
 * @return String
 */
function replaceImagesFileSourceWithInlineRepresentation(htmlData, imageSrcs, imagesHexSources, isBase64Data =
    true) {
    if (imageSrcs.length === imagesHexSources.length) {
        for (let i = 0; i < imageSrcs.length; i++) {
            const newSrc = isBase64Data ?
                `data:${imagesHexSources[i].type};base64,${_convertHexToBase64(imagesHexSources[i].hex)}` :
                imagesHexSources[i];

            htmlData = htmlData.replace(imageSrcs[i], newSrc);
        }
    }

    return htmlData;
}

/**
 * 十六进制转base64
 */
function _convertHexToBase64(hexString) {
    return btoa(hexString.match(/\w{2}/g).map(char => {
        return String.fromCharCode(parseInt(char, 16));
    }).join(''));
}
</script>

总结

  1. 本文基本上完美实现了从 word 复制粘贴图片的需求。

  2. 至于样式部分丢失的问题,目前不可能 100%解决,wangEditor 本身解析内容的原理导致,就目前而言,只能尽可能对损失或丢失的样式做一些额外的处理,使其接近复制粘贴的预期。

声明:本文为原创文章,51blog.xyz和作者拥有版权,如需转载,请注明来源于51blog.xyz并保留原文链接:https://www.51blog.xyz/article/30.html

文章归档

推荐文章

buildadmin logo
Thinkphp8 Vue3 Element PLus TypeScript Vite Pinia

🔥BuildAdmin是一个永久免费开源,无需授权即可商业使用,且使用了流行技术栈快速创建商业级后台管理系统。

热门标签

PHP ThinkPHP ThinkPHP5.1 Go Mysql Mysql5.7 Redis Linux CentOS7 Git HTML CSS CSS3 Javascript JQuery Vue LayUI VMware Uniapp 微信小程序 docker wiki Confluence7 学习笔记 uView ES6 Ant Design Pro of Vue React ThinkPHP6.0 chrome 扩展 翻译工具 Nuxt SSR 服务端渲染 scrollreveal.js ThinkPHP8.0 Mac webman 跨域CORS vscode GitHub ECharts Canvas