无界云图是爱趣五科技开源的一款在线图片编辑工具,工具采用 B/S 架构,底层基于 Leafjs(一款高效的 2D Canvas 渲染引擎)做的 开发,该技术文档是为了方便开发者快速接入并进行二次开发所著,文档中有一些专业术语如果有疑问的请告知我们。
主要技术&框架如下:
src
├─assets 静态资源
│ └─images
├─components 公共组件
│ ├─error-boundary 错误提示
│ ├─list-status 异步列表
│ ├─login 登录/注册模块
│ │ ├─loginMobile 手机登录
│ │ ├─loginQrcode 二维码登录
│ │ └─loginRegisterBox
│ ├─not-found 404页面
│ ├─page-loading 页面顶部的加载条
│ └─water-full 瀑布流组件
├─config 全局配置
├─icons 公共icon
├─language 多语言配置
├─layout 布局框架
│ ├─index-layout
│ └─manage-layout
├─less 全局less
├─pages 页面模块
│ ├─editor 编辑器页面
│ │ ├─common 公共组件
│ │ │ ├─dragitem 拖拽素材到画布
│ │ │ └─source 资源列表框
│ │ ├─components 编辑器大模块
│ │ │ ├─canvas 画布区域组件
│ │ │ ├─contextMenu 鼠标右键菜单
│ │ │ ├─header 编辑器顶部栏
│ │ │ ├─options 设置区域模块
│ │ │ │ ├─components 设置小模块
│ │ │ │ │ ├─align 对齐方式设置
│ │ │ │ │ ├─background 背景设置
│ │ │ │ │ ├─blur 模糊设置
│ │ │ │ │ ├─border 边框设置
│ │ │ │ │ ├─color 颜色设置
│ │ │ │ │ ├─colour 叠加模式设置
│ │ │ │ │ ├─filter 滤镜设置
│ │ │ │ │ ├─flipxy 翻转、复制、锁定、可见等快捷按钮
│ │ │ │ │ ├─group 组设置
│ │ │ │ │ ├─item 公共布局小模块
│ │ │ │ │ ├─opacity 透明度设置
│ │ │ │ │ ├─position 位置设置
│ │ │ │ │ ├─radius 圆角设置
│ │ │ │ │ ├─rotation 旋转设置
│ │ │ │ │ ├─set-color 颜色设置
│ │ │ │ │ ├─shadow 阴影设置
│ │ │ │ │ ├─size 宽高设置
│ │ │ │ │ ├─slider-input 滚动条插件
│ │ │ │ │ ├─strength 强度设置
│ │ │ │ │ ├─text-content 文字内容修改
│ │ │ │ │ └─text-style 文字样式修改
│ │ │ │ └─elements 元素的设置区域功能配置文件
│ │ │ ├─sidebar 左侧菜单
│ │ │ └─sources 左侧资源列表
├─server API服务
├─stores mobx的store管理
├─theme 主题配置
└─utils 公共插件
图层包括:图片、文字等元素,图层可以理解为有层级的元素,上面的图层会挡住下面的图层,为了增加项目的扩展性,后续我们会针对 图层做插件化处理,让开发者可以添加自己的图层元素,比如二维码,图表,表格,词云等元素。
我们使用 JSON 数据来保存工程数据,将工程数据传入渲染内核就可以进行视图的预览、编辑、导出等操作。
工程数据中需要明白页面
,图层
的概念,一个工程中可以包含多个页面,比如我们在设计一个名片的时候,有正面和反面,这就涉及
到 2 个页面,而每个页面下面又有多个图层元素,比如:姓名、LOGO、地址、电话等等、职位。我们可以归纳为:
了解完图层、页面的概念,接下来我们了解工程文件,工程文件就是记录页面,元素数据的总文件。工程数据说明请参考内核代
码src/pages/editor/core/types/data.ts
中的ViewData
,下面是一个工程数据案例:
import type { ViewData, ImageLayer, BasePage, TextLayer } from './core/types/data';
export const tdata: ViewData = {
name: 'mock数据', // 工程名称
desc: '暂无描述', // 工程的描述
version: '1.0.0', // 工程文件当前的版本(版本号是和我们的内核相关的)
thumb: '', // 工程项目的封面图
selectPageId: 'p1', // 默认展示的页面id
createTime: 0, // 创建时间
updateTime: 0, // 更新时间
pages: [ // 页面
{
id: 'p1',
name: 'p1',
desc: '',
width: 1000, // 页面的尺寸
height: 600,
background: { // 页面背景
type: 'solid',
color: '#fff',
},
layers: [ // 图层
{
id: 't1',
name: 'test',
desc: '',
x: 100,
y: 100,
opacity: 1,
rotation: 0,
flipx: true,
flipy: false,
blur: 0,
type: 'image',
_dirty: '1', // 用于视图更新
_lock: false, // 是否锁定
_hide: false, // 是否隐藏
width: 200,
height: 200,
border: {
stroke: '#f00', // 边框色
strokeWidth: 2,
visible: false,
},
shadow: {
x: 10,
y: 10,
blur: 10,
blendMode: 'normal',
spread: 0,
color: 'rgba(0,0,0,0.5)',
box: false,
visible: true,
},
naturalWidth: 200,
naturalHeight: 285,
url: 'https://cdn.h5ds.com/video/uploads/9715/20240207/679367666016878592.png', // 图片链接
cornerRadius: [200, 200, 200, 200], // 圆角
} as ImageLayer,
{
id: 't3',
name: 'txt',
desc: '',
x: 100,
y: 100,
opacity: 1,
rotation: 0,
blur: 0,
type: 'text',
text: '测试文字,无界云PS',
fill: null,
blendMode: 'normal',
fontFamilyURL: '',
textStyle: {
fontSize: 20,
fill: {
type: 'linear',
from: { x: 0, y: 0 },
to: { x: 1, y: 0 },
stops: [
{ offset: 0, color: '#FF4B4B' },
{ offset: 1, color: '#FEB027' },
],
},
},
_dirty: '1', // 用于视图更新
_lock: false, // 是否锁定
_hide: false, // 是否隐藏
border: {
stroke: '#f00', // 边框色
strokeWidth: 2,
visible: false,
},
shadow: {
x: 10,
y: 10,
blur: 10,
blendMode: 'normal',
spread: 0,
color: 'rgba(0,0,0,0.5)',
box: false,
visible: false,
},
} as TextLayer,
],
} as BasePage,
],
};
基于 Leaferjs 和 React 封装了一个渲染内核,渲染内核的本质为一个 React 组件,可以快速在项目中使用。
参数 | 说明 | 类型 | 默认值 | 必填 |
---|---|---|---|---|
data | 页面模块的 JSON 数据 | BasePage | - | ✔ |
target | 画布放入的 DOM 容器 | HTMLDivElement | - | ✔ |
env | 当前的渲染环境 | 'editor','preview' | 'editor' | ✔ |
resourceHost | 资源文件的 HOST 地址,资源存放路径 | string | '' | ✔ |
callback | 渲染后的回调函数 | Store => void | - | |
onControlSelect | 选中元素会触发 | (_event, ids: string[]) => void | - | |
onDragUp | 鼠标弹起来会触发 | () => void | - | |
onContextMenu | 鼠标右键会触发 | (_event, layers: BaseLayer[]) => void | - |
import { View } from './core';
const editor = {...};
return (
<View
resourceHost="https://cdn.h5ds.com"
callback={_store => {
editor.store = _store;
}}
env="editor"
data={pageData}
target={document.body}
onContextMenu={(e, layers) => {
// 显示鼠标右键菜单
editor.showContextMenu(e.origin, {
layers,
});
}}
onDragUp={() => {
// 更新设置区域
editor.updateOption();
}}
onControlSelect={(_e, ids) => {
// 选中元素
editor.setSelectedElementIds(ids);
}}
/>
);
在 callback 回调函数中可以获取到一个store
实例,该实例下面存放了很多可以操作画布的方法。API 文档如下:
.data
: BasePage
工程数据中的页面数据
.record
: RecordParams
操作记录类的实例,内置方法
.app
: ILeafer
Leaferjs 的实例
.editor
: IEditorBase
控制器相关业务的实例
.env
: ENV
内核当前的运行环境
.resourceHost
: string
资源文件的 host 地址
.helper
: Object
帮助工具
.utils
: Object
小工具方法的集合
.setURL(url: string)
: string
给 URL 自动添加 resourceHost 配置,比如当前的资源路径是 /assets/img.png
,资源实际地址在 cdn 中,这时候我们需要配置
resourceHost 为 cdn 的地址,然后在使用的时候可以调用这个方法得到一个完整的资源路径
const url = '/assets/img.png';
const newURL = store.setURL(url);
// newURL地址为:https://cdn.h5ds.com/assets/img.png;
.getLayerUIByIds(ids: string[])
: IUI[]
通过 ID 获取 Leaferjs 的 UI 对象数组
.getLayerByIds(ids: string[])
: BaseLayer[]
通过 ID 获取图层的 JSON 数据
.deleteLayers(ids: string[])
: void
通过图层 ID 删除图层数据
.groupData(ids: string[])
: GroupLayer
将多个图层合并成一个图层,并返回合成后的图层数据
.unGroupData(gid: string)
: string[]
传入一个组的 id,将该组打散并返回打散后的元素 id
.emitControl(ids: string[])
: void
触发指定元素的控制器选中状态,如果传入空数组就会取消所有的控制器
.autoViewSize()
: void
根据当前的窗口自动设置画布大小
.setViewSize(scale: number)
: void;
设置画布的缩放比例
.update()
: void
当数据发生变化后,可以调用此方法触发视图的更新
.triggerRotation(elementData: BaseLayer, rotation: number)
: void
手动设置元素的旋转,解决外部触发旋转不是围绕中心点旋转的问题,旋转元素会导致 x,y 同时发生变化
.capture(params?: IExportOptions)
: Promise(string);
导出图片
.destroy()
: void
销毁实例
数据和画布中的视图做了双向绑定,所以数据改变后只需要调用store.update()
就可以触发视图的更新。为什么是手动?有时候我们为
了节约渲染开销,需要做很多数据改动后才会去触发一次 update,所以这里做了类似 react 的 setState,需要我们去手动触发 update
函数来通知视图的更新。
// 修改坐标
layer.x = 100;
layer.y = 200;
// 通知视图更新
store.update();
yarn install
该过程耗时比较长,需要从远程去拉取所需依赖包,安装依赖包成功之后才可以进行接下来的操作。
yarn dev
项目使用 vite 进行打包和调试,如果需要对项目进行二次开发和修改,可以执行该命令进入开发模式,项目启动成功后浏览器输入 :https://127.0.0.1:3002 进行项目开发和调试。
开发前请注意是否需要修改 vite 的代理配置:
// video-editor/vite.config.ts
server: {
https: true,
host: '0.0.0.0',
port: 3002,
headers: { // 因为视频编辑器的业务需求,必须加上这两个头信息才可以让ffmpeg在浏览器正常运行
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'credentialless',
},
proxy: {
'/cgi-bin': { // 微信二维码地址代理
target: 'https://video.h5ds.com',
changeOrigin: true,
},
'/api': { // api地址代理
target: 'https://video.h5ds.com',
changeOrigin: true,
},
'/fonts': { // 字体包资源的代理地址
target: 'https://cdn.h5ds.com/assets',
changeOrigin: true,
},
'/video': { // 部分素材的代理地址
target: 'https://cdn.h5ds.com',
changeOrigin: true,
},
},
},
yarn build
打包成功后会生成 dist 目录,直接部署到服务器即可。具体的部署文档请参考部署说明文档。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。