Craft.js 基础教程:构建可视化页面编辑器

Craft.js 基础教程:构建可视化页面编辑器

craft.js 🚀 A React Framework for building extensible drag and drop page editors craft.js 项目地址: https://gitcode.com/gh_mirrors/cr/craft.js

前言

Craft.js 是一个基于 React 的页面构建框架,它允许开发者创建高度可定制的可视化编辑器。本教程将带你从零开始构建一个简单的页面编辑器,通过实践掌握 Craft.js 的核心概念和基本用法。

环境准备

首先需要安装 Craft.js 核心包:

# 使用 yarn
yarn add @craftjs/core

# 或使用 npm
npm install --save @craftjs/core

为了快速构建 UI,我们还将安装 Material-UI 等辅助库:

yarn add @mui/material react-contenteditable material-ui-color-picker

构建用户组件

用户组件是最终用户可以在编辑器中操作的基础元素。我们先创建几个基本组件:

文本组件

// components/user/Text.js
import React from "react";
import { useNode } from "@craftjs/core";

export const Text = ({text, fontSize}) => {
  const { connectors: {connect, drag} } = useNode();
  return (
    <div ref={ref => connect(drag(ref))}>
      <p style={{fontSize}}>{text}</p>
    </div>
  )
}

Text.craft = {
  rules: {
    canDrag: (node) => node.data.props.text != "Drag"
  }
}

按钮组件

// components/user/Button.js
import React from "react";
import { Button as MaterialButton } from "@mui/material";
import { useNode } from "@craftjs/core";

export const Button = ({size, variant, color, children}) => {
  const { connectors: {connect, drag} } = useNode();
  return (
    <MaterialButton 
      ref={ref => connect(drag(ref))}
      size={size} 
      variant={variant} 
      color={color}
    >
      {children}
    </MaterialButton>
  )
}

容器组件

容器组件可以包含其他组件,并允许用户调整背景色和内边距:

// components/user/Container.js
import React from "react";
import { Paper } from "@mui/material";
import { useNode } from "@craftjs/core";

export const Container = ({background, padding = 0, children}) => {
  const { connectors: {connect, drag} } = useNode();
  return (
    <Paper 
      ref={ref => connect(drag(ref))}
      style={{margin: "5px 0", background, padding: `${padding}px`}}
    >
      {children}
    </Paper>
  )
}

卡片组件

卡片是一个复合组件,包含两个可拖放区域:

// components/user/Card.js
import React from "react";
import { Element, useNode } from "@craftjs/core";
import { Container } from "./Container";
import { Text } from "./Text";
import { Button } from "./Button";

// 定义卡片顶部区域(仅接受文本组件)
export const CardTop = ({children}) => {
  const { connectors: {connect} } = useNode();
  return (
    <div ref={connect} className="text-only">
      {children}
    </div>
  )
}

CardTop.craft = {
  rules: {
    canMoveIn: (incomingNodes) => 
      incomingNodes.every(node => node.data.type === Text)
  }
}

// 定义卡片底部区域(仅接受按钮组件)
export const CardBottom = ({children}) => {
  const { connectors: {connect} } = useNode();
  return (
    <div ref={connect}>
      {children}
    </div>
  )
}

CardBottom.craft = {
  rules: {
    canMoveIn: (incomingNodes) => 
      incomingNodes.every(node => node.data.type === Button)
  }
}

// 卡片主组件
export const Card = ({background, padding = 20}) => {
  return (
    <Container background={background} padding={padding}>
      <Element id="text" is={CardTop} canvas>
        <Text text="Title" fontSize={20} />
        <Text text="Subtitle" fontSize={15} />
      </Element>
      <Element id="buttons" is={CardBottom} canvas>
        <Button size="small">Learn more</Button>
      </Element>
    </Container>
  )
}

构建编辑器界面

工具箱组件

工具箱允许用户拖拽组件到画布:

// components/Toolbox.js
import React from "react";
import { useEditor } from "@craftjs/core";
import { Box, Typography, Grid, Button as MaterialButton } from "@mui/material";

export const Toolbox = () => {
  const { connectors } = useEditor();
  
  return (
    <Box px={2} py={2}>
      <Typography>Drag to add</Typography>
      <Grid container direction="column" spacing={1}>
        <Grid item>
          <MaterialButton 
            ref={ref => connectors.create(ref, <Button />)}
            variant="contained"
          >
            Button
          </MaterialButton>
        </Grid>
        {/* 其他组件按钮... */}
      </Grid>
    </Box>
  )
}

设置面板

设置面板用于编辑选中组件的属性:

// components/SettingsPanel.js
import React from "react";
import { useNode } from "@craftjs/core";
import { Box, Chip, Typography, Slider } from "@mui/material";

export const SettingsPanel = () => {
  const { actions: {setProp}, props } = useNode((node) => ({
    props: node.data.props
  }));

  return (
    <Box bgcolor="rgba(0, 0, 0, 0.06)" mt={2} px={2} py={2}>
      <Typography variant="subtitle1">Selected</Typography>
      <Slider
        value={props.fontSize || 12}
        onChange={(e, value) => setProp(props => props.fontSize = value)}
      />
    </Box>
  )
}

主编辑器

最后将所有组件组合起来:

// pages/index.js
import React from "react";
import { Editor, Frame, Element } from "@craftjs/core";
import { Grid, Typography, Paper } from "@mui/material";

import { Toolbox, SettingsPanel, Topbar } from "../components";
import { Container, Button, Text, Card } from "../components/user";

export default function App() {
  return (
    <div style={{margin: "0 auto", width: "800px"}}>
      <Typography variant="h5" align="center">页面编辑器</Typography>
      <Editor resolver={{Card, Button, Text, Container}}>
        <Grid container spacing={3}>
          <Grid item xs>
            <Frame>
              <Element is={Container} padding={5} background="#eee" canvas>
                <Card />
              </Element>
            </Frame>
          </Grid>
          <Grid item xs={3}>
            <Paper>
              <Toolbox />
              <SettingsPanel />
            </Paper>
          </Grid>
        </Grid>
      </Editor>
    </div>
  );
}

核心概念解析

  1. Editor 组件:提供编辑器上下文,管理整个编辑器的状态
  2. Frame 组件:定义可编辑区域,将渲染控制权交给 Craft.js
  3. Element 组件:用于定义节点类型和属性
    • is 属性指定组件类型
    • canvas 属性使节点成为可放置区域
  4. useNode 钩子:在用户组件中使用,提供节点相关方法和状态
  5. useEditor 钩子:提供全局编辑器方法和状态

进阶功能

完成基础教程后,你可以进一步探索:

  1. 实现撤销/重做功能
  2. 添加组件复制/粘贴支持
  3. 实现多层级嵌套组件
  4. 开发自定义渲染器
  5. 集成状态持久化

通过本教程,你已经掌握了 Craft.js 的基本用法。这个框架的强大之处在于它的灵活性和可扩展性,你可以基于这些基础知识构建出各种复杂的可视化编辑器。

craft.js 🚀 A React Framework for building extensible drag and drop page editors craft.js 项目地址: https://gitcode.com/gh_mirrors/cr/craft.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

管翔渊Lacey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值