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

完善公式

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

        符号所表达的含义是 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(非完整文档)

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

socket套接字

1.IP地址 IP地址是在IP协议中, 用来标识网络中不同主机的地址。由点分十进制组成&#xff0c;在数据传输中IP地址是一直不变的。 在IP数据包头部中, 有两个IP地址&#xff0c; 分别叫做源IP地址和目的IP地址。 2.端口号 由2字节16位整数组成&#xff0c;标识当前主机的唯一…

YOLO V3 网络构架解析

YOLO V3&#xff08;You Only Look Once version 3&#xff09;是由Joseph Redmon等人于2018年提出的一种基于深度学习的目标检测算法。它在速度和精度上相较于之前的版本有了显著提升&#xff0c;成为计算机视觉领域的一个重要里程碑。本文将详细解析YOLO V3的网络架构&#x…

关于WPF项目降低.Net版本

本来有项目是.NET Framework 4.8的&#xff0c;为了兼容升级到.NET 8.0&#xff0c;后期又为了兼容放弃.NET 8.0&#xff0c;升级的步骤&#xff1a;利用vs2022 的 .NET Upgrade Assistant 扩展&#xff0c;磕磕绊绊也升级完成了&#xff1b; 扩展链接&#xff1a; Upgrading…

前端拥抱AI:LangChain.js 入门遇山开路之PromptTemplate

PromptTemplate是什么 PromptTemplate是一个可重复使用的模板&#xff0c;用于生成引导模型生成特定输出的文本。与Prompt的区别: PromptTemplate相对于普通Prompt的优势&#xff0c;即其灵活性和可定制性。 简单了解PromptTemplate后&#xff0c;咱们就来聊聊LangChain里的P…

Hadoop 安装教程——单节点模式和分布式模式配置

文章目录 一、预备知识1.1 Hadoop 发行版本1.2 部署方式 二、预备条件2.1 环境准备2.2 创建新用户(可选)2.3 配置 SSH 无密码登录2.4 下载 Hadoop2.5 编辑 hadoop-env.sh 脚本2.6 编辑 dfs 和 yarn 脚本 三、单节点模式部署3.1 官方使用案例3.2 查看运行结果 四、伪分布模式部署…

golang 手动解析 epub 电子书格式

如题&#xff0c;本篇简单分析如何使用go语言解析epub格式的电子书&#xff0c;获取其内部资源内容。 EPUB格式 首先我们需要了解epub格式具有哪些特点。 已知的是&#xff0c;epub是一种类似doc或者pdf&#xff0c;可以提供图文并茂电子书的格式。 那么我们首先使用二进制…

重学SpringBoot3-集成Hazelcast

重学SpringBoot3-集成Hazelcast 1. Hazelcast 的作用2. Spring Boot 3 整合 Hazelcast 的步骤2.1 添加 Hazelcast 依赖2.2 配置 Hazelcast 实例 3. 集成 Hazelcast 与 Spring Boot 缓存4. 验证 Hazelcast 缓存5. Hazelcast 集群配置6. 总结 Hazelcast 是一个流行的开源内存数据…

Linux重点yum源配置

1.配置在线源 2.配置本地源 3.安装软件包 4.测试yum源配置 5.卸载软件包

浅谈人工智能之基于阿里云使用vllm搭建Llama3

浅谈人工智能之基于阿里云使用vllm搭建Llama3 引言 随着人工智能技术的迅速发展&#xff0c;Llama3作为一个先进的语言模型&#xff0c;受到广泛关注。本文将介绍如何在阿里云上使用VLLM搭建Llama3&#xff0c;为用户提供一套完整的技术流程。 环境准备 阿里云账户 确保您…

记录:网鼎杯2024赛前热身WEB01

目录扫描&#xff0c;发现上传点&#xff0c;判断可能存在文件上传漏洞&#xff0c;并根据文件后缀判断网站开发语言为php 编写蚁剑一句话木马直接上传 蚁剑连接 这里生成 的flag是随机的&#xff0c;因为烽火台反作弊会随机生成环境&#xff0c;在一顿查找后&#xff0c;在hom…

【部署篇】RabbitMq-03集群模式部署

一、准备主机 准备3台主机用于rabbitmq部署&#xff0c;文章中是在centos7上安装部署rabbitmq3.8通过文章中介绍的方式可以同样在centos8、centos9上部署&#xff0c;只需下载对应的版本进行相同的操作。 主机IP角色说明192.168.128.31种子节点192.168.128.32普通节点192.16…

【达梦数据库】两台或多台服务器之间免密登录设置-【dmdba用户】

目录 背景1、服务器A免密登录本机1.1、生成私钥&#xff08;id_rsa&#xff09;和公钥&#xff08;id_rsa.pub&#xff09;1.2、追加公钥到服务器A的密码登录权限管理文件1.3、结果验证 2、服务器A免密登录服务器B2.1、确认服务器B有目的文件夹2.2、服务器A的公钥复制到服务器B…

网安加·百家讲坛 | 徐一丁:金融机构网络安全合规浅析

作者简介&#xff1a;徐一丁&#xff0c;北京小西牛等保软件有限公司解决方案部总监&#xff0c;网络安全高级顾问。2000年开始从事网络安全工作&#xff0c;主要领域为网络安全法规标准研究、金融行业安全咨询与解决方案设计、信息科技风险管理评估等。对国家网络安全法规标准…

react18中的合成事件与浏览器中的原生事件

React 通过将事件 normalize 以让他们在不同浏览器中拥有一致的属性。 合成事件 SyntheticEvent 实例将被传递给你的事件处理函数&#xff0c;它是浏览器的原生事件的跨浏览器包装器。除兼容所有浏览器外&#xff0c;它还拥有和浏览器原生事件相同的接口&#xff0c;包括 stopP…

项目文章 | 药学TOP期刊PRChIP-seq助力揭示激酶LIMK2促进梗死不良重构的机制

急性心肌梗死&#xff08;MI&#xff09;是全球死亡的主要原因&#xff0c;尽管MI的死亡率有所下降&#xff0c;缺血性心力衰竭的发病率却呈上升趋势。这一现象提示我们&#xff0c;尽管在急救和治疗急性心肌梗死方面取得了进展&#xff0c;但心脏在梗死后的长期功能恢复仍然是…

Pr 视频效果:自动重构

视频效果/变换/自动重构 Transform/Auto Reframe 自动重构 Auto Reframe效果是用于快速调整视频素材以适应不同长宽比的一项强大工具。 随着各种平台和设备的多样化&#xff0c;视频内容需要适应不同的屏幕尺寸和比例&#xff0c;如 16:9&#xff08;横屏&#xff09;、9:16&am…

「Qt Widget中文示例指南」如何实现半透明背景?

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 本文将为大家展示如…

新鲜出炉,ECCV2024.9.25 首次提出基于 YOLO 目标检测的无源域自适应

原文标题&#xff1a;Source-Free Domain Adaptation for YOLO Object Detection 中文标题&#xff1a;基于 YOLO 目标检测的无源域自适应 论文地址&#xff1a; https://arxiv.org/abs/2409.16538 代码地址&#xff1a; GitHub - vs-cv/sf-yolo 1、Abstract 无源域自适应&…

单细胞 | 转录因子足迹分析

数据加载 在本案例中&#xff0c;将采用之前在轨迹构建案例中已经介绍并处理过的数据集。 library(Signac)library(Seurat)bone <- readRDS("cd34.rds")DimPlot(bone, label TRUE) 要执行足迹分析&#xff0c;必须首先向对象中添加Motif 信息&#xff0c;这包括每…

微软发布 Win11 22H2/23H2 十月可选更新KB5044380!

系统之家于10月23日发出最新报道&#xff0c;微软针对Win11 22H2和23H2用户&#xff0c;发布了10月可选更新KB5044380&#xff0c;用户安装后版本号升至22621.4391和22631.4391。本次更新开始推出屏幕键盘的新游戏板键盘布局&#xff0c;支持用户使用Xbox控制器在屏幕上移动和键…