逐步教程

GrapesJS 教程 · 构建你的第一个可视化编辑器

一步步学习GrapesJS。构建你的第一个可视化编辑器,添加模块和自定义组件,配置样式和资源,保存项目,导出HTML/CSS,安装插件,并将GrapesJS与React、Vue、Angular或Next.js集成。

  • 开源编辑器框架
  • 自托管
  • 可通过插件扩展
  • 自定义组件
  • HTML/CSS 导出
  • React / Vue / Angular / 原版 JS

GrapesJS 核心以 BSD-3-Clause 许可证发布;官方的 React 包装 @grapesjs/react 是 MIT。两者都允许商业使用。以下所有示例均与 GrapesJS 0.23.6 进行了核对。

结果

你将构建的内容

完成本教程后,你将拥有一个可运行的拖放可视化编辑器,可以创建页面、编辑组件、管理样式和资源、保存项目,并导出生成的 HTML 和 CSS。

  • 拖块到画布上
  • 选择和编辑组件
  • 重新设计任何来自 Style Manager 的东西
  • 在桌面、平板和手机宽度之间切换
  • 把项目读回去,写成JSON
  • 将页面导出为HTML和CSS

预计学习路径

初级 → 中级 → 生产环境

具体需要多长时间,完全取决于你的产品需要多少生产层。第1到第6步是一个下午;第7到第12步是工作时间。

打开 GrapesJS 官方演示
前提条件

开始之前

非常少。如果你能手写一页,你就能跟着写。

基础 HTML 和 CSS

你应该能识别标签、类和CSS属性。GrapesJS编辑HTML和CSS——没有更特殊的事情。

基础JavaScript

足够读取一个对象的字面和一个函数。这里的每个示例都是纯JavaScript。

Node.js和npm

只有在你从 npm 安装时才会有。第一步的 CDN 路线只需要一个文本编辑器和一个浏览器。

你不需要任何之前的GrapesJS经验,也不需要框架。React、Vue、Angular和Next.js在第11步会涵盖,核心内容通过后。

初级

1. 安装GrapesJS

GrapesJS 是你添加到自己应用中的一个库,而不是你注册的服务。有两种进入方式,选择哪种对后续步骤没有影响。

来自npm

如果你有任何构建步骤,这就是应该采取的路径。它会把编辑器和样式表安装到你的项目中;运行时不会从第三方主机抓取任何东西。

terminal
npm install grapesjs

来自CDN

完全不用构建工具:在HTML文件里放两个标签,你就有编辑器了。非常适合做初次展示或原型。无论你发货什么,都钉住一个完全相同的版本,而不是跟踪最新版本。

index.html
<link
  rel="stylesheet"
  href="https://unpkg.com/grapesjs/dist/css/grapes.min.css"
/>
<script src="https://unpkg.com/grapesjs"></script>

<div id="gjs"></div>

<script>
  // The UMD build puts the library on window.grapesjs
  const editor = grapesjs.init({ container: '#gjs' });
</script>

你刚安装的是什么

编辑器核心

画布、组件树、拖放、撤销/重做、面板以及块、样式、资源、traits、图层和存储管理器。

样式表

grapesjs/dist/css/grapes.min.css — 编辑器自己的Chrome。没有它,编辑器看起来很坏。

没有任何阻碍

核心会附带一个空的块调色板。熟悉的列/文本/图片块来自插件,这是步骤3。

没有后端

没有账户,没有数据库,没有托管。GrapesJS 运行在浏览器中,将数据传递给你的应用,这是第7步。

GrapesJS在你应用中的位置

它是客户端组件。你的应用程序认证用户,决定打开哪个项目,将编辑器挂载到DOM元素中,保存后会接收项目数据。编辑器上下的所有内容都是你的。

初级

2. 创建你的第一个GrapesJS编辑器

最小的实用 GrapesJS 应用是一个空元素和一个调用。将下面的两个模块复制到页面中,你就有一个可用的编辑器。
index.html
<!-- The editor takes over this element completely.
     Do not render anything inside it yourself. -->
<div id="gjs"></div>
一个元素。GrapesJS 完全替换了它的内容,所以千万不要在它里渲染你自己的 UI。
editor.js
import grapesjs from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';

const editor = grapesjs.init({
  // Where the editor mounts: a selector or an HTMLElement.
  container: '#gjs',
  height: '100vh',
  width: 'auto',

  // Do not adopt the markup already inside #gjs...
  fromElement: false,
  // ...load this instead. Strings are parsed into components.
  components: `
    <section class="hero">
      <h1>Hello GrapesJS</h1>
      <p>Drag a block from the panel on the right.</p>
    </section>`,
  style: `
    .hero { padding: 64px 32px; font-family: system-ui, sans-serif; }
    .hero h1 { margin: 0 0 12px; font-size: 40px; }`,

  // Storage is ON by default and writes to localStorage.
  // Turn it off until you have decided where projects really live.
  storageManager: false,
});
这里有两个选项值得在第一天就知道:fromElement 决定编辑器是否采纳容器内已有的标记,storageManager 默认使用 localStorage 支持的存储库,默默地将画布保存在阅读器浏览器中。现在关闭它可以避免以后出现大量混乱的幽灵状态。

每个部分的含义

容器
编辑器挂载的元素——CSS 选择器或 HTMLElement。给它一个真实高度,否则编辑器渲染的像素高度为零。
编辑器实例
init() 返回的内容。本教程中的每个 API 都挂在它上,调用 destroy() 会释放 DOM 和听众。
画布
你页面编辑的iframe内部。因为它是真实的iframe,你的页面的CSS不会泄漏到它里面,它的CSS也不会泄漏出来。
初始项目
画布起始的部分——组件和样式选项,或者存储层加载的内容。
配置
一个单一对象。后面步骤中的每个管理器都从其上的一个键配置:blockManager、styleManager、assetManager、storageManager、deviceManager。

你应该看到的

一个三部分界面:画布在中间,面板切换器在右上角,以及——在第三步添加块后——一个可拖曳的调色板。本节上方的编辑器正是这种配置,添加了六块和三种设备宽度。

打开在线演示
初级

3. 添加拖放块

在任何代码之前,有一个区别。这是初学者最常犯错的一点,从此一切都取决于它。

区块

一个调色板条目。它只存在于面板中,包含一个制作配方。它有标签、分类、图标和内容。

组成部分

画布中的一个节点。它有一个类型、属性、样式、子节点,这些节点会被导出和保存。

一个块是用户拖入画布的。一旦丢弃,它就会在编辑器内创建组件。一个块可以创建完整的组件子树——而丢弃两个相同的块则会形成两个独立的子树。

入门调色板

  • Hero
  • 图片
  • 正文
  • 按钮
  • 两列
  • 联系表格
blocks.js
// A Block is a palette entry. Dropping it creates Components.
editor.Blocks.add('hero-section', {
  label: 'Hero',
  category: 'Sections',
  // Shown in the palette. Any HTML string works; an inline SVG keeps it sharp.
  media: '<svg viewBox="0 0 24 24" width="22"><rect x="3" y="5" width="18" height="6" rx="1" fill="currentColor"/><rect x="3" y="13" width="11" height="3" rx="1" fill="currentColor" opacity=".5"/></svg>',
  content: `
    <section class="hero">
      <h1>Headline</h1>
      <p>Supporting copy.</p>
      <a href="#" class="btn">Call to action</a>
    </section>`,
});

// The same block, expressed as a component definition instead of HTML.
// Use this form once you have your own component types (step 10).
editor.Blocks.add('product-card', {
  label: 'Product card',
  category: 'Commerce',
  content: { type: 'product-card' },
});
内容接受HTML字符串或组件定义。字符串是最快的入门方式;目标形式是你在第10步拥有自己组件类型后切换到的。

核心船没有区块

这几乎让所有人都感到惊讶。开箱即用的GrapesJS编辑器有一个空白调色板——每个截图中出现的熟悉“1列/2列/文本/图片”集合,来自grapesjs-blocks-basic或某个预设。你要么像上面一样写自己的块,要么添加预设。

blocks-preset.js
import grapesjs, { usePlugin } from 'grapesjs';
import blocksBasic from 'grapesjs-blocks-basic';

// The core ships no blocks at all. The familiar
// "1 column / 2 columns / text / image" palette is a plugin.
grapesjs.init({
  container: '#gjs',
  plugins: [usePlugin(blocksBasic, { flexGrid: true })],
});
usePlugin() 是当前注册插件的方式。较早的教程称 grapesjs.plugins.add();该 API 已被弃用并会记录警告。
初级

4. 了解GrapesJS组件

画布不是GrapesJS保存时解析的HTML字符串。它是组件模型的活树,HTML是从该树生成的。一旦理解了这一点,API的其他部分就不再令人惊讶了。

词汇

组件树
画布中的每个节点都是一个组件,每个组件都有父节点和子节点。根节点是包装器。
组件类型
内置类型包括文本、图像、链接、视频、表格以及通用默认值。类型决定节点的行为、渲染和导出。
嵌套组件
封闭是一种真实的关系,而不是压入。droppable和draggable控制着什么可以放进什么。
属性
HTML 属性 — class、href、id、data-*。它们最终会逐字出现在导出的标记中。
性质
模型状态不是HTML属性。对于编辑需要记住但页面不应携带的内容非常有用。
特征
所选组件的设置面板。特质默认会编辑属性,或者在设置changeProp时编辑属性。

典型子树

  • wrapper, 深度 0
  • Section, 深度 1
  • Container, 深度 2
  • Heading, 深度 3
  • Button, 深度 3
Layer Manager 正好显示了这一点。在画布中选择节点,树中选择该节点,反之亦然。
components.js
// Every node in the canvas is a Component, and the canvas is a tree.
const wrapper = editor.getWrapper();

wrapper.components().forEach((component) => {
  console.log(
    component.get('type'),      // 'text' | 'image' | 'link' | your own type
    component.getName(),        // label shown in the Layer manager
    component.components().length // number of children
  );
});

// React to what the user selects — the hook most custom UI hangs off.
editor.on('component:selected', (component) => {
  console.log('selected', component.getId(), component.get('type'));
});
getWrapper() 是树的根节点。从那里,components() 给出了任意节点的子节点——这就是生产编辑器中每个自定义面板、导出器和验证器如何走进文档的方式。
cta-button.js
// Traits are the settings panel for a component.
// By default a trait writes an HTML attribute.
editor.Components.addType('cta-button', {
  extend: 'link',
  model: {
    defaults: {
      name: 'CTA button',
      attributes: { class: 'btn' },
      components: 'Call to action',
      traits: [
        { name: 'href', label: 'Link' },
        { name: 'title', label: 'Title' },
        {
          type: 'select',
          name: 'target',
          label: 'Opens in',
          options: [
            { id: '', name: 'Same tab' },
            { id: '_blank', name: 'New tab' },
          ],
        },
      ],
    },
  },
});
extend 继承了现有类型的所有内容,只覆盖你命名的类型。它几乎总是正确的起点:一个表现得像链接的链接,顶部是你自己的 traits。
初级

5. 配置Style Manager

Style Manager 为所选内容编写 CSS 规则。默认情况下,它会向使用你编辑器的用户提供大量 CSS 的代码——这对开发者工具来说没问题,但对几乎所有产品来说都是错误的。

扇区,以及通常放进去的东西

排版
字体族、字体大小、字体粗细、行高、颜色、对齐。
间距
边距和填充,通常是作者实际会考虑的两个属性。
尺寸
宽度、最大宽度、高度及其最小/最大变体。
荣誉
背景颜色和图像、边框半径、边框、阴影。
响应式
样式是按设备写入的。切换画布到平板或手机,同样的控件会写入媒体查询。
自定义属性
你可以自定义属性类型——比如代币选择器、间距刻度——而不是直接暴露原始的CSS。
style-manager.js
grapesjs.init({
  container: '#gjs',
  styleManager: {
    // Sectors are the collapsible groups in the right-hand panel.
    // Listing them yourself is how you stop the editor offering
    // 100+ CSS properties to a non-technical author.
    sectors: [
      {
        id: 'typography',
        name: 'Typography',
        open: true,
        properties: [
          'font-family',
          'font-size',
          'font-weight',
          'line-height',
          'color',
          'text-align',
        ],
      },
      { id: 'spacing', name: 'Spacing', properties: ['margin', 'padding'] },
      {
        id: 'dimension',
        name: 'Dimension',
        properties: ['width', 'max-width', 'height'],
      },
      {
        id: 'decorations',
        name: 'Decorations',
        properties: ['background-color', 'border-radius', 'border', 'box-shadow'],
      },
    ],
  },
});
扇区是右侧面板中的可折叠组合。明确命名它们是将“整个CSS”变成简短、刻意的选择集合的方式。

结构和造型是另一个问题

组件结构

存在什么,包含什么,可以添加或删除什么。通过组件类型 droppable、draggable 和 removable 控制。

组件样式

组件可能长什么样。通过Style Manager配置控制,组件本身则用stylable / unstylable控制。

restricted-heading.js
// Structure and styling are separate concerns. A component can accept
// children while refusing to be restyled beyond a fixed allowance.
editor.Components.addType('brand-heading', {
  extend: 'text',
  model: {
    defaults: {
      name: 'Brand heading',
      // Only these properties reach the Style manager for this component.
      stylable: ['color', 'text-align'],
      // Everything else stays on the class in your own stylesheet.
      attributes: { class: 'brand-h2' },
    },
  },
});
组件可以接受子组件,但拒绝重新样式。这种配对——开放结构,封闭样式——正是设计系统编辑器的组成部分。
初级

6. 管理图片和资源

Asset Manager 是当阅读器双击图片时打开的模态。它列出资源,接受上传,并将 URL 返回给所选组件。

内容涵盖

图片上传
拖放或文件选择器,发布到你选择的端点。
图片网址
阅读器可以为你已经托管的软件粘贴URL。
资源选择
双击图片组件会打开面板并写回所选的 src。
定制供应商
完全用uploadFile替换上传内容,然后和你用的存储系统沟通。
外部存储
S3、R2、Cloudinary,一个内部的DAM——GrapesJS从不需要知道字节的位置,只需知道URL。
asset-manager.js
grapesjs.init({
  container: '#gjs',
  assetManager: {
    // Seed the panel with images you already host.
    assets: [
      'https://cdn.example.com/hero.jpg',
      { src: 'https://cdn.example.com/team.jpg', name: 'Team', category: 'People' },
    ],
    // Your upload endpoint. Set `upload: false` to disable uploading entirely.
    upload: 'https://api.example.com/uploads',
    uploadName: 'files',
    headers: { Authorization: 'Bearer <token>' },
    multiUpload: true,
    // Add the response's assets to the panel automatically. Your endpoint must
    // answer with { data: [ ...assets ] }.
    autoAdd: true,
  },
});
上传是最快的路径:指向一个响应{ data: [ ... ] }的端点,然后设置autoAdd。头部是你的认证令牌所在。
asset-upload.js
// Full control: upload wherever you like, then hand the URLs back.
grapesjs.init({
  container: '#gjs',
  assetManager: {
    async uploadFile(event) {
      const files = event.dataTransfer
        ? event.dataTransfer.files
        : event.target.files;

      const urls = await uploadToYourStorage(files); // S3, R2, Cloudinary…
      editor.AssetManager.add(urls);
    },
  },
});

// Without `upload` or `uploadFile`, dropped images are embedded as base64
// straight into the project — convenient in a demo, painful in production.
uploadFile 直接把原始文件交给你,然后就让开了。大多数制作编辑最终都会用这个版本,因为上传通常需要签名、调整大小或租户前缀。

有一点需要尽早决定

在没有上传和 uploadFile 配置的情况下,GrapesJS 会将丢弃的图像嵌入项目中,格式为 base64。它即时生效,并且会让存储的项目膨胀,直到加载缓慢且移动成本高昂。在任何人开始制作内容之前,先将 Asset Manager 连接到真实存储。

中级

7. 保存并加载GrapesJS项目

Storage Manager 决定可编辑项目的去向。它默认开启,写入 localStorage——这也是为什么你昨天做的编辑器里还保留着昨天的画布。

它给你带来的

JSON 计划
整个可编辑文档——组件、样式、页面、资源——作为一个纯可序列化的对象。
自动保存
用stepsBeforeSave在一定次数的编辑后保存,而不是每次按键都保存。
负载
你可以在init上抓项目,或者随时自己调用loadProjectData()。
保存
由autosave触发,或者由editor.store()在你自己的工具栏按钮触发。
远程存储
内置基于抓取的存储:赋予它加载URL、存储URL、报头和请求/响应适配器。
自定义存储
注册你自己的加载/存储对,用于GraphQL、离线缓存或远程不覆盖的设备。
storage-manager.js
grapesjs.init({
  container: '#gjs',
  storageManager: {
    type: 'remote',
    autosave: true,
    autoload: true,
    // Batch changes: save after N edits rather than after every keystroke.
    stepsBeforeSave: 5,
    options: {
      remote: {
        urlLoad: '/api/projects/42',
        urlStore: '/api/projects/42',
        headers: { 'X-CSRF-Token': csrfToken },
        credentials: 'include',
        // Shape the request body to match your API…
        onStore: (data) => ({ project: data }),
        // …and pull the project back out of your response.
        onLoad: (result) => result.project,
      },
    },
  },
});
onStore 和 onLoad 是真实 API 中最重要的两个关键点:它们让编辑的有效载荷和端点的契约不同,且双方都不会妥协。
custom-storage.js
// When `remote` does not fit — GraphQL, a queue, an offline-first cache —
// register a storage of your own and select it by name.
editor.Storage.add('my-api', {
  async load() {
    const res = await fetch('/api/projects/42');
    const { project } = await res.json();
    return project; // the object you previously stored
  },
  async store(data) {
    await fetch('/api/projects/42', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ project: data }),
    });
  },
});

// storageManager: { type: 'my-api' }

// You can also drive it by hand, with no storage configured at all:
const project = editor.getProjectData();   // plain JSON — store it anywhere
editor.loadProjectData(project);           // and put it back
或者干脆跳过Storage Manager。getProjectData()返回的是普通的JSON,loadProjectData()则会放回去——很多生产编辑正是这样做的,并从自己的应用状态驱动保存。

GrapesJS的终止点

GrapesJS 提供编辑器和项目数据层,但你的应用决定生产数据存储在哪里。哪个用户拥有项目,属于哪个租户,谁可以打开项目,保留多少版本,何时备份——这些都不在库里,也不应该存在。

中级

8. 导出HTML和CSS

两个调用会把画布变成一页。它们是上面直播编辑器上HTML和CSS按钮连接的调用。
export.js
const html = editor.getHtml();
const css = editor.getCss();

// Two things surprise everyone on their first export:
//
// 1. getHtml() returns the canvas wrapped in <body> … </body>.
//    Strip or template around it before you save a fragment.
// 2. getCss() includes GrapesJS's own canvas reset unless you opt out:
const pageCss = editor.getCss({ avoidProtected: true });

// Export one branch instead of the whole page:
const selected = editor.getSelected();
const partial = editor.getHtml({ component: selected });

// The editable project — NOT the same thing as the exported page.
const project = editor.getProjectData();
getHtml() 和 getCss() 读取当前文档。它们都不涉及存储,也不受你是否配置存储器的影响。

两件让所有人都感到惊讶的事情

getHtml() 包裹在 <body> 中

包装组件作为主体元素导出。如果你保存片段,可以围绕它模板或剥离它——不要假设你能拿回一个裸部分。

getCss() 包含编辑器的重置功能

GrapesJS 附带了一个小的受保护样式表。通过 avoidProtected:当你只想要阅读器实际创建的 CSS 时,是正确的。

典型的出版流程

  1. GrapesJS
  2. HTML + CSS
  3. Your API
  4. Storage / CMS
  5. Published page
具体的流程是针对应用程序的——有些产品写入静态文件,有些会在项目旁边存储渲染文档,有些则在每次请求时从项目 JSON 进行服务器端渲染。GrapesJS 的部分在第一个箭头处结束。

两个输出,两个工作

getHtml() 和 getCss() 给你访问者看到的内容。getProjectData() 给你作者可以继续编辑的内容。存储项目;从它重新生成页面。只存储 HTML 意味着下一次编辑是从解析标记开始,而不是从作者构建的文档开始。

中级

9. 用插件扩展GrapesJS

插件是扩展 GrapesJS 超越核心编辑器的主要方式之一。插件不过是一个接收编辑器实例的函数——你在自己的设置代码里能做的任何事情,插件也能做到。
plugins.js
import grapesjs, { usePlugin } from 'grapesjs';
import blocksBasic from 'grapesjs-blocks-basic';
import forms from 'grapesjs-plugin-forms';

grapesjs.init({
  container: '#gjs',
  plugins: [
    usePlugin(blocksBasic, { flexGrid: true }),
    usePlugin(forms, {}),
  ],
});
usePlugin() 会注册一个插件及其选项。较早的教程展示了 grapesjs.plugins.add();该 API 在当前版本中已被弃用,并会记录警告,提示你改用 API。
my-plugin.js
// A plugin is just a function that receives the editor.
// Anything you can do at init you can do inside one.
export default function dividerPlugin(editor, options = {}) {
  const category = options.category ?? 'Basic';

  editor.Blocks.add('divider', {
    label: 'Divider',
    category,
    content: '<hr class="divider" />',
  });

  editor.Commands.add('clear-canvas', {
    run: (ed) => ed.Components.clear(),
  });
}

// Then: plugins: [usePlugin(dividerPlugin, { category: 'Layout' })]
写一个和配置编辑器是一样的工作,只是被转移到一个可以跨项目重复使用的文件里。块、组件类型、命令、面板、traits 和样式扇区都可以从内部注册。

或者安装一个

需要基础编辑器中没有的功能吗?探索通过GJS.Market提供的插件和扩展。以下是目录中的真实列表,按所属步骤分组。

市场

你刚完成的步骤插件

这些是 GJS.Market 目录中的真实列表,按教程部分分组。这里没有任何东西替代核心——每个链接都填补了核心故意留下的缝隙。

高级

10. 构建自定义组件

这就是 GrapesJS 编辑器不再是通用的 HTML 编辑器,而是成为你产品的一部分。自定义组件类型是作者可以放置的命名对象,拥有自己的结构、设置和可更改的规则。

组件类型包含什么

类型
这个名称需要在 Components.addType 注册,可以选择扩展内置类型。
模型
默认值、子节点、属性、属性和锁——droppable、draggable、removable、stylable。
特征
是作者实际看到的设置。changeProp 写入的是模型,而不是属性。
景色
可选。当画布需要显示导出标记以外的内容时,覆盖渲染。
isComponent
GrapesJS 在解析保存的 HTML 回归树时如何识别你的类型。
一个区块
创建它的调色板条目,内容为:{ type: 'your-type' }。

产品卡,作为组件类型

  • Product Card, 深度 0
  • Image, 深度 1
  • Product Name, 深度 1
  • Price, 深度 1
  • CTA, 深度 1
作者编辑图片、名称、价格和按钮。他们不能删除价格、重新排序零件或将卡片变成别的东西——因为droppable是虚假的,子节点是固定的。
product-card.js
editor.Components.addType('product-card', {
  // Lets GrapesJS recognise the type when parsing saved HTML.
  isComponent: (el) => el.dataset?.gjsType === 'product-card',

  model: {
    defaults: {
      name: 'Product card',
      attributes: { 'data-gjs-type': 'product-card', class: 'product-card' },

      // Author-visible settings. `changeProp` writes to the model
      // instead of to an HTML attribute.
      traits: [
        { name: 'sku', label: 'SKU', changeProp: true },
        {
          type: 'checkbox',
          name: 'showPrice',
          label: 'Show price',
          changeProp: true,
        },
      ],
      sku: '',
      showPrice: true,

      // Fixed structure: the author edits the parts, not the layout.
      components: [
        { type: 'image', attributes: { class: 'product-card__image' } },
        { type: 'text', name: 'Name', components: 'Product name' },
        { type: 'text', name: 'Price', attributes: { class: 'product-card__price' }, components: '$0.00' },
        { type: 'cta-button', components: 'Add to cart' },
      ],

      // Locks that make the card a card and not a free-form div.
      droppable: false,
      stylable: ['background-color', 'border-radius', 'box-shadow'],
    },

    init() {
      this.on('change:showPrice', this.togglePrice);
    },

    togglePrice() {
      const price = this.components().at(2);
      price?.addStyle({ display: this.get('showPrice') ? 'block' : 'none' });
    },
  },
});
底部的锁是有趣的部分。droppable:false 阻止卡片成为容器;stylable 限制重样式为三个属性;traits 仅给作者两个决定。

为什么这对产品很重要

SaaS 应用可以暴露产品特定组件——连接真实计划的定价表、绑定 SKU 的产品卡、预订小组件——而非通用的无限制 HTML 编辑器。作者获得的选择更少,结果更好,你的支持队列也不会看到有人用零散浮动点破坏页面。

构建起来

用你的设计系统构建一个受控编辑器

步骤3、5和10结合成了你在GrapesJS中能做的最有用的事情:用“这七件事,只要做得好”,替代“一切皆有可能”。每个机制都是你已经遇到过的。

自定义区块

调色板就是菜单。如果没有放在架子上,没人能添加。

自定义组件

每个模块的结构固定,应该可编辑的部分标记为可编辑。

特征

作者获得的设置——标题、链接、变体——而不是原始的标记。

Style Manager 配置

扇区只列出系统实际允许的属性。

允许的风格

每个组件的stylable和unstylable,所以卡片可以变色但不会变成浮点。

可重复使用组件

共享的作品保持同步,而不是逐页复制。

模板

每页类型起始文档,所以没人会从空白画布开始。

定制UI

GrapesJS的面板是可以更换的。产品编辑器很少看起来像默认的。

你的SaaS设计系统

  • Hero
  • Feature Grid
  • Pricing
  • Testimonials
  • FAQ
  • CTA
  • Footer
七个区块,每个区块背后有你控制的组件类型。作者从这个书架上挑选,不能生成不符合品牌的页面,因为没有不合适的内容可选。

把GrapesJS从一个通用编辑器变成专门为你的产品设计的编辑器。

有两本指南进一步说明,分为两个方向:

积分

在你的框架中使用GrapesJS

GrapesJS 渲染成一个普通的 DOM 元素,因此“将其与框架集成”归结为一个问题:哪个生命周期钩子调用 init(),哪个调用 destroy()。下面的模式是 React;专门的指南涵盖了其余部分,包括真正针对框架的部分。

Editor.tsx
import { useEffect, useRef } from 'react';
import grapesjs, { type Editor } from 'grapesjs';

export function GjsEditor() {
  const ref = useRef<HTMLDivElement>(null);
  const editorRef = useRef<Editor | null>(null);

  useEffect(() => {
    if (!ref.current) return;
    editorRef.current = grapesjs.init({
      container: ref.current,
      height: '100vh',
      storageManager: false,
    });
    // GrapesJS owns this node now — React must never render into it again.
    return () => {
      editorRef.current?.destroy();
      editorRef.current = null;
    };
  }, []);

  return <div ref={ref} />;
}
两个规则可以存活于每个框架:初始化一次,且之后绝不让框架重新渲染到容器中。GrapesJS 拥有该节点。

有一个需要注意的警告:GrapesJS 在模块加载时会触摸窗口,所以编辑器必须只在客户端导入。Next.js 指南正好涵盖了这一点。

建筑

从教程到生产环境

制作编辑器通常需要的不仅仅是grapesjs.init()。并不是因为库不完整——而是编辑器是产品的一层,而其周围的层就是你的。

典型的生产堆栈

  1. Your application
  2. Authentication
  3. GrapesJS editor
  4. Custom components / blocks
  5. Storage API
  6. Database / CMS
  7. Asset storage
  8. Publishing
GrapesJS 占据了图中的一部分。其上下的都是你编写、购买或已有的应用代码。

周围的层级要回答什么

认证
谁在编辑,编辑的存储电话怎么证明?
权限
谁可以编辑哪一页,谁可以发布?
项目所有权
项目属于哪个用户或团队,以及他们离开后会发生什么。
多租户
将一个客户的项目、资源和模板与另一个客户分开。
自动保存
失败存档时会发生什么,失败时读者会看到什么。
模板
新页面的起点,以及模板变更如何到达已创建页面。
资源存储
上传内容的去向、命名方式以及谁有权获取。
出版
导出后的HTML和CSS如何成为访客可以加载的页面。
错误处理
一个无声失败的存档是编辑能遇到的最糟糕的bug。
备份
Project JSON 体积小,压缩效果很好。丢失它没有任何借口。
安全性
自定义代码块和粘贴的HTML是用户输入。使用时进行消毒。
性能
大型项目、庞大的资源列表和长长的撤销堆栈都有值得衡量的成本。
版本管理
修改、草稿,以及回滚被破坏页面的功能。
分界线

GrapesJS的职责与你的应用

以下内容并非对GrapesJS的批评——它是一个编辑器框架,这里才是它该停下来的正确地点。在开始之前知道具体内容,是避免三周制作变成九个月制作的关键。

你的应用
  • 认证不是在图书馆,这是刻意为之。
  • 角色与权限谁可以编辑,谁可以发表。
  • 项目所有权用户、团队、转移、删除。
  • 多租户客户之间的隔离。
  • 版本管理草稿、修订、回滚。
  • 出版将导出页面转换为活跃页面。
  • 备份保留与恢复。
  • 托管与域名DNS、证书、交付。
GrapesJS 核心
  • 编辑画布iframe、选择、悬停、工具栏。
  • 拖放移动、嵌套和重新排序组件。
  • 组件树类型、子、属性、traits。
  • Style Manager为选择编写CSS规则。
  • 响应式剪辑设备和每个断点样式。
  • 撤销并重做核心命令,默认是键盘绑定。
  • 项目数据文档序列化和恢复。
  • HTML/CSS 导出getHtml()和getCss()。
插件或者你的设置代码
  • 区块库核心版不提供任何预设、插件或你的。
  • 富文本编辑少量RTE飞船;CKEditor/TinyMCE/Froala互换。
  • 资源管道面板可以发货;但后面的存储空间则不行。
  • 存储适配器本地和远程飞船;你的API属于你。

其中八个完全属于你。这就是作品的真实形态,无论你选哪个视觉编辑,这个形态都是一样的。

选择你的道路

你在建造什么?

这些核心都是一样的。不同的是包围它的层次——而且每个层都有自己的指引。

避免这些

常见的GrapesJS错误

这些都源自同一个地方:把剪辑师当作产品,而不是其中一层。

  1. 混淆积木与组件

    你最终会给调色板条目添加行为,却不明白为什么物品一旦放到画布上就什么都没有。

    改为这样做

    块只负责创建东西。所有行为——traits、锁、渲染、验证——都属于它创建的组件类型。

  2. 将所有内容存储在浏览器状态

    默认存储是写入localStorage。看起来就像一直保存到读者切换设备、清空浏览器,或者在两个标签页中打开同一个项目。

    改为这样做

    在有人制作内容之前,先确定项目的真实位置,并在确定之前将storageManager: false设置为假。

  3. 完全没有配置存储

    工作会默默地存在于你从未设计过的商店里,而一旦有多个项目存在,加载顺序就变得不可预测。

    改为这样做

    配置远程存储,注册自定义存储,或者存档并加载getProjectData()和loadProjectData()。

  4. 为所有内容创建组件类型

    四十种几乎一模一样的类型,每种都有自己的traits,还有一个没人能导航的调色板。

    改为这样做

    优先选择带有traits的一种类型,而不是五种颜色差异的类型。扩展内置类型,而不是重建它们。

  5. 让Style Manager完全开放

    作者们追求浮动、绝对位置和13像素边距,每一页都离设计系统越来越远。

    改为这样做

    明确列出你的扇区,并每个组件使用 stylable / unstylable。控制更少,页面质量更好。

  6. 将GrapesJS视为完整的CMS

    花了好几周时间寻找那些从未存在过的用户、角色、工作流程和发布功能。

    改为这样做

    先读第12步的责任分工。GrapesJS是编辑层;它周围的CMS是你的产品。

  7. 我不打算存放资源

    如果没有上传配置,图片会以base64形式嵌入,存储的项目会不断增长,直到加载缓慢、移动变得尴尬。

    改为这样做

    第一天就把Asset Manager接到真实存储上,即使那个存储是磁盘上的文件夹。

  8. 不定义出版工作流程

    你有一个编辑器会保存,却没有回答“这怎么会变成访客可以打开的页面?”。

    改为这样做

    在构建编辑器之前,先从第8步草绘出流程。这通常会改变你存储的内容。

  9. 把所有自定义都放到一个插件里

    一个2000行的单一文件,用于注册块、类型、面板和命令,且不能分段重复使用或测试。

    改为这样做

    每个项目一个插件。他们会作曲,每个插件都可以被赋予选项。

  10. 直到最后都忽视了反应性行为

    页面在桌面画布上看起来很顺畅,手机上却会破碎,数百条桌面专用规则已经写好。

    改为这样做

    在构建过程中切换设备。样式是按设备写的,所以按一个宽度创作时,这个宽度会被烘焙进去。

故障排除

常见问题

第一次构建时最容易出错的五个问题,以及每项需要注意的事项。

编辑器未出现

通常是安装问题,而不是GrapesJS的问题。

检查项

  • 容器元素在init()运行时已存在于DOM中。
  • 容器有一个高度——零高度元素渲染零高度编辑器。
  • 加载了GrapesJS样式表;没有它时编辑器存在但不可见。
  • 初始化是在客户端运行,而不是在服务器渲染时。

样式缺失,或者编辑器看起来有问题

涉及两种不同的样式表,且都不容易加载。

检查项

  • grapesjs/dist/css/grapes.min.css 是为编辑器自己的 Chrome 加载的。
  • 你页面的CSS会传递到画布——它是iframe,所以你的应用样式表不会自动到达它。
  • Style Manager 配置了扇区;空扇区阵列渲染空面板。
  • 所选组件并未标注为您所寻找的房产的unstylable。

该项目不进行保存

注意storage:error——GrapesJS报告的是故障,而不是吞噬它们。

检查项

  • storageManager 是配置好的,类型与实际注册的存储相匹配。
  • urlStore 可访问并返回成功状态。
  • 凭据和头部正在发送——远程存储默认包含凭证。
  • 网络面板中没有CORS错误;被阻挡的预检看起来就像无声故障。

在React中,编辑器在重新渲染时会重复或死亡

几乎总是生命周期问题,不是GrapesJS的问题。

检查项

  • init() 运行一次,效果为空依赖数组。
  • destroy() 在清理阶段运行——React 18 Strict Mode 在开发中会挂载两次效果。
  • React 之后从未将子节点渲染到容器元素中。

构建或服务器导入时崩溃

GrapesJS 在模块评估时会触及窗口,因此在服务器渲染时无法导入。

检查项

  • 编辑器组件仅在客户端加载——动态导入时禁用 SSR,或在效果内导入。
  • 样式表不会导入服务器渲染模块。
下一步

继续学习GrapesJS

等到上面的编辑器说清楚后,大致按难度顺序去哪里。

教程还是参考资料?

本页是构建:安装、配置、扩展、运输。完整指南是参考——架构、生态系统和设计背后的理由。大多数人最终会按这个顺序阅读两者。

阅读完整指南
服务

需要帮助构建一个GrapesJS编辑器吗?

大部分教程都是一天的工作。底层——存储、租赁、发布、与设计系统匹配的编辑器——是项目最长的地方。GJS.Market可以承担这部分。

  • 自定义组件
  • 自定义插件
  • SaaS 页面构建器
  • 迁徙
  • 集成
  • 白标编辑
  • 存储与API集成
  • React / Next.js / Vue / Angular 积分
  • 生产架构
FAQ

常见问题解答

什么是GrapesJS?

GrapesJS 是一个开源的网页构建框架:一个你可以将它嵌入到自己应用中的拖放可视化编辑器。它为你提供了画布、组件树、样式管理器、资源管理器和导出步骤,并将账户、存储和发布任务留给了应用程序。

GrapesJS 是免费的吗?

是的。GrapesJS 免费下载和使用,包括商业用途。无需支付许可费,无需购买托管服务——你自己运行。市场中的可选插件可能需要付费;编辑器本身无需付费。

GrapesJS 是开源的吗?

是的。核心以BSD-3-Clause许可证发布,官方React封装@grapesjs/react则以MIT发布。两者都允许商业使用和修改。源代码在GitHub上,软件包在npm上。

我该如何安装GrapesJS?

npm 要么在带有构建步骤的项目中安装 grapesjs,要么在一个普通的 HTML 文件中安装两个 CDN 的标签。两者都给你相同的库;CDN 路线完全不需要工具。记得同时加载样式表和脚本——没有它,编辑器渲染了但看起来很坏。

我该如何制作我的第一个GrapesJS编辑器?

在页面上添加一个空元素,然后调用grapesjs.init({ container: '#gjs' })。这真的就是全部了。实际上你还需要高度,fromElement:false,这样编辑器不会采纳现有标记,storageManager:false,直到你确定项目的位置。

什么是GrapesJS块?

块是用户拖曳的调色板中的一个条目。它包含标签、分类、图标和要创建的内容。它没有自己的行为——只生成组件。GrapesJS 核心根本不附带任何块:你写入它们,或者添加预设插件。

什么是GrapesJS组件?

组件是画布中的一个节点:一个带有类型、属性、样式、子节点和traits的模型。画布是组件树,导出的HTML是从该树生成的,而不是反过来。

我该如何创建自定义组件?

调用editor.Components.addType('my-type', { model, view }),可选择扩展内置类型。该模型包含默认值、子节点、traits以及决定作者可更改内容的锁——droppable、stylable、removable。添加isComponent,使GrapesJS在解析保存的HTML时识别该类型。

我该如何添加自定义区块?

editor.Blocks.add('my-block', { label, category, media, content })。内容可以选择HTML字符串或组件定义,比如{ type: 'my-type' }。对象形式是你拥有自己组件类型后使用的。

我该如何保存GrapesJS项目?

将Storage Manager配置为类型为“remote”并加载/存储URL,注册editor.Storage.add()自定义存储,或者完全跳过,直接用自己的代码调用getProjectData()和loadProjectData()。存储默认开启,写入localStorage,这在生产环境中很少是你想要的。

我该如何导出HTML和CSS?

editor.getHtml() 和 editor.getCss()。有两点需要注意:getHtml() 会返回包裹在实体元素中的画布,getCss() 包含 GrapesJS 自己的保护画布重置,除非你通过 avoidProtected:是的。

可以在 React 中使用 GrapesJS 吗?

是的。在一个效果中初始化它,依赖数组为空,清理时销毁它,并且永远不要让React再渲染到容器里。如果你想把编辑器的UI组合成React组件,还有官方的包装器@grapesjs/react。

可以在 Next.js 中使用 GrapesJS 吗?

是的,但有一个前提:GrapesJS 在模块作用域时会接触窗口,所以必须只在客户端加载——比如禁用服务器渲染的动态导入,或者在特效中导入。其他内容和普通的 React 是一样的。

可以在 Vue 或 Angular 中使用 GrapesJS 吗?

是的。GrapesJS 渲染成一个普通的 DOM 元素,所以它可以支持任何框架:在挂载钩子调用 init(),在拆解钩子调用 destroy()。没有官方的 Vue 或 Angular 封装器——集成方式只有几行。

我可以用GrapesJS做一个SaaS页面构建器吗?

是的,这也是选择它最常见的原因之一。GrapesJS提供编辑层;你的应用提供账户、权限、租用、存储、模板和发布。在开始前知道这个分工,是短项目和长项目的区别。

我可以用插件扩展GrapesJS吗?

是的。插件是一个接收编辑器实例的函数,因此它可以注册块、组件类型、命令、面板、traits和样式扇区。用usePlugin();旧版grapesjs.plugins.add()的API已被弃用。

我在哪里可以找到GrapesJS插件?

官方维护的插件在npm上以GrapesJS组织名义发布。GJS.Market按类别——块、组件、存储、资源、富文本编辑器、预设和开发工具——目录中包含社区和商业插件,且本页所有列表均来自该类别。
轮到你了

准备好用 GrapesJS 构建了吗?

先从核心编辑器开始,针对你的产品进行定制,需要更多功能时再加插件和集成扩展。

从这里开始

开始教程

安装GrapesJS,十分钟内让编辑器运行。

进入第一步
延伸

浏览插件

来自GJS.Market目录的区块、组件、存储和资源提供者。

浏览插件
与我们一起建设

与我们的团队一起构建

定制组件、存储集成和生产架构,和你一起完成。

跟我们聊聊

本页所有样本均与GrapesJS、0.23.6、2026-09-03对比。