基于 Konva 实现Web PPT 编辑器(三)

news2024/11/26 10:42:15

完善公式

        上一节我们简单讲述了公式的使用,并没有给出完整的样例,下面还是完善下相关步骤,我们是默认支持公式的编辑功能的哈,因此,我们只需要提供必要的符号即可:

        符号所表达的含义是 mathlive 的command命令符号:

        当我们点击符号的时候,执行 executeCommand('insert')即可:

const svgs = menuItem.querySelectorAll("svg");
svgs.forEach((svg) => {
  svg.addEventListener("click", () => {
    // 获取对应的 latex 命令
    const command = <string>svg.dataset?.command;
    latex.executeCommand("insert", command);
    latex.focus();
  });
});

         如果大家使用时,有些公式显示异常,应该是字体文件没有引入,需要将全部字体放到 public/fonts 下(字体文件在 node_modelus/mathlive下有的)

        导出的实现,就是基于html-to-image 库:

// 1. 将公式转为 HTML(一定先转HTML哈)
const html = convertLatexToMarkup(latex.value);

// ... 还需要将生成的 html 插入页面,并做好隐藏

// 调用 html-to-image 库
toBlob(html).then((blob) => {
    // ... 生成的文件转成 konva 图片即可
});

拖拽排序

        这个功能在很多网站都有,网上也有好多教程,大家搜一下就行了,给下大致实现思路:

  /** 拖拽开始 - 添加拖拽样式 */
  private imageBoxDragStart(e: DragEvent) {
    const thumbItem = <HTMLDivElement>e.target;
    nextTick(() => {
      thumbItem.classList.add("thumb-draging");
    });
  }
private imageBoxDragEnter(e: DragEvent) {
     // 父元素在 thumbBox 中,需要根据这个获取顺序,因为拖拽过程中  顺序是变得哦
    const childrenList = [...thumbBox.children];

    const dragingBox = thumbBox.querySelector(".thumb-draging")!;
    const dragingBoxParent = <HTMLElement>dragingBox.parentNode;
    const dragingIndex = childrenList.indexOf(dragingBoxParent);

    // 被进入元素 index
    const enterParent = <HTMLElement>target.parentNode;
    const enterIndex = childrenList.indexOf(enterParent);

    if (dragingIndex > enterIndex) {
      thumbBox.insertBefore(dragingBoxParent, enterParent);
    } else {
      thumbBox.insertBefore(dragingBoxParent, enterParent.nextElementSibling);
    }
}
private imageBoxDragEnd(e: DragEvent) {
    // 完成之后 更新 layerManager 的 layerList
    const root = this.draw.getRootBox();
    const thumbBox = root.querySelector(".konva-root-container-thumb")!;
    const order = [];
    for (let i = 0; i < thumbBox.children.length; i++) {
      const item = <HTMLDivElement>thumbBox.children[i];
      order.push(parseInt(item.dataset.index!));
    }
    this.draw.getLayerManager().updateLayerList(order);
}

富文本实现

        为了丰富文本展示形式,利用quill进行富文本编辑,使用 html-to-image 转成konva图片的形式,实现富文本.

 // 1. 初始化 quill
    const quill = new Quill(".richtext-editor #editor", {
      placeholder: "input your content...",
      modules: {
        toolbar: "#toolbar-container",
      },
      theme: "snow",
    });
  
  // 2. 确认按钮转 blob
    this.confirmHandle = () => {
      return new Promise<void>((resolve) => {
        toBlob(quill.root).then((blob) => {
          // 为了下次编辑,还需要将当前的 Delta 转存到 shape 中
          // 下次编辑 setContents(delta: Delta, source: string = 'api'): Delta
          this.result = { blob, delta: quill.getContents() };
          resolve();
        });
      });
    };

元素的复制粘贴

        我们的元素统一封装为 Group,因此,复制时,只需要拿到图形的ID属性,然后clone 一个新的图形,添加到画布上即可:

  // 复制 - 当前选中的元素
  public copy() {
    const selected = this.draw.getKonvaGraph().getSelected();
    if (!selected.length) return;

    const data = selected.map((g) => g.clone());

    const layerManager = this.draw.getLayerManager();
    layerManager.setCopyGroupList(data);
  }
  // 粘贴
  public async paste() {
    const layerManager = this.draw.getLayerManager();
    const copyGroupList = layerManager.getCopyGroupList();
    const layer = this.draw.getLayer();

    if (!copyGroupList.length || !layer) return;

    this.draw.clearTransformer();
    const newGroupList = <Konva.Group[]>[];

    // 不然 直接将 groupList 的group 修改 ID 及 x y 重新添加到当前 layer 上
    copyGroupList.forEach((group) => {
      const newGroup = group.clone();
      newGroup.setAttrs({
        id: getUniqueId(),
        x: newGroup.x() + 20,
        y: newGroup.y() + 20,
      });

      newGroupList.push(newGroup.clone());

      layer.add(newGroup);
      groupTransformer(this.draw, newGroup.children[0]);
    });

    // 实现持续的复制粘贴
    layerManager.setCopyGroupList(newGroupList);
    this.draw.render();
  }

         剪切的思路与上复制粘贴一致,就是先执行 group.destroy ,然后再将数据放置到copyGroupList 中即可。

实现文件的导入导出

        文件导入使用的是 pptxtojson这个库,

        在线体验地址:https://pipipi-pikachu.github.io/pptxtojson/

        文件导出使用的是 pptxgenjs这个库,

        官网地址:Quick Start Guide | PptxGenJS

        具体用法大家自行参考案例哈,这里不做细说了~

        文件导出这里我重点说一下哈:

我们需要设置幻灯片尺寸,也可以自定义,但是!!!

        真实的元素在页面上的位置,用的是像素!!!而不能直接转换成幻灯片的位置关系,这里建议使用 百分比 来处理,比较简单:

    /** 工具函数 - 转换成 百分比 显示 */
    function getPercent(type: "w" | "h" | "x" | "y", v: number) {
      let result = null;
      if (type === "h" || type === "y") result = (v / stageHeight) * 100 + "%";
      else result = (v / stageWidth) * 100 + "%";
      return <PptxGenJS.Coord>result;
    }

         同时,底层库不支持直接式添加文本,例如:

// 不支持直接式添加文本
slide.addShape("rect", {
    text:"直接式添加文本",
    rotate,
    fill: { color: getColor(fill) },
    x: getPercent("x", x),
    y: getPercent("y", y),
    w: getPercent("w", realWidth || width),
    h: getPercent("h", realHeight || height),
});

而是需要将文本节点单独添加:

//  创建文本节点
slide.addText(text, {
    valign: "middle",
    align: "center",
    rotate,
    x: getPercent("x", x),
    y: getPercent("y", y),
    w: getPercent("w", realWidth || width),
    h: getPercent("h", realHeight || height),
    color: getColor(fill),
    fontSize,
});

实现拖拽上传

        拖拽上传的核心事件是dragover、drop,在释放时,可以通过 dataTransfer 读取拖拽的内容,具体的使用可以看MDN dataTransfer :

container.addEventListener("dragover", e => {
    e.preventDefault(); ! important
});

container.addEventListener("drop", e => {
    //  读取内容
    const data = e.dataTransfer?.getData("text");
    if (data) {
        // 存在则是 string 文本
    }else {
        // 不存在则读取文件
        const files = e.dataTransfer?.files;
    }
});

总结

        这篇主要丰富了Unipptx的功能,支持富文本、公式导出、拖拽上传及幻灯片排序等,还有难度较大的PPTX导入导出实现。整体来说,目前已经可完全编辑了,后面主要实现的功能有 预览、元素动画实现、协同实现。

        最近工作较忙哈,更新较慢,大家多多谅解,欢迎大家fork代码,一起创作开发~

        gitee:https://gitee.com/wfeng0/uni-pptx(未完全开发版)

        官方文档:https://wf0.github.io/unippt.html(非完整文档)

        大家有啥新的想法、创意,页可以留言讨论实现方案,一起完善相关功能~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2219770.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从0开始深度学习(12)——多层感知机的逐步实现

依然以Fashion-MNIST图像分类数据集为例&#xff0c;手动实现多层感知机和激活函数的编写&#xff0c;大部分代码均在从0开始深度学习&#xff08;9&#xff09;——softmax回归的逐步实现中实现过 1 读取数据 import torch from torchvision import transforms import torchv…

JavaCove部署文档

1. 基础配置 1.1服务器&#xff1a; 2 核 2G 1.2. 一个域名 1.3. 项目地址&#xff1a; gitee:https://gitee.com/guo-_jun/JavaCove github:https://github.com/nansheng1212/JavaCove 2. CentOS 安装 Docker 官方网站上有各种环境下的 安装指南&#xff0c;这里主要介绍…

webpack自定义插件 ChangeScriptSrcPlugin

插件文件 class ChangeScriptSrcPlugin {apply(compiler) {const pluginName "ChangeScriptSrcPlugin";compiler.hooks.compilation.tap(pluginName, (compilation, callback) > {compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(pluginName,(html…

SpringCloudStream使用StreamBridge实现延时队列

利用RabbitMQ实现消息的延迟队列 一、安装RabbitMQ 1、安装rabbitmq 安装可以看https://blog.csdn.net/qq_38618691/article/details/118223851,进行安装。 2、安装插件 安装完毕后,exchange是不支持延迟类型的,需要手动安装插件,需要和安装的rabbitmq版本一致 https:…

动态规划:17.简单多状态 dp 问题_买卖股票的最佳时机III_C++

题目链接&#xff1a; 一、题目解析 题目&#xff1a;123. 买卖股票的最佳时机 III - 力扣&#xff08;LeetCode&#xff09; 解析&#xff1a; 拿示例1举例&#xff1a; 我们可以如图所示买入卖出股票&#xff0c;以求得最大利润&#xff0c;并且交易次数不超过2次 拿示…

基于SpringBoot设计模式之结构型设计模式·组合模式

文章目录 介绍开始架构图定义条目定义文件定义文件夹 测试样例 总结 介绍 能够使容器与内容具有一致性&#xff0c;创造出递归结构的模式就是 Composite 模式。Composite 在英文中是“混合物”“复合物”的意思。   以目录为例&#xff0c;在计算机中&#xff0c;某个目录下有…

在海外留学/工作,如何报考微软mos认证?

重点首先得强调的是&#xff0c;即使在海外也可以顺利地在国内获取微软MOS认证&#xff01; 01 微软mos认证简介 Microsoft Office Specialist 简称MOS。是微软公司和第三方国际认证机构、全球三大IT测验与教学中心之一的思递波/Certiport公司于1997年联合推出的&#xff0c;…

2009年国赛高教杯数学建模A题制动器试验台的控制方法分析解题全过程文档及程序

2009年国赛高教杯数学建模 A题 制动器试验台的控制方法分析 汽车的行车制动器&#xff08;以下简称制动器&#xff09;联接在车轮上&#xff0c;它的作用是在行驶时使车辆减速或者停止。制动器的设计是车辆设计中最重要的环节之一&#xff0c;直接影响着人身和车辆的安全。为了…

分享一个IDEA里面的Debug调试设置

1.问题来源 其实我们在这个IDEA里面的这个进行调试的时候&#xff0c;这个是只有步入&#xff0c;出去的选项的&#xff1b; 之前学习这个sort的底层源码的时候&#xff0c;进不去&#xff0c;我们是设置了一个取消java*什么的选项&#xff0c;然后使用这个step into就可以进…

计算机网络易混知识点

1.以太网采用曼彻斯特编码&#xff1b;以太网帧最短为64B&#xff0c;其中14个B首部(目的MAC-6B&#xff0c;源MAC-6B&#xff0c;类型-2B)4B尾部 2.OSI协议中&#xff0c;每一层为上一层提供服务&#xff0c;为下一层提供接口 3.帧序号的比特数表示的是发送窗口的大小&#…

java逻辑运算符 C语言结构体定义

1. public static void main(String[] args) {System.out.println(true&true);//&两者均为true才trueSystem.out.println(false|false);// | 两边都是false才是falseSystem.out.println(true^false);//^ 相同为false&#xff0c;不同为trueSystem.out.println(!false)…

(38)MATLAB分析带噪信号的频谱

文章目录 前言一、MATLAB仿真代码二、仿真结果画图总结 前言 本文给出带噪信号的时域和频域分析&#xff0c;指出频域分析在处理带噪信号时的优势。 首先使用MATLAB生成一段信号&#xff0c;并在信号上叠加高斯白噪声得到带噪信号&#xff0c;然后对带噪信号对其进行FFT变换&…

Java面试指南:Java基础介绍

这是《Java面试指南》系列的第1篇&#xff0c;本篇主要是介绍Java的一些基础内容&#xff1a; 1、Java语言的起源 2、Java EE、Java SE、Java ME介绍 3、Java语言的特点 4、Java和C的区别和联系&#xff1f; 5、面向对象和面向过程的比较 6、Java面向对象的三大特性&#xff1a…

云计算-----单机LNMP结构WordPress网站

LNMP结构 博客网站 day1 小伙伴们&#xff0c;LNMP结构在第一二阶段浅浅的学习过&#xff0c;这里我们可以离线部署该结构。L指&#xff08;虚拟机&#xff09;服务器&#xff0c;nginx&#xff08;前端代理服务器&#xff09;mysql数据库&#xff0c;最后基于php建设动态…

AlDente Pro for Mac电脑 充电限制保护工具 安装教程【简单,轻松上手】

Mac分享吧 文章目录 AlDente Pro for Mac 充电限制保护工具 安装完成&#xff0c;软件打开效果一、AlDente Pro for Mac 充电限制保护工具 Mac电脑版——v1.28.41️⃣&#xff1a;下载软件2️⃣&#xff1a;安装软件&#xff0c;将安装包从左侧拖入右侧文件夹中&#xff0c;等…

Halcon实战——基于NCC模板匹配的芯片检测(附源码)

Halcon实战——基于NCC模板匹配的芯片检测&#xff08;附源码&#xff09; 关于作者 作者&#xff1a;小白熊 作者简介&#xff1a;精通python、matlab、c#语言&#xff0c;擅长机器学习&#xff0c;深度学习&#xff0c;机器视觉&#xff0c;目标检测&#xff0c;图像分类&am…

Java | Leetcode Java题解之第493题翻转对

题目&#xff1a; 题解&#xff1a; class Solution {public int reversePairs(int[] nums) {Set<Long> allNumbers new TreeSet<Long>();for (int x : nums) {allNumbers.add((long) x);allNumbers.add((long) x * 2);}// 利用哈希表进行离散化Map<Long, Int…

linux 效率化 - 输入法 - fcitx5

安装 Fcitx5 1. 卸载 ibus 框架 由于 ibus 和 fcitx 可能会冲突&#xff0c;先卸载 ibus&#xff08;暂未确认原因&#xff09; sudo apt remove --purge ibus2. 安装 fcitx5 输入法框架 sudo apt update sudo apt install fcitx5 fcitx5-chinese-addons fcitx5-frontend-gtk…

深入理解Nest的REQUEST范围和TRANSIENT范围

深入理解Nest的REQUEST范围和TRANSIENT范围 单例模式REQUEST范围控制器的REQUEST范围REQUEST范围的冒泡特性场景 TRANSIENT范围例外场景 总结 单例模式 单例模式是指在整个程序执行期间&#xff0c;程序内的类都会实例化&#xff0c;且与应用程序生命周期直接相关&#xff0c;…

javax.el.PropertyNotFoundException: Property ‘XXX‘ not found on type XXX(类的路径)

捣鼓了半小时的bug 在网上找了好多方案,都没有解决 其中一个佬的解决方案:异常&#xff1a;javax.el.PropertyNotFoundException: Property xxx not found on type java.lang.String-CSDN博客 但是还是没有解决我的问题 最终解决方法,在jsp文件头部导入了类包(第三行我导入…