@jiujue/react-canvas-fiber 2.1.1 → 2.1.2

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
@@ -69,6 +69,10 @@ Common props:
69
69
 
70
70
  - `style?: YogaStyle`: layout box; see Layout section below
71
71
  - `background?: string`: background fill color
72
+ - `backgroundImage?: string`: image URL
73
+ - `backgroundSize?: string`: `cover` | `contain` | `auto` | `100px 50px` | `50% 50%`
74
+ - `backgroundPosition?: string`: `center` | `top left` | `10px 20px` | `50% 50%`
75
+ - `backgroundRepeat?: string`: `repeat` | `no-repeat` | `repeat-x` | `repeat-y`
72
76
  - `border?: string`: CSS-like border. Supported forms:
73
77
  - `"<number>px solid <color>"` (e.g. `1px solid rgba(255,255,255,0.2)`)
74
78
  - `"<number> <color>"` (e.g. `2 #fff`)
package/README.zh.md CHANGED
@@ -69,6 +69,10 @@ export function Example() {
69
69
 
70
70
  - `style?: YogaStyle`:布局样式,见下方「布局(Yoga 子集)」
71
71
  - `background?: string`:背景填充色
72
+ - `backgroundImage?: string`:背景图片 URL
73
+ - `backgroundSize?: string`:`cover` | `contain` | `auto` | `100px 50px` | `50% 50%`
74
+ - `backgroundPosition?: string`:`center` | `top left` | `10px 20px` | `50% 50%`
75
+ - `backgroundRepeat?: string`:`repeat` | `no-repeat` | `repeat-x` | `repeat-y`
72
76
  - `border?: string`:类似 CSS 的 border,支持:
73
77
  - `"<number>px solid <color>"`(例如 `1px solid rgba(255,255,255,0.2)`)
74
78
  - `"<number> <color>"`(例如 `2 #fff`)
package/dist/index.cjs CHANGED
@@ -48,6 +48,8 @@ function createNode(type, props) {
48
48
  };
49
49
  if (type === "Image") {
50
50
  node.imageInstance = null;
51
+ } else if (type === "View") {
52
+ node.backgroundImageInstance = null;
51
53
  }
52
54
  return node;
53
55
  }
@@ -131,6 +133,105 @@ function drawBorder(ctx, x, y, w, h, radius, border) {
131
133
  ctx.stroke();
132
134
  ctx.restore();
133
135
  }
136
+ function resolveBgRepeat(repeat) {
137
+ if (!repeat) return "repeat";
138
+ if (repeat === "no-repeat" || repeat === "repeat-x" || repeat === "repeat-y" || repeat === "repeat")
139
+ return repeat;
140
+ return "repeat";
141
+ }
142
+ function parseBgSize(size, w, h, imgW, imgH) {
143
+ if (!size || size === "auto") return { width: imgW, height: imgH };
144
+ if (size === "cover") {
145
+ const scale = Math.max(w / imgW, h / imgH);
146
+ return { width: imgW * scale, height: imgH * scale };
147
+ }
148
+ if (size === "contain") {
149
+ const scale = Math.min(w / imgW, h / imgH);
150
+ return { width: imgW * scale, height: imgH * scale };
151
+ }
152
+ const parts = size.trim().split(/\s+/);
153
+ let rw = imgW;
154
+ let rh = imgH;
155
+ if (parts[0]) {
156
+ if (parts[0].endsWith("%")) {
157
+ rw = w * parseFloat(parts[0]) / 100;
158
+ } else if (parts[0] !== "auto") {
159
+ rw = parseFloat(parts[0]);
160
+ }
161
+ }
162
+ if (parts[1]) {
163
+ if (parts[1].endsWith("%")) {
164
+ rh = h * parseFloat(parts[1]) / 100;
165
+ } else if (parts[1] !== "auto") {
166
+ rh = parseFloat(parts[1]);
167
+ }
168
+ } else {
169
+ if (parts[0] !== "auto") {
170
+ rh = imgH * (rw / imgW);
171
+ }
172
+ }
173
+ return { width: rw, height: rh };
174
+ }
175
+ function parseBgPos(pos, w, h, targetW, targetH) {
176
+ const parts = (pos || "").trim().split(/\s+/);
177
+ if (parts.length === 0 || parts.length === 1 && !parts[0]) {
178
+ return { x: 0, y: 0 };
179
+ }
180
+ const xStr = parts[0];
181
+ let yStr = parts[1];
182
+ if (parts.length === 1) {
183
+ yStr = "center";
184
+ }
185
+ function resolve(val, containerDim, imgDim) {
186
+ if (val === "left" || val === "top") return 0;
187
+ if (val === "right" || val === "bottom") return containerDim - imgDim;
188
+ if (val === "center") return (containerDim - imgDim) / 2;
189
+ if (val.endsWith("%")) {
190
+ return (containerDim - imgDim) * parseFloat(val) / 100;
191
+ }
192
+ if (val.endsWith("px")) {
193
+ return parseFloat(val);
194
+ }
195
+ return parseFloat(val) || 0;
196
+ }
197
+ return {
198
+ x: resolve(xStr, w, targetW),
199
+ y: resolve(yStr, h, targetH)
200
+ };
201
+ }
202
+ function drawBackgroundImage(ctx, node, x, y, w, h, radius) {
203
+ const img = node.backgroundImageInstance;
204
+ if (!img || !img.complete || img.naturalWidth === 0 || img.naturalHeight === 0) return;
205
+ const props = node.props;
206
+ const bgSize = props.backgroundSize;
207
+ const bgPos = props.backgroundPosition;
208
+ const bgRepeat = resolveBgRepeat(props.backgroundRepeat);
209
+ const imgW = img.naturalWidth;
210
+ const imgH = img.naturalHeight;
211
+ const { width: targetW, height: targetH } = parseBgSize(bgSize, w, h, imgW, imgH);
212
+ const { x: posX, y: posY } = parseBgPos(bgPos, w, h, targetW, targetH);
213
+ ctx.save();
214
+ drawRoundedRect(ctx, x, y, w, h, radius);
215
+ ctx.clip();
216
+ if (bgRepeat === "no-repeat") {
217
+ ctx.drawImage(img, x + posX, y + posY, targetW, targetH);
218
+ } else {
219
+ const pattern = ctx.createPattern(img, bgRepeat);
220
+ if (pattern) {
221
+ const matrix = new DOMMatrix();
222
+ const scaleX = targetW / imgW;
223
+ const scaleY = targetH / imgH;
224
+ matrix.translateSelf(x + posX, y + posY);
225
+ matrix.scaleSelf(scaleX, scaleY);
226
+ pattern.setTransform(matrix);
227
+ ctx.fillStyle = pattern;
228
+ ctx.beginPath();
229
+ ctx.rect(x, y, w, h);
230
+ ctx.fill();
231
+ }
232
+ }
233
+ ctx.restore();
234
+ }
134
235
  function drawNode(state, node, offsetX, offsetY) {
135
236
  const { ctx } = state;
136
237
  const x = offsetX + node.layout.x;
@@ -165,6 +266,10 @@ function drawNode(state, node, offsetX, offsetY) {
165
266
  ctx.fill();
166
267
  ctx.restore();
167
268
  }
269
+ const viewNode = node;
270
+ if (viewNode.backgroundImageInstance) {
271
+ drawBackgroundImage(ctx, viewNode, x, y, w, h, viewRadius);
272
+ }
168
273
  if (scrollX || scrollY) {
169
274
  viewIsScroll = true;
170
275
  ctx.save();
@@ -645,6 +750,18 @@ var hostConfig = {
645
750
  if (!img.complete) {
646
751
  img.onload = () => rootContainer.invalidate();
647
752
  }
753
+ } else if (type === "View" && props.backgroundImage) {
754
+ const viewNode = node;
755
+ const img = new Image();
756
+ img.crossOrigin = "anonymous";
757
+ img.src = props.backgroundImage;
758
+ if (img.dataset) {
759
+ img.dataset.src = props.backgroundImage;
760
+ }
761
+ viewNode.backgroundImageInstance = img;
762
+ if (!img.complete) {
763
+ img.onload = () => rootContainer.invalidate();
764
+ }
648
765
  }
649
766
  return node;
650
767
  },
@@ -729,6 +846,38 @@ var hostConfig = {
729
846
  }
730
847
  }
731
848
  }
849
+ } else if (instance.type === "View") {
850
+ const viewNode = instance;
851
+ const newBg = instance.props.backgroundImage;
852
+ const currentBg = viewNode.backgroundImageInstance?.dataset?.src;
853
+ if (newBg !== currentBg) {
854
+ if (!newBg) {
855
+ viewNode.backgroundImageInstance = null;
856
+ } else {
857
+ const img = new Image();
858
+ img.crossOrigin = "anonymous";
859
+ img.src = newBg;
860
+ if (img.dataset) {
861
+ img.dataset.src = newBg;
862
+ }
863
+ viewNode.backgroundImageInstance = img;
864
+ const invalidate = () => {
865
+ let p = viewNode;
866
+ while (p) {
867
+ if (p.type === "Root") {
868
+ p.container?.invalidate();
869
+ return;
870
+ }
871
+ p = p.parent;
872
+ }
873
+ };
874
+ if (!img.complete) {
875
+ img.onload = invalidate;
876
+ } else {
877
+ invalidate();
878
+ }
879
+ }
880
+ }
732
881
  }
733
882
  },
734
883
  commitTextUpdate() {