[开源] 从零到一打造在线 PPT 编辑器:React + Zustand + Zundo

技术栈

  • React 框架
  • TS 语言
  • Umi 脚手架
  • Zustand 状态管理
  • Zundo 状态回滚记录
  • Svg 图形
  • pptxgenjs导出为PPT,还没做
  • Ant Design UI框架

核心设计与实现

这个在线 PPT 编辑器的核心可以拆解为几个关键模块:数据模型、画布渲染器、状态管理器和属性面板

1. 数据模型:JSON

首先,我们需要定义数据结构。整个演示文稿、每一页幻灯片以及幻灯片上的每一个元素(文本、形状、图片等)都应该被结构化、序列化为 JSON 对象

typescript 复制代码
// 单个元素(文本、形状、图片等)
export interface PPTElement {
  id: string;
  type: 'text' | 'image' | 'shape' | 'line';
  left: number;
  top: number;
  width: number;
  height: number;
  rotate?: number;
  content: string; // 文本内容、图片 URL、或形状类型
  style?: {
    // ... 样式属性
  };
}

// 单张幻灯片
export interface Slide {
  id: string;
  elements: PPTElement[];
  background?: {
    color?: string;
    image?: string;
  };
}

// 整个演示文稿
export interface Presentation {
  title: string;
  slides: Slide[];
}

这种设计使得状态的读取、更新和持久化都变得非常直观

2. 状态管理:Zustand + Zundo

状态管理是编辑器的灵魂。我使用 Zustand 创建了一个 presentationStore 来统一管理所有的状态和操作。

Zundo 的集成异常简单,只需将你的状态创建函数包裹在 zundo 中间件里即可。

typescript 复制代码
import { create } from 'zustand';
import { temporal } from 'zundo'; // 引入 zundo

export const usePresentationStore = create(
  temporal( // 使用 temporal 中间件包裹
    (set, get) => ({
      slides: initialSlides, // 初始幻灯片数据
      selectedSlideId: initialSlides[0].id,
      selectedElementIds: [],

      // 添加元素
      addElement: (element) => {
        // ...
      },

      // 更新元素
      updateElement: (id, patch) => {
        // ...
      },

      // ... 其他所有操作 slides 的方法
    }),
    {
      // Zundo 配置项
      limit: 50, // 最多记录 50 步历史
    }
  )
);

现在,presentationStore 自动拥有了撤销 (undo) 和重做 (redo) 的能力。我们只需要在组件中调用它们:

tsx 复制代码
import { useTemporalStore } from '@/stores/presentationStore';

const CanvasHeader = () => {
  const { undo, redo } = useTemporalStore.temporal.getState();

  return (
    <div>
      <Tooltip title="撤销">
        <Button icon={<UndoOutlined />} onClick={() => undo()} />
      </Tooltip>
      <Tooltip title="重做">
        <Button icon={<RedoOutlined />} onClick={() => redo()} />
      </Tooltip>
      {/* ... */}
    </div>
  );
};

复杂的状态历史追溯功能被 Zundo 优雅地解决了,真香!

3. 画布与渲染器

画布是用户与 PPT 交互的核心区域。它负责根据当前 Slide 的数据,渲染出所有元素。

我创建了一个 ElementRenderer 组件,它会根据元素的 type 字段,动态地渲染出不同的子组件(如 TextElementImageElement 等)。

tsx 复制代码
const ElementRenderer = ({ element }: { element: PPTElement }) => {
  switch (element.type) {
    case 'text':
      return <div style={...}>{element.content}</div>;
    case 'image':
      return <img src={element.content} style={...} />;
    case 'shape':
      return <ShapeElement type={element.content} style={...} />;
    default:
      return null;
  }
};

缩略图列表的实现 :我没有为缩略图单独编写一套渲染逻辑,而是直接复用了 渲染器组件,只是通过 props 传入一个缩放比例 scale,并禁用其交互

tsx 复制代码
const SlideThumbnail = ({ slide, size }) => {
  const scale = size.width / 1920; // 假设画布标准宽度为 1920

  return (
    <div style={{ width: size.width, height: size.height }}>
      <Canvas
        slide={slide}
        scale={scale}
        interactive={false} // 禁用交互
        embedded={true}     // 嵌入模式
      />
    </div>
  );
};

4. 属性面板:响应式交互

当用户选中一个元素时,右侧的属性面板会显示其对应的可编辑属性(如颜色、字体大小、位置等)

这里的逻辑是:

  1. 监听 presentationStore 中的 selectedElementIds
  2. 当选中元素变化时,从 slides 数据中找到该元素的详细信息
  3. 将元素属性绑定到属性面板的输入框中
  4. 当用户修改输入框时,调用 store 中的 updateElement 方法来更新状态

数据驱动视图的理念

未来路线图 (Roadmap)

这个项目还有很大的想象空间,我计划在未来加入更多的功能:

  • PPT 导出 :支持将编辑好的内容导出为 .pptx 文件
  • 完备的快捷键:增加更多快捷键
  • 更多的属性配置:支持配置多种多样的样式
  • 元素对齐与分布:提供辅助线、元素吸附、水平/垂直分布等高级编辑功能
  • 动画效果:为元素添加入场、退场动画
  • 主题与模板:内置更多精美的设计模板
  • 多人实时协作:这是最具挑战性的功能,也是在线文档的终极形态

写在最后

这个项目目前还处于早期阶段,有很多不完善之处。非常欢迎大家提出宝贵的建议、报告 Bug,甚至参与到开发中来。勿喷! Star!!!!

相关推荐
xump7 分钟前
如何在DevTools选中调试一个实时交互才能显示的元素样式
前端·javascript·css
折翅嘀皇虫9 分钟前
fastdds.type_propagation 详解
java·服务器·前端
Front_Yue10 分钟前
深入探究跨域请求及其解决方案
前端·javascript
wordbaby11 分钟前
React Native 进阶实战:基于 Server-Driven UI 的动态表单架构设计
前端·react native·react.js
抱琴_12 分钟前
【Vue3】我用 Vue 封装了个 ECharts Hooks,同事看了直接拿去复用
前端·vue.js
风止何安啊13 分钟前
JS 里的 “变量租房记”:闭包是咋把变量 “扣” 下来的?
前端·javascript·node.js
Danny_FD18 分钟前
用 ECharts markLine 标注节假日
前端·echarts
程序员西西19 分钟前
SpringBoot无感刷新Token实战指南
java·开发语言·前端·后端·计算机·程序员
烛阴19 分钟前
Luban集成CocosCreator完整教程
前端·typescript·cocos creator
有点笨的蛋20 分钟前
深入理解 JavaScript 原型机制:构造函数、原型对象与原型链
前端·javascript