Skip to content

Editor 元素

图形编辑器,用于编辑操作图形,可移动、缩放、旋转、倾斜,支持多选、框选、编组、 双击进组,锁定、层级,向专业级设计工具看齐。支持自定义 配置编辑工具内部编辑器

继承

Editor  >  Group  >  UI


一般由应用自动生成,通过 app.editor 获取实例。

后面计划会增加精细化的属性编辑器,如编辑圆角、形状、路径等,暂时与 自动布局 元素不能混用。

安装插件

需要安装 编辑器插件、resize 插件 才能使用,点此访问 Github 仓库

sh
npm install @leafer-in/editor
npm install @leafer-in/resize
sh
pnpm add @leafer-in/editor
pnpm add @leafer-in/resize
sh
yarn add @leafer-in/editor
yarn add @leafer-in/resize
sh
bun add @leafer-in/editor
bun add @leafer-in/resize

或通过 script 标签引入,使用全局变量 LeaferIN.editor 访问插件内部功能。

html
<script src="https://unpkg.com/@leafer-in/editor@1.5.1/dist/editor.min.js"></script>
<script src="https://unpkg.com/@leafer-in/resize@1.5.1/dist/resize.min.js"></script>
<script>
  const { Editor } = LeaferIN.editor
</script>
html
<script src="https://unpkg.com/@leafer-in/editor@1.5.1/dist/editor.js"></script>
<script src="https://unpkg.com/@leafer-in/resize@1.5.1/dist/resize.js"></script>
<script>
  const { Editor } = LeaferIN.editor
</script>

关键属性

target:UI | UI[]

设置需要编辑的元素, 默认通过 编辑选择器 自动选取 editable 元素(不用设置 draggable )。

element: UI

选中元素的唯一代表(只读), 可使用 element 元素的布局属性方法直接移动、变换编辑框。

单选时为选中元素, 多选时为模拟元素(代替多个元素),用来同步布局编辑框。

config: IEditorConfig

编辑器配置,应用初始化时通过 editor: {} 配置对象传入。

可通过 app.editor.config 实时修改 ( 特殊情况调用 updateEditTool() 方法立即更新 )。

次要属性

hoverTarget:UI

设置 hover 状态的元素, 默认通过 编辑选择器 自动选取 editable 元素。

mergeConfig: IEditorConfig

实际使用的编辑器配置(只读),实时合并编辑器的 config 与元素的 editConfig,频繁访问会有性能开销。

更多属性

名称描述
状态
visible是否显示编辑器,隐藏后,交互功能也将禁用
hittable编辑器是否响应交互事件,设为 false 后,将禁用编辑器交互
single是否只选中了单个元素
multiple是否选中了多个元素
editing是否处于编辑状态,选择元素后即进入编辑状态
innerEditing是否处于内部编辑状态,双击单个元素进入内部编辑状态(有内部编辑器的情况)
groupOpening是否处于打开组状态,双击组可进入打开状态,方便选择组内元素
dragging是否正在拖拽编辑器,包含拖拽控制点、边
列表
list当前选中的元素列表(只读),未选中时为空数组
leafList当前选中的元素列表对象(只读), 未选中时为空列表对象
openedGroupList当前处于打开状态的组列表对象(只读), 未选中时为空列表对象
显示元素
buttons按钮组,用于放置自定义按钮,整体 around 对齐, 位于编辑器底部,可以 进行配置
editBox编辑框,负责编辑框的显示与交互,了解 EditBoxEditPoint
editTool当前使用的编辑工具,用来编辑元素的尺寸、外形,选中元素时会自动载入,可 自定义编辑工具
innerEditor当前使用的内部编辑器,用来编辑文本、路径等内部细节,通过双击元素打开, 可 自定义内部编辑器
selector选择器,负责单选、多选、框选元素的交互,渲染元素选中、hover 线框

关键方法

名称描述
选择
select()选中元素
cancel()取消选中元素
hasItem()是否已选中某个元素
addItem()新增一个元素到选中列表
removeItem()从选中列表中移出元素
编组
group()将选中的元素进行编组
ungroup()将选中的元素进行解组
openGroup()打开组, 模拟双击打开组的功能
closeGroup()关闭组,关闭双击打开的组
内部编辑
openInnerEditor()打开元素的内部编辑器
closeInnerEditor()关闭内部编辑器
锁定
lock()锁定选中的元素
unlock()解锁选中的元素
层级
toTop()移动选中元素到最顶层(所属 Group 中)
toBottom()移动选中元素到最底层(所属 Group 中)
更新
update()手动更新编辑器的布局、样式等
updateEditBox()手动更新编辑框,使其贴合元素,一般用于多选元素对齐后操作。
updateEditTool()更新编辑工具,选择元素后自动调用此方法
变换
move()位移选中元素 增量操作
flip()按轴方向( 世界坐标系) 镜像/翻转选中元素
scaleOf()围绕 element 元素的原点 origin( box 坐标 )缩放选中元素 增量操作
rotateOf()围绕 element 元素的原点 origin( box 坐标 )旋转选中元素 增量操作
skewOf()围绕 element 元素的原点 origin( box 坐标 )倾斜选中元素 增量操作

配置 editor

基础     事件     样式     按钮组     光标     选择     控制     启用     内部编辑器

resize 元素

编辑器默认通过修改元素的宽高、路径坐标来调整大小,与专业级的设计工具效果一致。

同时也支持配置通过 fontSize、缩放属性来调整大小, 了解 editSize

辅助

快捷键

历史记录

事件

编辑器操作事件,通过 app.editor.on() 监听。

名称描述
EditorEvent选中、取消元素事件
EditorMoveEvent移动事件
EditorScaleEvent调整大小事件
EditorRotateEvent旋转事件
EditorSkewEvent倾斜事件
EditorGroupEvent编组事件
InnerEditorEvent内部编辑器事件

开始体验

  1. 点击选中元素, 支持多选、框选。

  2. 将光标移动至控制点或边上,拖动可缩放元素。

  3. 将光标移动至控制点外沿,出现方向光标,拖动可旋转元素。

按住Ctrl / Command 键,将光标移动至四条边上,拖动可倾斜元素。

注意事项

编辑器必须在 App 中使用, 通过传入 editor 配置,可实现自动创建 app.editor 实例及 app.tree, app.sky 分层结构,并自动将 editor 添加到 sky 层中。

ts
// #图形编辑器 [简洁创建]
import { App, Rect } from 'leafer-ui'
import '@leafer-in/editor' // 导入图形编辑器插件 //
import '@leafer-in/viewport' // 导入视口插件 (可选)

const app = new App({
    view: window,
    editor: {}  //  配置 editor 会自动创建并添加 app.editor 实例、tree 层、sky 层
})

app.tree.add(Rect.one({ editable: true, fill: '#FEB027', cornerRadius: [20, 0, 0, 20] }, 100, 100))
app.tree.add(Rect.one({ editable: true, fill: '#FFE04B', cornerRadius: [0, 20, 20, 0] }, 300, 100))
ts
// #图形编辑器 [实现原理]
import { App, Rect } from 'leafer-ui'
import { Editor } from '@leafer-in/editor' // 导入图形编辑器插件 //
import '@leafer-in/viewport' // 导入视口插件 (可选)

const app = new App({ view: window }) 

app.tree = app.addLeafer({ type: 'design' }) // 添加 tree 层
app.sky = app.addLeafer() // 添加 sky 层

app.tree.add(Rect.one({ editable: true, fill: '#FEB027', cornerRadius: [20, 0, 0, 20] }, 100, 100))
app.tree.add(Rect.one({ editable: true, fill: '#FFE04B', cornerRadius: [0, 20, 20, 0] }, 300, 100))

app.sky.add(app.editor = new Editor())  // 添加图形编辑器,用于选中元素进行编辑操作

示例

点击选中元素

元素必需要有 editable 属性才能被选取, 可通过 app.editor 快速访问编辑器实例。

ts
// #图形编辑器 [editable]
import { App, Rect } from 'leafer-ui'
import '@leafer-in/editor' // 导入图形编辑器插件 //
import '@leafer-in/viewport' // 导入视口插件 (可选)

const app = new App({ view: window, editor: {} })

const rect1 = Rect.one({ editable: true, fill: '#FEB027', cornerRadius: [20, 0, 0, 20] }, 100, 100) 
const rect2 = Rect.one({ editable: true, fill: '#FFE04B', cornerRadius: [0, 20, 20, 0] }, 300, 100)

app.tree.add(rect1)
app.tree.add(rect2)

显示底部旋转控制点

默认会继承基础样式,可以进一步 配置 旋转控制点的样式。

ts
// #图形编辑器 [显示旋转控制点]
import { App, Rect } from 'leafer-ui'
import '@leafer-in/editor' // 导入图形编辑器插件 //
import '@leafer-in/viewport' // 导入视口插件 (可选)

const app = new App({
    view: window,
    editor: { circle: {} }
})

const rect = Rect.one({ editable: true, fill: '#32cd79', cornerRadius: 30 }, 100, 100)
app.tree.add(rect)

app.editor.select(rect)

显示中间控制点,并修改样式

默认会继承基础样式、旋转角度, 可以精确 设置 每个控制点的样式。

ts
// #图形编辑器 [显示中间控制点,并修改样式]
import { App, Rect } from 'leafer-ui'
import '@leafer-in/editor' // 导入图形编辑器插件 //
import '@leafer-in/viewport' // 导入视口插件 (可选)

const app = new App({
    view: window,
    editor: {
        point: { cornerRadius: [0, 0, 10, 0] },
        middlePoint: { width: 12, height: 4, cornerRadius: 2 }
    }
})

const rect = Rect.one({ editable: true, fill: '#32cd79', cornerRadius: 30 }, 100, 100)
app.tree.add(rect)

app.editor.select(rect)

添加底部固定按钮

元素旋转、翻转后仍保持固定方位,可以 配置 按钮组的方位。

ts
// #图形编辑器 [添加底部固定按钮]
import { App, Rect, Box, PointerEvent } from 'leafer-ui'
import '@leafer-in/editor' // 导入图形编辑器插件 //
import '@leafer-in/viewport' // 导入视口插件 (可选)

const app = new App({
    view: window,
    editor: { buttonsFixed: true }
})

const rect = Rect.one({ editable: true, fill: '#32cd79' }, 100, 100)
app.tree.add(rect)
app.tree.add(Rect.one({ editable: true, fill: '#32cd79' }, 100, 300))

const button = Box.one({  // // 添加移除按钮
    around: 'center',
    fill: '#FEB027',
    cornerRadius: 20,
    cursor: 'pointer',
    children: [{ tag: 'Text', fill: 'white', text: '移除', padding: [7, 10] }]
})

app.editor.buttons.add(button)

button.on(PointerEvent.TAP, () => { // 点击删除元素,并取消选择
    app.editor.list.forEach(rect => rect.remove())
    app.editor.target = null
})

app.editor.select(rect)

监听选择事件

ts
// #图形编辑器 [选中元素事件]
import { App, Rect } from 'leafer-ui'
import { EditorEvent } from '@leafer-in/editor' // 导入图形编辑器插件 //
import '@leafer-in/viewport' // 导入视口插件 (可选)

const app = new App({
    view: window,
    editor: {}
})

app.tree.add(Rect.one({ fill: '#32cd79', editable: true }, 100, 100))
app.tree.add(Rect.one({ fill: '#32cd79', editable: true }, 300, 100))

app.editor.on(EditorEvent.SELECT, (e: EditorEvent) => {
    console.log(e.editor.list)
})
js
import { App, Rect } from 'leafer-ui'
import { EditorEvent } from '@leafer-in/editor' // 导入图形编辑器插件 //
import '@leafer-in/viewport' // 导入视口插件 (可选)

const app = new App({
    view: window,
    editor: {}
})

app.tree.add(Rect.one({ fill: '#32cd79', editable: true }, 100, 100))
app.tree.add(Rect.one({ fill: '#32cd79', editable: true }, 300, 100))

app.editor.on(EditorEvent.SELECT, (e) => {
    console.log(e.editor.list)
})

手动旋转元素

ts
// #图形编辑器 [手动旋转元素]
import { App, Rect } from 'leafer-ui'
import '@leafer-in/editor' // 导入图形编辑器插件 //
import '@leafer-in/viewport' // 导入视口插件 (可选)

const app = new App({ view: window, editor: {} })

const rect = Rect.one({ editable: true, fill: '#FEB027', cornerRadius: [20, 0, 0, 20] }, 100, 100)

app.tree.add(rect)
app.tree.add(Rect.one({ editable: true, fill: '#FFE04B', rotation: 10, cornerRadius: [0, 20, 20, 0] }, 300, 100))


app.editor.select(rect) // 选中 rect

setTimeout(() => {

    // 手动旋转到45度 
    const rotation = 45

    // 围绕中心旋转到指定 rotation, 需减去元素的 rotation,如下:
    app.editor.rotateOf('center', rotation - rect.rotation)

}, 2000)

创建图形模式

ts
// #图形编辑器 [创建图形]
import { App, DragEvent, Rect } from 'leafer-ui'
import '@leafer-in/editor' // 导入图形编辑器插件 //
import '@leafer-in/viewport' // 导入视口插件 (可选)


const app = new App({ view: window, editor: {}, fill: '#333' })

app.tree.add({ tag: 'Text', x: 100, y: 100, text: '2秒后,按下鼠标拖动可创建矩形', fill: '#999', fontSize: 16 })


app.tree.add(Rect.one({ editable: true, fill: '#FEB027', cornerRadius: [20, 0, 0, 20] }, 100, 300))
app.tree.add(Rect.one({ editable: true, fill: '#FFE04B', rotation: 10, cornerRadius: [0, 20, 20, 0] }, 300, 300))

app.editor.select(app.tree.children[2])

setTimeout(() => {

    // 2秒后进入创建图形模式 //
    app.editor.visible = false
    app.tree.hitChildren = false

    // 创建矩形(拖拽)
    let rect: Rect

    app.on(DragEvent.START, () => {
        rect = new Rect({ editable: true, fill: '#32cd79' })
        app.tree.add(rect)
    })

    app.on(DragEvent.DRAG, (e: DragEvent) => {
        if (rect) rect.set(e.getPageBounds()) // 获取事件在 page 坐标系中绘制形成的包围盒  //
    })

}, 2000)
js
// #图形编辑器 [创建图形]
import { App, DragEvent, Rect } from 'leafer-ui'
import '@leafer-in/editor' // 导入图形编辑器插件 //
import '@leafer-in/viewport' // 导入视口插件 (可选)


const app = new App({ view: window, editor: {}, fill: '#333' })

app.tree.add({ tag: 'Text', x: 100, y: 100, text: '2秒后,按下鼠标拖动可创建矩形', fill: '#999', fontSize: 16 })

app.tree.add(Rect.one({ editable: true, fill: '#FEB027', cornerRadius: [20, 0, 0, 20] }, 100, 100))
app.tree.add(Rect.one({ editable: true, fill: '#FFE04B', rotation: 10, cornerRadius: [0, 20, 20, 0] }, 300, 100))

app.editor.select(app.tree.children[0])

setTimeout(() => {

    // 2秒后进入创建图形模式 //
    app.editor.visible = false
    app.tree.hitChildren = false

    // 创建矩形(拖拽)
    let rect

    app.on(DragEvent.START, () => {
        rect = new Rect({ editable: true, fill: '#32cd79' })
        app.tree.add(rect)
    })

    app.on(DragEvent.DRAG, (e) => {
        if (rect) rect.set(e.getPageBounds())  // 获取事件在 page 坐标系中绘制形成的包围盒  //
    })

}, 2000)

Released under the MIT License.