這款國外開源框架, 讓你輕鬆構建自己的頁面編輯器

徐小夕發表於2021-08-09

前段時間我一直在設計和研究低程式碼搭建平臺,也開源了幾款視覺化編輯器框架,最近在 github 上發現了一款非常強大的基於自然流佈局的頁面搭建框架 GrapesJS,接下來我就帶大家摸索一下這款框架。

按照我一向的寫作風格,我會在下面列出文章的大綱,以便大家有選擇且高效率的閱讀和學習:

  • GrapesJS 框架基本介紹

  • 如何使用 GrapesJS 構建 web 編輯器

  • 基於 GrapesJS 構建的開源網頁編輯器 craft.js

  • 更多視覺化編輯器推薦

基本介紹

chrome-capture.gif

乍眼一看我們可能會認為它只是一個頁面/HTML 編輯器,但它能做的不僅僅如此。GrapesJS 是一個多用途的 Web 頁面搭建框架,這意味著它允許我們輕鬆建立一個支援拖放的任何具有類似 HTML 結構的構建器。它所包含的內容遠不止網頁。我們使用類似 HTML 的結構的場景有:

並且 GrapesJS 附帶的功能和工具使我們能夠製作易於使用的編輯器。這使使用者無需任何編碼知識即可建立複雜的類似 HTML 的模板。

同時 GrapesJS 官網上還給我們提供了3個不同場景的案例, 我們可以參考這些案例快速製作屬於我們自己的web編輯器:

那麼至於這些搭建框架的實現原理, 我之前的文章中也做了很多剖析和設計, 大家如果感興趣可以參考研究一下, 接下來我們看看如何安裝和使用它.

如何使用 GrapesJS 構建 web 編輯器

1. 安裝

我們可以用 umd 的方式來匯入:

<link rel="stylesheet" href="//unpkg.com/grapesjs/dist/css/grapes.min.css">
<script src="//unpkg.com/grapesjs"></script>
複製程式碼

也可以通過 npm 來安裝:

npm i grapesjs -S
複製程式碼

之後我們可以通過如下方式匯入到專案:

import 'grapesjs/dist/css/grapes.min.css';
import grapesjs from 'grapesjs';
複製程式碼

2. 第一個demo

在安裝完之後, 我們先實現一個基本的頁面編輯demo:

chrome-capture (1).gif

相關程式碼如下:

<html>
  <head>
    <link rel="stylesheet" href="//unpkg.com/grapesjs/dist/css/grapes.min.css">
    <script src="//unpkg.com/grapesjs"></script>
    <style>
        #gjs {
          border: 3px solid #444;
        }
        .gjs-cv-canvas {
          top: 0;
          width: 100%;
          height: 100%;
        }
    </style>
  </head>
  <body>
    <div id="gjs">
      <h1>Hello World Component!</h1>
    </div>
    <script>
       const editor = grapesjs.init({
          container: '#gjs',
          // 我們也可以使用可選的: `components: '<h1>Hello World Component!</h1>'`,
          fromElement: true,
          // 編輯器尺寸
          height: '300px',
          width: 'auto',
          // 禁用儲存管理, 下面的文章我會介紹
          storageManager: false,
          panels: { defaults: [] },
         });
    </script>
  </body>
</html>
複製程式碼

這樣就實現了一個簡單的編輯器, 是不是很簡單呢? 我們接下來繼續探索更強大的功能。

3. 新增和定義元件

我們都知道網頁編輯器需要提供非常豐富的元件, 這樣能幫助使用者更輕鬆的搭建頁面, 同樣 grapesjs 支援新增各種自定義元件, 也內建了常用的基礎元件, 我們來看一個 demo :

chrome-capture (2).gif

由以上 demo 我們可以看到新增了3個基本元件: 區塊, 文字, 圖片。基本實現程式碼如下:

const editor = grapesjs.init({
  // ...其他配置
  blockManager: {
    appendTo: '#blocks',
    blocks: [
      {
        id: 'section',
        label: '<b>Section</b>', 
        attributes: { class:'gjs-block-section' },
        content: `<section>
          <h1>H5-Dooring</h1>
          <div>積木式搭建H5頁面</div>
        </section>`,
      }, {
        id: 'text',
        label: 'Text',
        content: '<div data-gjs-type="text">My Baby</div>',
      }, {
        id: 'image',
        label: 'Image',
        select: true,
        content: { type: 'image' },
        activate: true,
      }
    ]
  },
});
複製程式碼

由程式碼我們可以發現我們只需要在 blockManagerblocks 裡新增指定的元件即可。同時我們還可以動態的新增元件:

editor.BlockManager.add('my-block-id', {
    // ...其他配置如label
    content: {
        tagName: 'div',
        draggable: false,
        attributes: { 'some-attribute': 'some-value' },
        components: [
          {
            tagName: 'span',
            content: '<b>DooringX</b>',
          }, {
            tagName: 'div',
            components: '<span>無限可能</span>',
          }
        ]
      }
})
複製程式碼

至於更詳細的元件配置文件, 大家可以參考文件: grapesjs元件如何工作

image.png

4. 新增功能皮膚

僅僅實現元件新增還不夠, 一個有尊嚴的編輯器還應該有各種功能按鈕, 來實現不同使用者的需求。

現在我們有了畫布和自定義元件,讓我們看看如何建立一個功能皮膚,裡面有按鈕(使用Panels API)。

chrome-capture (3).gif

我們可以看到頂部有3個功能按鈕:

  • 是否顯示元件邊線
  • 顯示原始碼
  • 顯示json

首先我們需要定義用來展示功能皮膚的元素(樣式可以自定義):

<div class="panel__top">
    <div class="panel__basic-actions"></div>
</div>
複製程式碼

其次我們來定義掛載功能皮膚:

editor.Panels.addPanel({
  id: 'panel-top',
  el: '.panel__top',
});
editor.Panels.addPanel({
  id: 'basic-actions',
  el: '.panel__basic-actions',
  buttons: [
    {
      id: 'visibility',
      active: true,
      className: 'btn-toggle-borders',
      label: '<u>B</u>',
      command: 'sw-visibility',
    }, {
      id: 'export',
      className: 'btn-open-export',
      label: 'Exp',
      command: 'export-template',
      context: 'export-template', 
    }, {
      id: 'show-json',
      className: 'btn-show-json',
      label: 'JSON',
      context: 'show-json',
      command(editor) {
        editor.Modal.setTitle('Components JSON')
          .setContent(`<textarea style="width:100%; height: 250px;">
            ${JSON.stringify(editor.getComponents())}
          </textarea>`)
          .open();
      },
    }
  ],
});
複製程式碼

我們可以定義更多的功能, 大家可以參考文件來學習使用。

5. 新增圖層管理皮膚

在處理 Web 元素時,我們可能會發現另一個常見的工具是圖層管理器。它是樹狀結構的,使我們能夠輕鬆地對頁面元素進行管理。要啟用它,我們只需指定要渲染它的位置:

const editor = grapesjs.init({
  // ...
  layerManager: {
    appendTo: '.layers-container'
  },
  // 我們能定義一個預設的皮膚作為側邊圖層管理器
  panels: {
    defaults: [{
      id: 'layers',
      el: '.panel__right',
      // 定義皮膚能否拖拽
      resizable: {
        maxDim: 350,
        minDim: 200,
        tc: 0,
        cl: 1, // 左側可拖拽
        cr: 0,
        bc: 0,
        keyWidth: 'flex-basis',
      },
    }]
  }
});
複製程式碼

效果如下:

chrome-capture (4).gif

我們可以看到右側的圖層皮膚, 可以輕鬆管理我們頁面上的元素。

6. 新增樣式配置皮膚

樣式皮膚也很簡單, 我們先定義對應的容器:

<div class="panel__right">
    <div class="layers-container"></div>
    <div class="styles-container"></div>
</div>
複製程式碼

然後初始化對應的配置指令碼:

const editor = grapesjs.init({
  // ...
  panels: {
    defaults: [
      // ...
      {
        id: 'panel-switcher',
        el: '.panel__switcher',
        buttons: [{
            id: 'show-layers',
            active: true,
            label: 'Layers',
            command: 'show-layers',
            // Once activated disable the possibility to turn it off
            togglable: false,
          }, {
            id: 'show-style',
            active: true,
            label: 'Styles',
            command: 'show-styles',
            togglable: false,
        }],
      }
    ]
  },
  selectorManager: {
    appendTo: '.styles-container'
  },
  styleManager: {
    appendTo: '.styles-container',
    sectors: [{
        name: 'Dimension',
        open: false,
        buildProps: ['width', 'min-height', 'padding'],
        properties: [
          {
            type: 'integer',
            name: 'The width',
            property: 'width', 
            units: ['px', '%'], 
            defaults: 'auto', 
            min: 0, 
          }
        ]
      },{
        name: 'Extra',
        open: false,
        buildProps: ['background-color', 'box-shadow', 'custom-prop'],
        properties: [
          {
            id: 'custom-prop',
            name: 'Custom Label',
            property: 'font-size',
            type: 'select',
            defaults: '32px',
            options: [
              { value: '12px', name: 'Tiny' },
              { value: '18px', name: 'Medium' },
              { value: '32px', name: 'Big' },
            ],
         }
        ]
      }]
  },
});

// 定義指令
editor.Commands.add('show-layers', {
  getRowEl(editor) { return editor.getContainer().closest('.editor-row'); },
  getLayersEl(row) { return row.querySelector('.layers-container') },

  run(editor, sender) {
    const lmEl = this.getLayersEl(this.getRowEl(editor));
    lmEl.style.display = '';
  },
  stop(editor, sender) {
    const lmEl = this.getLayersEl(this.getRowEl(editor));
    lmEl.style.display = 'none';
  },
});
editor.Commands.add('show-styles', {
  getRowEl(editor) { return editor.getContainer().closest('.editor-row'); },
  getStyleEl(row) { return row.querySelector('.styles-container') },

  run(editor, sender) {
    const smEl = this.getStyleEl(this.getRowEl(editor));
    smEl.style.display = '';
  },
  stop(editor, sender) {
    const smEl = this.getStyleEl(this.getRowEl(editor));
    smEl.style.display = 'none';
  },
});
複製程式碼

我們可以看看配置後的效果:

chrome-capture (5).gif

7. 更多用法演示

除了以上介紹的功能, 我們還能實現:

  • 定義響應模式(pc, 移動, ipad),
  • 設定儲存和載入資料的模式
  • 自定義主題
  • 國際化 i18n 支援

這裡就不一一介紹了, 我們直接看一下配置後的演示效果:

chrome-capture (6).gif

基於 GrapesJS 構建的開源網頁編輯器 craft.js

那麼 GrapesJS 還有很多有意思的功能我們可以挖掘, 接下來我和大家分享一款基於GrapesJS 二次封裝的一個開源編輯器框架 craft.js

chrome-capture (7).gif

我們可以使用它外掛化的搭建我們自己的編輯器, 如下是一個應用在React中的例子:

import {Editor, Frame, Canvas, Selector} from "@craftjs/core";
// 定義本文元件
import {useNode} from "@craftjs/core";

const TextComponent = ({text}) => {
  const { connectors: {drag} } = useNode();

  return (
    <div ref={drag}>
      <h2>{text}</h2>
    </div>
  )
}

// 初始化編輯器
const App = () => {
  return (
    <div>
      <Editor>
        // 可編輯的區域
        <Frame resolver={TextComponent, Container}>
          <Canvas>
            <TextComponent text="趣談前端 - 徐小夕" />
          </Canvas>
        </Frame>
      </Editor>
    </div>
  )
}
複製程式碼

更多視覺化編輯器推薦

最後

後期我會在資料視覺化和工程化上輸出更多實用的開源專案和框架,如果有其他問題或需求,可以和筆者交流學習。如果這篇文章對你有幫助,希望能給筆者 點贊+收藏 以此鼓勵作者繼續創作前端硬核文章。也可以關注作者公眾號 趣談前端 第一時間推送前端好文。

相關文章