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>
);
}
核心概念解析
- Editor 组件:提供编辑器上下文,管理整个编辑器的状态
- Frame 组件:定义可编辑区域,将渲染控制权交给 Craft.js
- Element 组件:用于定义节点类型和属性
is
属性指定组件类型canvas
属性使节点成为可放置区域
- useNode 钩子:在用户组件中使用,提供节点相关方法和状态
- useEditor 钩子:提供全局编辑器方法和状态
进阶功能
完成基础教程后,你可以进一步探索:
- 实现撤销/重做功能
- 添加组件复制/粘贴支持
- 实现多层级嵌套组件
- 开发自定义渲染器
- 集成状态持久化
通过本教程,你已经掌握了 Craft.js 的基本用法。这个框架的强大之处在于它的灵活性和可扩展性,你可以基于这些基础知识构建出各种复杂的可视化编辑器。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考