leafer-ui 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,23 @@
1
- English | [简体中文](./README-CN.md)
1
+ [English](./README-EN.md) | 简体中文
2
2
 
3
3
  <br/>
4
4
 
5
- # LeaferJS: An Easy-to-Use Canvas Engine
5
+ # LeaferJS:好用的 Canvas 引擎
6
6
 
7
- Effortlessly build graphic interaction and editing — an infinite canvas engine for the AI era
7
+ 轻松实现图形交互与编辑,AI 时代的无限画布引擎
8
8
 
9
- Official Website: [leaferjs.com](https://www.leaferjs.com)
9
+ 官网: [leaferjs.com](https://www.leaferjs.com)
10
10
 
11
- **👉 A Canvas engine that can handle 1 million graphics in the browser**
12
- **👉 The first Canvas core capable of building a “Figma-level editor”**
11
+ **👉 在浏览器里 “跑得动 100 万个图形” Canvas 引擎**
12
+ **👉 可以做 “Figma 级编辑器” Canvas 内核**
13
13
 
14
14
  <p align="center">
15
- <a href="https://www.youtube.com/watch?v=bJ6fHMQdATs" target="_blank">
16
- <img src="https://www.leaferjs.com/image/video/leaferjs-en.jpg" />
15
+ <a href="https://www.bilibili.com/video/BV1E56vBwEiB" target="_blank">
16
+ <img src="https://www.leaferjs.com/image/video/leaferjs.jpg?d=1126" />
17
17
  </a>
18
18
  </p>
19
19
  <p align="center">
20
- <b>Extreme Performance · Ultra-Low Memory · DOM-like API · Graphic Editing · Cross-Platform · Zero Dependencies · Lightweight (70KB min+gzip)</b>
20
+ <b>极致性能 · 极低内存 · DOM API · 图形编辑 · 跨平台 · 零依赖 · 轻量 (70KB min+gzip)</b>
21
21
  </p>
22
22
 
23
23
  <div align="center">
@@ -29,74 +29,73 @@ Official Website: [leaferjs.com](https://www.leaferjs.com)
29
29
 
30
30
  </div>
31
31
 
32
- ## 🧐 Why Choose LeaferJS?
32
+ ## 🧐 为什么选择LeaferJS
33
33
 
34
- In Web graphics development, developers often face a trade-off between **performance and usability**.
34
+ Web 图形开发中,开发者常面临“性能与易用性”的选型困境。
35
35
 
36
- **LeaferJS aims to eliminate this compromise.** Rebuilt from the ground up, it not only pushes the limits of Web rendering and interaction performance, but also pursues ultimate simplicity in developer experience. It is a standardized engine designed for productivity tools handling **large-scale, high-density, and massive-layer graphics**.
36
+ **LeaferJS 致力于终结这种权衡。** 它从底层重构,不仅在性能上挑战 Web 渲染和交互的物理极限,更在开发体验上追求极致的简单。它是为了解决“大规模、高密度、海量图层”的生产力工具而诞生的标准化引擎。
37
37
 
38
- ## 🏗️ Why an Infinite Canvas Engine for the AI Era?
38
+ ## 🏗️ 为什么是 AI 时代的无限画布引擎?
39
39
 
40
- With the explosion of AI-generated content, the challenge for graphics engines has shifted from **“how to render”** to **“how to organize and refine”**:
40
+ AI 生成内容爆发的今天,图形引擎的挑战已从 **“如何画出来”** 转变为 **“如何编排与精修”**:
41
41
 
42
- - **⚡ Extreme Capacity:** Handles millions of interactive layers smoothly, perfectly accommodating massive AI-generated fragments.
43
- - **🤖 Semantic Editing:** A structured scene tree allows AI to manipulate graphics like the DOM, enabling true AI collaborative workflows.
44
- - **🛠️ Native Editor:** Built-in Editor plugin enables one-click access to industrial-grade editing features such as rotation, scaling, and multi-selection for AI-generated content.
42
+ - **⚡ 极致承载力:** 突破极限,百万交互图层依然保持丝滑响应,完美承载 AI 生成的海量碎片。
43
+ - **🤖 语义化编辑:** 结构化场景树让 AI 能像操作 DOM 一样操控图形,构建真正的 AI 协同工作流。
44
+ - **🛠️ 原生编辑器:** 内置 Editor 插件,让AI 生成的内容一键获得精准旋转、缩放、多选等工业级编辑能力。
45
45
 
46
- [Leafer AI Knowledge Base](https://github.com/leaferjs/ai-docs) | [MCP & Skills](https://context7.com/leaferjs/ai-docs?tab=skills) | [Ask AI](https://context7.com/leaferjs/ai-docs?tab=chat)
46
+ [Leafer AI 知识库](https://github.com/leaferjs/ai-docs) | [MCP & Skills](https://context7.com/leaferjs/ai-docs?tab=skills) | [Ask AI](https://context7.com/leaferjs/ai-docs?tab=chat)
47
47
 
48
- ## 🎨 Use Cases
48
+ ## 🎨 应用场景
49
49
 
50
- With its extreme performance and standardized capabilities, LeaferJS is an ideal foundation for:
50
+ LeaferJS 的极致性能与标准化能力,使其成为以下领域的理想底座:
51
51
 
52
- - 🤖 **AI Applications:** Infinite AI canvas, AI design tools, generative UI interactions.
53
- - 🛠️ **Productivity Tools:** Graphic editors, online design platforms (Figma/Canva-like), whiteboards, low-code engines.
54
- - 📊 **Industrial Visualization:** Large-scale node systems, flowcharts, asset monitoring, massive topology diagrams.
55
- - 🎬 **Digital Content Generation:** Batch image/poster generation, short video frame rendering (Node.js), interactive H5.
56
- - 🎮 **Interactive Applications:** Lightweight games, brand interactive apps, high-frequency data dashboards.
52
+ - 🤖 **AI 场景:** AI 无限画布、AI 设计工具、生成式 UI 交互。
53
+ - 🛠️ **生产力工具:** 图形编辑器、在线设计平台(Figma/Canva 类)、电子白板、低代码引擎。
54
+ - 📊 **工业可视化:** 万级节点电力组态、流程图、资产监控、大规模拓扑图。
55
+ - 🎬 **数字内容生成:** 批量生成图片/海报、短视频帧渲染(Node.js 端)、互动 H5
56
+ - 🎮 **互动应用:** 轻量小游戏、品牌互动应用、高频交互数据大屏。
57
57
 
58
- ## 🔥 Performance
58
+ ## 🔥 性能表现
59
59
 
60
- LeaferJS pushes the ceiling of Web graphics processing by approximately **10x**.
60
+ LeaferJS Web 图形处理的“天花板”向上推举了约 10 倍。
61
61
 
62
- | Test (1M Interactive Rectangles) | Traditional Canvas Libraries | **LeaferJS** | Improvement |
63
- | :------------------------------- | :--------------------------- | :----------- | :---------------- |
64
- | **Initial Render Time** | ~915 seconds | **1.28s** | **~8x faster 🎉** |
65
- | **Memory Usage** | ~2-4GB (may crash browser) | **320MB** | **~8x lower** |
66
- | **Drag FPS (single element)** | 04 FPS | **60 FPS** | **~15x faster** |
62
+ | 测试项 (100万个可交互矩形) | 传统 Canvas | **LeaferJS** | 提升 |
63
+ | :------------------------- | :---------------------- | :----------- | :--------------- |
64
+ | **首屏创建速度** | ~9-15 | **1.28 秒** | **约快 8 🎉** |
65
+ | **内存占用** | ~2-4GB (浏览器可能崩溃) | **320 MB** | **约节省 8 倍** |
66
+ | **单元素拖拽帧率** | 0-4 FPS | **60 FPS** | **约流畅 15 倍** |
67
67
 
68
- Test environment: 2K laptop screen / Chrome V143.0. Results are for reference only; actual performance depends on hardware.
68
+ 测试环境: 2K屏笔记本 / Chrome V143.0,数据仅供参考,实际表现取决于硬件性能。
69
69
 
70
- [Performance Details](https://www.leaferjs.com/#performance) | [Benchmark](https://benchmark.leaferjs.com/leafer/)
70
+ [性能对比详情](https://www.leaferjs.com/#performance) | [性能基准测试](https://benchmark.leaferjs.com/leafer/)
71
71
 
72
- ## ⚡️ Core Capabilities
72
+ ## ⚡️ 核心能力
73
73
 
74
- - **🎨 Powerful Graphics System:** Complete scene tree supporting vector graphics, SVG paths, and pixel operations.
75
- - **🧠 Ultra-Fast Interaction:** Native support for drag, zoom, multi-touch, and millisecond-level hit testing.
76
- - **🛠️ Built-in Editor Support:** Integrated **Editor plugin** enabling **scale, rotate, move, multi-select** with one click.
77
- - **🧩 Modern Layout Engine:** Rare native **Flexbox layout** support in a Canvas engine — as natural as writing HTML.
78
- - **🎬 State-Driven Animation:** Built-in high-performance transitions and path animations for smooth dynamic interactions.
79
- - **🌍 Cross-Platform:** One codebase runs seamlessly on Web, Node.js, WeChat Mini Programs, and mobile H5.
74
+ - **🎨 强大的图形系统:** 完整的场景树结构,支持矢量图形、SVG 路径及像素操作。
75
+ - **🧠 极致的交互响应:** 原生支持拖拽、缩放、多点触控,毫秒级的命中检测(Hit Testing)。
76
+ - **🛠️ 原生编辑器支持:** 内置 **Editor 插件**,一键开启图形的**缩放、旋转、移动、多选**等功能。
77
+ - **🧩 现代布局引擎:** 业内罕见地在 Canvas 引擎中原生支持 **Flex 布局**,像写 HTML 一样自然。
78
+ - **🎬 状态驱动动画:** 内置高性能过渡效果与路径动画,让动态交互更简单。
79
+ - **🌍 全平台适配:** 一套代码完美运行于 WebNode.js、微信小程序及移动端 H5 环境。
80
80
 
81
- [Full Feature List](https://www.leaferjs.com/#different) | [Live Examples](https://www.leaferjs.com/examples/)
81
+ [功能列表详情](https://www.leaferjs.com/#different) | [在线体验](https://www.leaferjs.com/examples/)
82
82
 
83
- ## 🛠️ Quick Start
83
+ ## 🛠️ 快速上手
84
84
 
85
85
  ```sh
86
86
  npm install leafer-ui
87
87
 
88
- # When using plugins, it is recommended to install core packages together
89
- # to avoid version mismatch issues
88
+ # 需使用插件时,推荐同时安装跨平台核心包,防止出现更新版本不同步问题
90
89
  npm install leafer-ui @leafer-ui/core @leafer-ui/draw
91
90
  ```
92
91
 
93
92
  ```ts
94
93
  import { Leafer, Rect } from 'leafer-ui'
95
94
 
96
- // Create an interactive app that adapts to the window
95
+ // 创建一个自适应窗口的交互应用
97
96
  const leafer = new Leafer({ view: window })
98
97
 
99
- // Create a draggable rectangle
98
+ // 创建一个可以被拖拽的矩形
100
99
  const rect = new Rect({
101
100
  x: 100,
102
101
  y: 100,
@@ -109,64 +108,64 @@ const rect = new Rect({
109
108
  leafer.add(rect)
110
109
  ```
111
110
 
112
- [Run Online Example](https://www.leaferjs.com/examples/#official%2Fstart%2Fcreate.ts) | [Editor Example](https://www.leaferjs.com/examples/#official%2Fplugin%2Feditor%2Fframe%2Ftransparent.ts)
111
+ [在线运行示例](https://www.leaferjs.com/examples/#official%2Fstart%2Fcreate.ts) | [图形编辑示例](https://www.leaferjs.com/examples/#official%2Fplugin%2Feditor%2Fframe%2Ftransparent.ts)
113
112
 
114
- ## 💼 Commercial Support & Sustainability
113
+ ## 💼 商业支持与可持续性
115
114
 
116
- There is a significant gap between “an engine” and “a mature product.” To ensure long-term maintenance of the open-source engine and help enterprises overcome complex development challenges, we’ve built a transparent and sustainable ecosystem:
115
+ 从“一个引擎”到“一个成熟产品”之间存在巨大的研发鸿沟。为了支持开源引擎的长久维护,并协助企业跨越复杂场景的开发瓶颈,我们构建了透明且健康的生态:
117
116
 
118
- - **LeaferJS Forever Open Source (MIT):** Core features and foundational plugins remain open and free, ensuring developer freedom and control.
119
- - **[PxGrow](https://www.pxgrow.com/) Commercial Plugins (Optional):** Focused on solving **complex industrial scenarios**, including advanced editor suites, extreme performance optimizations, and complex graphic algorithms — saving months or even years of development time.
120
- - **Sustainable Growth:** All commercial revenue is reinvested into the open-source engine to ensure LeaferJS stays at the forefront of Web graphics technology.
117
+ - **LeaferJS 永久开源 (MIT):** 核心功能和基础插件始终保持开源与自由,保障每一位开发者的选型安全与技术掌控权。
118
+ - **[PxGrow](https://www.pxgrow.com/) 商业插件 (可选):** 专注于解决**工业级应用**中的复杂业务难题。它封装了高级编辑器套件、极端场景性能优化及复杂图形算法,旨在帮企业节省数月甚至数年的业务功能研发周期。
119
+ - **良性循环与长期主义:** 所有的商业收入将用于反哺开源引擎的持续迭代,确保 LeaferJS 始终处于 Web 图形技术的领先水平。
121
120
 
122
- ### LeaferJS Repository Overview
121
+ ### LeaferJS 仓库组成一览表
123
122
 
124
- | Repository | Description | Link |
125
- | :------------ | :------------------------------- | :---------------------------------------------- |
126
- | **LeaferJS** | Main integration repo (runnable) | [GitHub](https://github.com/leaferjs/LeaferJS) |
127
- | **leafer** | Core engine | [GitHub](https://github.com/leaferjs/leafer) |
128
- | **leafer-ui** | UI layer | [GitHub](https://github.com/leaferjs/leafer-ui) |
129
- | **leafer-in** | Official plugins | [GitHub](https://github.com/leaferjs/leafer-in) |
130
- | **leafer-x** | Community plugins showcase | [GitHub](https://github.com/leaferjs/leafer-x) |
131
- | **test** | Automated testing | [GitHub](https://github.com/leaferjs/test) |
132
- | **code** | Example code | [GitHub](https://github.com/leaferjs/code) |
133
- | **docs** | Documentation | [GitHub](https://github.com/leaferjs/docs) |
123
+ | 仓库名称 | 功能描述 | 开源地址 |
124
+ | :------------ | :--------------------------- | :---------------------------------------------- |
125
+ | **LeaferJS** | 主集成仓库,支持直接运行代码 | [GitHub](https://github.com/leaferjs/LeaferJS) |
126
+ | **leafer** | 引擎核心仓库 | [GitHub](https://github.com/leaferjs/leafer) |
127
+ | **leafer-ui** | UI 表现层仓库 | [GitHub](https://github.com/leaferjs/leafer-ui) |
128
+ | **leafer-in** | 官方插件仓库 | [GitHub](https://github.com/leaferjs/leafer-in) |
129
+ | **leafer-x** | 社区插件提交/展示仓库 | [GitHub](https://github.com/leaferjs/leafer-x) |
130
+ | **test** | 自动化测试仓库 | [GitHub](https://github.com/leaferjs/test) |
131
+ | **code** | 示例代码仓库 | [GitHub](https://github.com/leaferjs/code) |
132
+ | **docs** | 在线文档仓库 | [GitHub](https://github.com/leaferjs/docs) |
134
133
 
135
- ## 🌟 Contribute: Five Years of Craftsmanship, Built from Passion
134
+ ## 🌟 参与贡献:五年沉淀,始于初心
136
135
 
137
- LeaferJS is an original open-source engine refined over five years. Our mission is to standardize the **graphics system foundation**, so developers can focus on creativity rather than low-level implementation.
136
+ LeaferJS 是一个持续打磨了五年的原创开源引擎。我们致力于把“图形系统底座”这件事标准化,让开发者专注产品创意,而非底层实现。
138
137
 
139
- **If you value originality and extreme performance, please give us a Star!**
138
+ **如果你支持原创、追求极致性能,请为我们点亮一颗 Star!**
140
139
 
141
140
  <div style="display:flex; gap: 12px">
142
141
  <a target="_blank" href="https://github.com/leaferjs/leafer-ui" aria-label="github" rel="noopener">
143
- <img width="120" title="github" src="https://www.leaferjs.com/svg/github-stars.svg?d=20260416" />
142
+ <img width="120" title="github" src="https://www.leaferjs.com/svg/github-stars.svg?d=20260511" />
144
143
  </a>
145
144
  </div>
146
145
 
147
- - 🌟 **Star the repo:** Your support means everything.
148
- - 🐞 **Report issues:** Every issue helps us improve.
149
- - 🤝 **Join the community:** Explore the limits of Web graphics together.
146
+ - 🌟 **Star 仓库:** 你的认可对我们至关重要。
147
+ - 🐞 **提交反馈:** 每一个 Issue 都是我们进步的机会。
148
+ - 🤝 **加入社区:** 与社区开发者共同探索 Web 图形技术的极限。
150
149
 
151
- ## Contribution Guide
150
+ ## 贡献指南
152
151
 
153
- When you use LeaferJS, you become part of this vibrant community — stepping into a growing “tech castle.” Only through collective participation can it truly flourish.
152
+ 当你使用 LeaferJS 时, 你就已成为了 这个充满活力的大家庭 的一员,踏入这座建设中的“技术城堡”。只有通过每位成员的热情参与与贡献,这座城堡才能逐步走向完善。
154
153
 
155
- [Code of Conduct](./contributor/CODE_OF_CONDUCT.md)
154
+ [社区行为准则](./contributor/CODE_OF_CONDUCT.md)
156
155
 
157
- [Commit Convention](./contributor/COMMIT_CONVENTION.md)
156
+ [代码提交规范](./contributor/COMMIT_CONVENTION.md)
158
157
 
159
- [How to Ask Questions the Smart Way](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md#%E6%8F%90%E9%97%AE%E7%9A%84%E6%99%BA%E6%85%A7)
158
+ [提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md#%E6%8F%90%E9%97%AE%E7%9A%84%E6%99%BA%E6%85%A7)
160
159
 
161
- ## Acknowledgements
160
+ ## 致谢贡献者
162
161
 
163
- [All code contributors](https://github.com/leaferjs/leafer-ui/graphs/contributors)
162
+ [每一位贡献代码的社区成员](https://github.com/leaferjs/leafer-ui/graphs/contributors)
164
163
 
165
- [All community contributors](https://www.leaferjs.com/ui/contribute/)
164
+ [每一位参与生态的社区成员](https://www.leaferjs.com/ui/contribute/)
166
165
 
167
- ## Sponsors
166
+ ## 赞助商
168
167
 
169
- <p><h3 align="center">Gold Sponsors</h3></p>
168
+ <p><h3 align="center">金牌赞助商</h3></p>
170
169
  <p style="display: flex;flex-wrap: wrap;justify-content: center;gap: 15px;">
171
170
  <a target="_blank" href="https://easysearch.cn">
172
171
  <img width="180" title="Easysearch - 企业级的分布式搜索型数据库" src="https://www.leaferjs.com/image/sponsor/gold/easysearch.png" loading="lazy" />
@@ -209,9 +208,15 @@ When you use LeaferJS, you become part of this vibrant community — stepping in
209
208
  </a>
210
209
  </p>
211
210
 
212
- <p><h3 align="center">Silver Sponsors</h3></p>
211
+ <p><h3 align="center">银牌赞助</h3></p>
213
212
  <p style="display: flex;flex-wrap: wrap;justify-content: center;gap: 5px;">
214
- <a target="_blank" href="">
213
+ <a target="_blank" href="">
214
+ <img width="40" title="怜生" src="https://www.pxgrow.com/image/user/default/3.jpg" loading="lazy" />
215
+ </a>
216
+ <a target="_blank" href="https://pro.kuaitu.cc/">
217
+ <img width="40" title="快图设计" src="https://www.pxgrow.com/image/sponsor/user/72.jpg" loading="lazy" />
218
+ </a>
219
+ <a target="_blank" href="">
215
220
  <img width="40" title="black" src="https://api.pxgrow.com/uploads/avatar/249/AzA/4v/u.jpg" loading="lazy" />
216
221
  </a>
217
222
  <a target="_blank" href="">
@@ -259,9 +264,6 @@ When you use LeaferJS, you become part of this vibrant community — stepping in
259
264
  <a target="_blank" href="">
260
265
  <img width="40" title="😊" src="https://www.pxgrow.com/image/sponsor/user/79.jpg" loading="lazy" />
261
266
  </a>
262
- <a target="_blank" href="https://pro.kuaitu.cc/">
263
- <img width="40" title="快图设计" src="https://www.pxgrow.com/image/sponsor/user/72.jpg" loading="lazy" />
264
- </a>
265
267
  <a target="_blank" href="">
266
268
  <img width="40" title="ycteng" src="https://www.pxgrow.com/image/sponsor/user/purple.png" loading="lazy" />
267
269
  </a>
@@ -384,12 +386,12 @@ When you use LeaferJS, you become part of this vibrant community — stepping in
384
386
  </a>
385
387
  </p>
386
388
 
387
- <p><h3 align="center">Bronze Sponsors</h3></p>
389
+ <p><h3 align="center">铜牌赞助</h3></p>
388
390
  <p style="display: flex;flex-wrap: wrap;justify-content: center;gap: 15px;">
389
391
  用户zw8T394C &nbsp;&nbsp;用户zw8T398C &nbsp;&nbsp;龙眼吃多了上火 &nbsp;&nbsp;用户zw8T392T &nbsp;&nbsp;用户z89CSw69 &nbsp;&nbsp;用户CzP9SCAz &nbsp;&nbsp;Kim &nbsp;&nbsp;用户249AzA8v &nbsp;&nbsp;l0f5c7bf &nbsp;&nbsp;夏先生 &nbsp;&nbsp;yinuo &nbsp;&nbsp;用户3wTwAz78 &nbsp;&nbsp;用户zw8T39zT &nbsp;&nbsp;用户249AzA82 &nbsp;&nbsp;用户CzP9SCvz &nbsp;&nbsp;用户39A334xT &nbsp;&nbsp;用户3wTwAz3x &nbsp;&nbsp;用户zw8T39zC &nbsp;&nbsp;稀饭、微凉 &nbsp;&nbsp;用户CzP9SCT4 &nbsp;&nbsp;便宜VPS服务器 &nbsp;&nbsp;菲鸽 &nbsp;&nbsp;szhua &nbsp;&nbsp;Cheng &nbsp;&nbsp;Suezp &nbsp;&nbsp;beyond &nbsp;&nbsp;用户3PvP2S63 &nbsp;&nbsp;o &nbsp;&nbsp;Arvin &nbsp;&nbsp;finallycc &nbsp;&nbsp;用户39A3346C &nbsp;&nbsp;大雷 &nbsp;&nbsp;用户CzP9SC4z &nbsp;&nbsp;随风 &nbsp;&nbsp;用户z89CSwT9 &nbsp;&nbsp;用户3wTwAzCx &nbsp;&nbsp;UPMuling &nbsp;&nbsp;军杨 &nbsp;&nbsp;桔子雨工作室 &nbsp;&nbsp;用户zw8T37xC &nbsp;&nbsp;前端炒饭仔 &nbsp;&nbsp;用户z89CSw46 &nbsp;&nbsp;崮生 &nbsp;&nbsp;互动矩阵 &nbsp;&nbsp;ZhanYoHo &nbsp;&nbsp;何佳Q &nbsp;&nbsp;coderhyh &nbsp;&nbsp;早上好啊 &nbsp;&nbsp;快图设计 &nbsp;&nbsp;do &nbsp;&nbsp;毛哥哥 &nbsp;&nbsp;迅排设计 &nbsp;&nbsp;用���z89CSw86 &nbsp;&nbsp;糖果 &nbsp;&nbsp;南城以北 &nbsp;&nbsp;黑色摩天仑 &nbsp;&nbsp;Charm &nbsp;&nbsp;Lauginwing &nbsp;&nbsp;在路上 &nbsp;&nbsp;Jerry &nbsp;&nbsp;张余🌈 &nbsp;&nbsp;李狗嗨。💢 &nbsp;&nbsp;用户zw8T376T &nbsp;&nbsp;用户249AzA2v &nbsp;&nbsp;ʚ LMT ɞ &nbsp;&nbsp;格子 &nbsp;&nbsp;等等 &nbsp;&nbsp;goosen &nbsp;&nbsp;F4nniu &nbsp;&nbsp;梁福斌 &nbsp;&nbsp;江万江 &nbsp;&nbsp;杨超 &nbsp;&nbsp;ToB Dev &nbsp;&nbsp;前端之虎陈随易 &nbsp;&nbsp;A☀️云☀️A &nbsp;&nbsp;zhk &nbsp;&nbsp;爱发电用户_c9c82 &nbsp;&nbsp;轻简历 &nbsp;&nbsp;爱发电用户_0fac0 &nbsp;&nbsp;wangyesheji.cn &nbsp;&nbsp;风间 &nbsp;&nbsp;爱发电用户_Tqsm &nbsp;&nbsp;爱发电用户_6KpE &nbsp;&nbsp;星小志 &nbsp;&nbsp;zwm &nbsp;&nbsp;爱发电用户_3725c &nbsp;&nbsp;Noth1ng &nbsp;&nbsp;纳西妲の√ &nbsp;&nbsp;爱发电用户_Ahb9 &nbsp;&nbsp;爱发电用户_7617d &nbsp;&nbsp;冷漠 &nbsp;&nbsp;爱发电用户_9RXB &nbsp;&nbsp;今日值得读 &nbsp;&nbsp;爱发电用户_49sT &nbsp;&nbsp;爱发电用户_NFCS &nbsp;&nbsp;爱发电用户_43ad8 &nbsp;&nbsp;爱发电用户_30455 &nbsp;&nbsp;砖吐筷筷 &nbsp;&nbsp;xiaozhang &nbsp;&nbsp;爱发电用户_b47b3 &nbsp;&nbsp;longbow1998 &nbsp;&nbsp;爱发电用户_5d755 &nbsp;&nbsp;爱发电用户_b76b8 &nbsp;&nbsp;爱发电用户_e70c2 &nbsp;&nbsp;xiaou@截图工具 &nbsp;&nbsp;ousiri &nbsp;&nbsp;爱发电用户_039dc &nbsp;&nbsp;花祁 &nbsp;&nbsp;爱发电用户_99f39 &nbsp;&nbsp;坤坤 &nbsp;&nbsp;爱发电用户_X6hp &nbsp;&nbsp;ycteng &nbsp;&nbsp;曹吉美爸爸 &nbsp;&nbsp;啸沧海 &nbsp;&nbsp;Ronny &nbsp;&nbsp;爱发电用户_UXEV &nbsp;&nbsp;Biu &nbsp;&nbsp;王志强 &nbsp;&nbsp;SaltedFish &nbsp;&nbsp;爱发电用户_76f9d &nbsp;&nbsp;PD.新城คิดถึง &nbsp;&nbsp;糖颂缘冥倾 &nbsp;&nbsp;ALBERT. &nbsp;&nbsp;爱发电用户_Pbm7 &nbsp;&nbsp;Leafer &nbsp;&nbsp;赞助我们 &nbsp;&nbsp;</p>
390
392
 
391
393
  ## License
392
394
 
393
- MIT License — free to use, including for commercial applications.
395
+ MIT 开源许可协议,可以免费使用,且能用于商业场景。
394
396
 
395
397
  Copyright © 2023-present Chao (Leafer) Wan
package/dist/web.cjs CHANGED
@@ -1726,8 +1726,8 @@ function compute(attrName, ui) {
1726
1726
  }
1727
1727
  }
1728
1728
  }
1729
- data["_" + attrName] = leafPaints.length ? leafPaints : undefined;
1730
1729
  if (leafPaints.length) {
1730
+ data["_" + attrName] = leafPaints;
1731
1731
  if (leafPaints.every(item => item.isTransparent)) {
1732
1732
  if (leafPaints.some(item => item.image)) isAlphaPixel = true;
1733
1733
  isTransparent = true;
@@ -1742,6 +1742,7 @@ function compute(attrName, ui) {
1742
1742
  }
1743
1743
  } else {
1744
1744
  data.__removePaint(attrName, false);
1745
+ data["_" + attrName] = "";
1745
1746
  }
1746
1747
  }
1747
1748
 
@@ -1891,11 +1892,12 @@ function checkSizeAndCreateData(ui, attrName, paint, image, leafPaint, boxBounds
1891
1892
  needUpdate = false;
1892
1893
  }
1893
1894
  }
1895
+ if (paint.mode === "brush") draw.PaintImage.brush(leafPaint);
1894
1896
  if (!leafPaint.data) {
1895
1897
  draw.PaintImage.createData(leafPaint, image, paint, boxBounds);
1896
- const {transform: transform} = leafPaint.data, {opacity: opacity, blendMode: blendMode} = paint;
1897
- const clip = transform && !transform.onlyScale || data.path || data.cornerRadius;
1898
- if (clip || opacity && opacity < 1 || blendMode) leafPaint.complex = clip ? 2 : true;
1898
+ const {transform: transform} = leafPaint.data, {opacity: opacity} = paint;
1899
+ const clip = (transform && !transform.onlyScale || data.path || data.cornerRadius) && !leafPaint.brush;
1900
+ if (clip || opacity && opacity < 1 || paint.blendMode) leafPaint.complex = clip ? 2 : true;
1899
1901
  }
1900
1902
  if (paint.filter) draw.PaintImage.applyFilter(leafPaint, image, paint.filter, ui);
1901
1903
  return needUpdate;
@@ -1985,6 +1987,7 @@ function getPatternData(paint, box, image) {
1985
1987
  break;
1986
1988
 
1987
1989
  case "repeat":
1990
+ case "brush":
1988
1991
  if (!sameBox || scaleX || rotation || skew) draw.PaintImage.repeatMode(data, box, width, height, tempImage.x, tempImage.y, scaleX, scaleY, rotation, skew, align, paint.freeTransform);
1989
1992
  if (!repeat) data.repeat = "repeat";
1990
1993
  const count = core.isObject(repeat);
@@ -2125,7 +2128,7 @@ function createPattern(paint, ui, canvas, renderOptions) {
2125
2128
  let {scaleX: scaleX, scaleY: scaleY} = draw.PaintImage.getImageRenderScaleData(paint, ui, canvas, renderOptions), id = paint.film ? paint.nowIndex : scaleX + "-" + scaleY;
2126
2129
  if (paint.patternId !== id && !ui.destroyed) {
2127
2130
  if (!(core.Platform.image.isLarge(paint.image, scaleX, scaleY) && !paint.data.repeat)) {
2128
- const {image: image, data: data} = paint, {opacity: opacity} = paint.originPaint, {transform: transform, gap: gap} = data, fixScale = draw.PaintImage.getPatternFixScale(paint, scaleX, scaleY);
2131
+ const {image: image, brush: brush, data: data} = paint, {opacity: opacity} = paint.originPaint, {transform: transform, gap: gap} = data, fixScale = draw.PaintImage.getPatternFixScale(paint, scaleX, scaleY);
2129
2132
  let imageMatrix, xGap, yGap, {width: width, height: height} = image;
2130
2133
  if (fixScale) scaleX *= fixScale, scaleY *= fixScale;
2131
2134
  width *= scaleX;
@@ -2133,6 +2136,10 @@ function createPattern(paint, ui, canvas, renderOptions) {
2133
2136
  if (gap) {
2134
2137
  xGap = gap.x * scaleX / abs$1(data.scaleX || 1);
2135
2138
  yGap = gap.y * scaleY / abs$1(data.scaleY || 1);
2139
+ if (brush) {
2140
+ const brushScale = draw.PaintImage.getBrushScale(paint, ui);
2141
+ xGap /= brushScale, yGap /= brushScale;
2142
+ }
2136
2143
  }
2137
2144
  if (transform || scaleX !== 1 || scaleY !== 1) {
2138
2145
  scaleX *= getFloorScale(width + (xGap || 0));
@@ -2142,7 +2149,7 @@ function createPattern(paint, ui, canvas, renderOptions) {
2142
2149
  scale(imageMatrix, 1 / scaleX, 1 / scaleY);
2143
2150
  }
2144
2151
  const imageCanvas = image.getCanvas(width, height, opacity, undefined, xGap, yGap, ui.leafer && ui.leafer.config.smooth, data.interlace);
2145
- const pattern = image.getPattern(imageCanvas, data.repeat || (core.Platform.origin.noRepeat || "no-repeat"), imageMatrix, paint);
2152
+ const pattern = brush ? imageCanvas : image.getPattern(imageCanvas, data.repeat || (core.Platform.origin.noRepeat || "no-repeat"), imageMatrix, paint);
2146
2153
  paint.style = pattern;
2147
2154
  paint.patternId = id;
2148
2155
  }
@@ -2163,9 +2170,9 @@ function getPatternFixScale(paint, imageScaleX, imageScaleY) {
2163
2170
 
2164
2171
  function checkImage(paint, drawImage, ui, canvas, renderOptions) {
2165
2172
  const {scaleX: scaleX, scaleY: scaleY} = draw.PaintImage.getImageRenderScaleData(paint, ui, canvas, renderOptions), id = paint.film ? paint.nowIndex : scaleX + "-" + scaleY;
2166
- const {image: image, data: data, originPaint: originPaint} = paint, {exporting: exporting, snapshot: snapshot} = renderOptions;
2173
+ const {image: image, brush: brush, data: data, originPaint: originPaint} = paint, {exporting: exporting, snapshot: snapshot} = renderOptions;
2167
2174
  if (!data || paint.patternId === id && !exporting || snapshot) {
2168
- return false;
2175
+ if (!(brush && paint.style)) return false;
2169
2176
  } else {
2170
2177
  if (drawImage) {
2171
2178
  if (data.repeat) {
@@ -2179,18 +2186,18 @@ function checkImage(paint, drawImage, ui, canvas, renderOptions) {
2179
2186
  canvas.fillStyle = paint.style || "#000";
2180
2187
  canvas.fill();
2181
2188
  }
2182
- draw.PaintImage.drawImage(paint, scaleX, scaleY, ui, canvas, renderOptions);
2183
- return true;
2184
2189
  } else {
2185
2190
  if (!paint.style || originPaint.sync || exporting) draw.PaintImage.createPattern(paint, ui, canvas, renderOptions); else draw.PaintImage.createPatternTask(paint, ui, canvas, renderOptions);
2186
- return false;
2191
+ if (!(brush && paint.style)) return false;
2187
2192
  }
2188
2193
  }
2194
+ draw.PaintImage.drawImage(paint, scaleX, scaleY, ui, canvas, renderOptions);
2195
+ return true;
2189
2196
  }
2190
2197
 
2191
2198
  function drawImage(paint, imageScaleX, imageScaleY, ui, canvas, _renderOptions) {
2192
- const {data: data, image: image, complex: complex} = paint;
2193
- let {width: width, height: height} = image;
2199
+ const {data: data, image: image, brush: brush, complex: complex} = paint;
2200
+ let {width: width, height: height} = image, view = brush || image;
2194
2201
  if (complex) {
2195
2202
  const {blendMode: blendMode, opacity: opacity} = paint.originPaint, {transform: transform} = data;
2196
2203
  canvas.save();
@@ -2198,16 +2205,17 @@ function drawImage(paint, imageScaleX, imageScaleY, ui, canvas, _renderOptions)
2198
2205
  blendMode && (canvas.blendMode = blendMode);
2199
2206
  opacity && (canvas.opacity *= opacity);
2200
2207
  transform && canvas.transform(transform);
2201
- image.render(canvas, 0, 0, width, height, ui, paint, imageScaleX, imageScaleY);
2208
+ view.render(canvas, 0, 0, width, height, ui, paint, imageScaleX, imageScaleY);
2202
2209
  canvas.restore();
2203
2210
  } else {
2204
2211
  if (data.scaleX) width *= data.scaleX, height *= data.scaleY;
2205
- image.render(canvas, 0, 0, width, height, ui, paint, imageScaleX, imageScaleY);
2212
+ view.render(canvas, 0, 0, width, height, ui, paint, imageScaleX, imageScaleY);
2206
2213
  }
2207
2214
  }
2208
2215
 
2209
2216
  function getImageRenderScaleData(paint, ui, canvas, _renderOptions) {
2210
2217
  const scaleData = ui.getRenderScaleData(true, paint.originPaint.scaleFixed), {data: data} = paint;
2218
+ if (paint.brush) draw.PaintImage.addBrushScale(scaleData, paint, ui);
2211
2219
  if (canvas) {
2212
2220
  const {pixelRatio: pixelRatio} = canvas;
2213
2221
  scaleData.scaleX *= pixelRatio;
@@ -2224,6 +2232,7 @@ function recycleImage(attrName, data) {
2224
2232
  const paints = data["_" + attrName];
2225
2233
  if (core.isArray(paints)) {
2226
2234
  let paint, image, recycleMap, input, url;
2235
+ const ui = data.__leaf;
2227
2236
  for (let i = 0, len = paints.length; i < len; i++) {
2228
2237
  paint = paints[i];
2229
2238
  image = paint.image;
@@ -2232,7 +2241,8 @@ function recycleImage(attrName, data) {
2232
2241
  if (!recycleMap) recycleMap = {};
2233
2242
  recycleMap[url] = true;
2234
2243
  core.ImageManager.recyclePaint(paint);
2235
- if (data.__willDestroy && image.parent) draw.PaintImage.recycleFilter(image, data.__leaf);
2244
+ if (paint.brush) draw.PaintImage.recycleBrush(paint, ui);
2245
+ if (data.__willDestroy && image.parent) draw.PaintImage.recycleFilter(image, ui);
2236
2246
  if (image.loading) {
2237
2247
  if (!input) {
2238
2248
  input = data.__input && data.__input[attrName] || [];
package/dist/web.esm.js CHANGED
@@ -1730,8 +1730,8 @@ function compute(attrName, ui) {
1730
1730
  }
1731
1731
  }
1732
1732
  }
1733
- data["_" + attrName] = leafPaints.length ? leafPaints : undefined;
1734
1733
  if (leafPaints.length) {
1734
+ data["_" + attrName] = leafPaints;
1735
1735
  if (leafPaints.every(item => item.isTransparent)) {
1736
1736
  if (leafPaints.some(item => item.image)) isAlphaPixel = true;
1737
1737
  isTransparent = true;
@@ -1746,6 +1746,7 @@ function compute(attrName, ui) {
1746
1746
  }
1747
1747
  } else {
1748
1748
  data.__removePaint(attrName, false);
1749
+ data["_" + attrName] = "";
1749
1750
  }
1750
1751
  }
1751
1752
 
@@ -1895,11 +1896,12 @@ function checkSizeAndCreateData(ui, attrName, paint, image, leafPaint, boxBounds
1895
1896
  needUpdate = false;
1896
1897
  }
1897
1898
  }
1899
+ if (paint.mode === "brush") PaintImage.brush(leafPaint);
1898
1900
  if (!leafPaint.data) {
1899
1901
  PaintImage.createData(leafPaint, image, paint, boxBounds);
1900
- const {transform: transform} = leafPaint.data, {opacity: opacity, blendMode: blendMode} = paint;
1901
- const clip = transform && !transform.onlyScale || data.path || data.cornerRadius;
1902
- if (clip || opacity && opacity < 1 || blendMode) leafPaint.complex = clip ? 2 : true;
1902
+ const {transform: transform} = leafPaint.data, {opacity: opacity} = paint;
1903
+ const clip = (transform && !transform.onlyScale || data.path || data.cornerRadius) && !leafPaint.brush;
1904
+ if (clip || opacity && opacity < 1 || paint.blendMode) leafPaint.complex = clip ? 2 : true;
1903
1905
  }
1904
1906
  if (paint.filter) PaintImage.applyFilter(leafPaint, image, paint.filter, ui);
1905
1907
  return needUpdate;
@@ -1989,6 +1991,7 @@ function getPatternData(paint, box, image) {
1989
1991
  break;
1990
1992
 
1991
1993
  case "repeat":
1994
+ case "brush":
1992
1995
  if (!sameBox || scaleX || rotation || skew) PaintImage.repeatMode(data, box, width, height, tempImage.x, tempImage.y, scaleX, scaleY, rotation, skew, align, paint.freeTransform);
1993
1996
  if (!repeat) data.repeat = "repeat";
1994
1997
  const count = isObject(repeat);
@@ -2129,7 +2132,7 @@ function createPattern(paint, ui, canvas, renderOptions) {
2129
2132
  let {scaleX: scaleX, scaleY: scaleY} = PaintImage.getImageRenderScaleData(paint, ui, canvas, renderOptions), id = paint.film ? paint.nowIndex : scaleX + "-" + scaleY;
2130
2133
  if (paint.patternId !== id && !ui.destroyed) {
2131
2134
  if (!(Platform.image.isLarge(paint.image, scaleX, scaleY) && !paint.data.repeat)) {
2132
- const {image: image, data: data} = paint, {opacity: opacity} = paint.originPaint, {transform: transform, gap: gap} = data, fixScale = PaintImage.getPatternFixScale(paint, scaleX, scaleY);
2135
+ const {image: image, brush: brush, data: data} = paint, {opacity: opacity} = paint.originPaint, {transform: transform, gap: gap} = data, fixScale = PaintImage.getPatternFixScale(paint, scaleX, scaleY);
2133
2136
  let imageMatrix, xGap, yGap, {width: width, height: height} = image;
2134
2137
  if (fixScale) scaleX *= fixScale, scaleY *= fixScale;
2135
2138
  width *= scaleX;
@@ -2137,6 +2140,10 @@ function createPattern(paint, ui, canvas, renderOptions) {
2137
2140
  if (gap) {
2138
2141
  xGap = gap.x * scaleX / abs$1(data.scaleX || 1);
2139
2142
  yGap = gap.y * scaleY / abs$1(data.scaleY || 1);
2143
+ if (brush) {
2144
+ const brushScale = PaintImage.getBrushScale(paint, ui);
2145
+ xGap /= brushScale, yGap /= brushScale;
2146
+ }
2140
2147
  }
2141
2148
  if (transform || scaleX !== 1 || scaleY !== 1) {
2142
2149
  scaleX *= getFloorScale(width + (xGap || 0));
@@ -2146,7 +2153,7 @@ function createPattern(paint, ui, canvas, renderOptions) {
2146
2153
  scale(imageMatrix, 1 / scaleX, 1 / scaleY);
2147
2154
  }
2148
2155
  const imageCanvas = image.getCanvas(width, height, opacity, undefined, xGap, yGap, ui.leafer && ui.leafer.config.smooth, data.interlace);
2149
- const pattern = image.getPattern(imageCanvas, data.repeat || (Platform.origin.noRepeat || "no-repeat"), imageMatrix, paint);
2156
+ const pattern = brush ? imageCanvas : image.getPattern(imageCanvas, data.repeat || (Platform.origin.noRepeat || "no-repeat"), imageMatrix, paint);
2150
2157
  paint.style = pattern;
2151
2158
  paint.patternId = id;
2152
2159
  }
@@ -2167,9 +2174,9 @@ function getPatternFixScale(paint, imageScaleX, imageScaleY) {
2167
2174
 
2168
2175
  function checkImage(paint, drawImage, ui, canvas, renderOptions) {
2169
2176
  const {scaleX: scaleX, scaleY: scaleY} = PaintImage.getImageRenderScaleData(paint, ui, canvas, renderOptions), id = paint.film ? paint.nowIndex : scaleX + "-" + scaleY;
2170
- const {image: image, data: data, originPaint: originPaint} = paint, {exporting: exporting, snapshot: snapshot} = renderOptions;
2177
+ const {image: image, brush: brush, data: data, originPaint: originPaint} = paint, {exporting: exporting, snapshot: snapshot} = renderOptions;
2171
2178
  if (!data || paint.patternId === id && !exporting || snapshot) {
2172
- return false;
2179
+ if (!(brush && paint.style)) return false;
2173
2180
  } else {
2174
2181
  if (drawImage) {
2175
2182
  if (data.repeat) {
@@ -2183,18 +2190,18 @@ function checkImage(paint, drawImage, ui, canvas, renderOptions) {
2183
2190
  canvas.fillStyle = paint.style || "#000";
2184
2191
  canvas.fill();
2185
2192
  }
2186
- PaintImage.drawImage(paint, scaleX, scaleY, ui, canvas, renderOptions);
2187
- return true;
2188
2193
  } else {
2189
2194
  if (!paint.style || originPaint.sync || exporting) PaintImage.createPattern(paint, ui, canvas, renderOptions); else PaintImage.createPatternTask(paint, ui, canvas, renderOptions);
2190
- return false;
2195
+ if (!(brush && paint.style)) return false;
2191
2196
  }
2192
2197
  }
2198
+ PaintImage.drawImage(paint, scaleX, scaleY, ui, canvas, renderOptions);
2199
+ return true;
2193
2200
  }
2194
2201
 
2195
2202
  function drawImage(paint, imageScaleX, imageScaleY, ui, canvas, _renderOptions) {
2196
- const {data: data, image: image, complex: complex} = paint;
2197
- let {width: width, height: height} = image;
2203
+ const {data: data, image: image, brush: brush, complex: complex} = paint;
2204
+ let {width: width, height: height} = image, view = brush || image;
2198
2205
  if (complex) {
2199
2206
  const {blendMode: blendMode, opacity: opacity} = paint.originPaint, {transform: transform} = data;
2200
2207
  canvas.save();
@@ -2202,16 +2209,17 @@ function drawImage(paint, imageScaleX, imageScaleY, ui, canvas, _renderOptions)
2202
2209
  blendMode && (canvas.blendMode = blendMode);
2203
2210
  opacity && (canvas.opacity *= opacity);
2204
2211
  transform && canvas.transform(transform);
2205
- image.render(canvas, 0, 0, width, height, ui, paint, imageScaleX, imageScaleY);
2212
+ view.render(canvas, 0, 0, width, height, ui, paint, imageScaleX, imageScaleY);
2206
2213
  canvas.restore();
2207
2214
  } else {
2208
2215
  if (data.scaleX) width *= data.scaleX, height *= data.scaleY;
2209
- image.render(canvas, 0, 0, width, height, ui, paint, imageScaleX, imageScaleY);
2216
+ view.render(canvas, 0, 0, width, height, ui, paint, imageScaleX, imageScaleY);
2210
2217
  }
2211
2218
  }
2212
2219
 
2213
2220
  function getImageRenderScaleData(paint, ui, canvas, _renderOptions) {
2214
2221
  const scaleData = ui.getRenderScaleData(true, paint.originPaint.scaleFixed), {data: data} = paint;
2222
+ if (paint.brush) PaintImage.addBrushScale(scaleData, paint, ui);
2215
2223
  if (canvas) {
2216
2224
  const {pixelRatio: pixelRatio} = canvas;
2217
2225
  scaleData.scaleX *= pixelRatio;
@@ -2228,6 +2236,7 @@ function recycleImage(attrName, data) {
2228
2236
  const paints = data["_" + attrName];
2229
2237
  if (isArray(paints)) {
2230
2238
  let paint, image, recycleMap, input, url;
2239
+ const ui = data.__leaf;
2231
2240
  for (let i = 0, len = paints.length; i < len; i++) {
2232
2241
  paint = paints[i];
2233
2242
  image = paint.image;
@@ -2236,7 +2245,8 @@ function recycleImage(attrName, data) {
2236
2245
  if (!recycleMap) recycleMap = {};
2237
2246
  recycleMap[url] = true;
2238
2247
  ImageManager.recyclePaint(paint);
2239
- if (data.__willDestroy && image.parent) PaintImage.recycleFilter(image, data.__leaf);
2248
+ if (paint.brush) PaintImage.recycleBrush(paint, ui);
2249
+ if (data.__willDestroy && image.parent) PaintImage.recycleFilter(image, ui);
2240
2250
  if (image.loading) {
2241
2251
  if (!input) {
2242
2252
  input = data.__input && data.__input[attrName] || [];