@sarmal/core 0.7.1 → 0.9.0
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/LICENSE +9 -0
- package/README.md +89 -3
- package/dist/auto-init.cjs +130 -41
- package/dist/auto-init.cjs.map +1 -1
- package/dist/auto-init.js +130 -41
- package/dist/auto-init.js.map +1 -1
- package/dist/index.cjs +170 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -6
- package/dist/index.d.ts +18 -6
- package/dist/index.js +170 -102
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
package/dist/index.js
CHANGED
|
@@ -186,21 +186,90 @@ function createEngine(curveDef, trailLength = 120) {
|
|
|
186
186
|
// src/renderer.ts
|
|
187
187
|
var DEFAULT_MORPH_DURATION_MS = 300;
|
|
188
188
|
var DEFAULT_HEAD_RADIUS = 4;
|
|
189
|
-
var DEFAULT_GLOW_SIZE = 20;
|
|
190
189
|
var DEFAULT_SKELETON_COLOR = "#ffffff";
|
|
191
190
|
var DEFAULT_SKELETON_OPACITY = 0.15;
|
|
192
191
|
var FIT_PADDING = 0.1;
|
|
193
|
-
var TRAIL_BATCH_SIZE = 20;
|
|
194
192
|
var TRAIL_FADE_CURVE = 1.5;
|
|
195
193
|
var TRAIL_MAX_OPACITY = 0.88;
|
|
196
194
|
var TRAIL_MIN_WIDTH = 0.5;
|
|
197
195
|
var TRAIL_MAX_WIDTH = 2.5;
|
|
198
|
-
var
|
|
199
|
-
|
|
196
|
+
var GRADIENT = {
|
|
197
|
+
bard: ["#a855f7", "#3b82f6", "#14b8a6", "#ec4899"],
|
|
198
|
+
sunset: ["#f97316", "#dc2626", "#9333ea", "#f472b6"],
|
|
199
|
+
ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
|
|
200
|
+
ice: ["#1e3a8a", "#67e8f9"],
|
|
201
|
+
fire: ["#7f1d1d", "#fbbf24"],
|
|
202
|
+
forest: ["#14532d", "#86efac"]
|
|
203
|
+
};
|
|
204
|
+
var PRESETS = {
|
|
205
|
+
bard: GRADIENT.bard,
|
|
206
|
+
sunset: GRADIENT.sunset,
|
|
207
|
+
ocean: GRADIENT.ocean,
|
|
208
|
+
ice: GRADIENT.ice,
|
|
209
|
+
fire: GRADIENT.fire,
|
|
210
|
+
forest: GRADIENT.forest
|
|
211
|
+
};
|
|
212
|
+
function hexToRgb(hex) {
|
|
213
|
+
const n = parseInt(hex.slice(1), 16);
|
|
214
|
+
return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
|
|
215
|
+
}
|
|
216
|
+
var lerpRgb = (a, b, t) => ({
|
|
217
|
+
r: Math.round(a.r + (b.r - a.r) * t),
|
|
218
|
+
g: Math.round(a.g + (b.g - a.g) * t),
|
|
219
|
+
b: Math.round(a.b + (b.b - a.b) * t)
|
|
220
|
+
});
|
|
221
|
+
function getPaletteColor(palette, position, timeOffset = 0) {
|
|
222
|
+
if (palette.length === 0) return { r: 255, g: 255, b: 255 };
|
|
223
|
+
if (palette.length === 1) return hexToRgb(palette[0]);
|
|
224
|
+
const cyclePos = (position + timeOffset) % 1;
|
|
225
|
+
const scaled = cyclePos * palette.length;
|
|
226
|
+
const idx = Math.floor(scaled);
|
|
227
|
+
const t = scaled - idx;
|
|
228
|
+
const c1 = hexToRgb(palette[idx % palette.length]);
|
|
229
|
+
const c2 = hexToRgb(palette[(idx + 1) % palette.length]);
|
|
230
|
+
return lerpRgb(c1, c2, t);
|
|
231
|
+
}
|
|
232
|
+
function resolvePalette(palette, trailStyle) {
|
|
233
|
+
if (Array.isArray(palette)) return palette;
|
|
234
|
+
if (palette && palette in PRESETS) return PRESETS[palette];
|
|
235
|
+
return trailStyle === "gradient-animated" ? GRADIENT.bard : GRADIENT.ice;
|
|
236
|
+
}
|
|
200
237
|
function hexToRgbComponents(hex) {
|
|
201
238
|
const n = parseInt(hex.slice(1), 16);
|
|
202
239
|
return `${n >> 16},${n >> 8 & 255},${n & 255}`;
|
|
203
240
|
}
|
|
241
|
+
function computeTangent(trail, i) {
|
|
242
|
+
const count = trail.length;
|
|
243
|
+
if (count < 2) {
|
|
244
|
+
return { x: 1, y: 0 };
|
|
245
|
+
}
|
|
246
|
+
if (i === 0) {
|
|
247
|
+
const dx2 = trail[1].x - trail[0].x;
|
|
248
|
+
const dy2 = trail[1].y - trail[0].y;
|
|
249
|
+
const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
|
|
250
|
+
return { x: dx2 / len2, y: dy2 / len2 };
|
|
251
|
+
}
|
|
252
|
+
if (i === count - 1) {
|
|
253
|
+
const dx2 = trail[count - 1].x - trail[count - 2].x;
|
|
254
|
+
const dy2 = trail[count - 1].y - trail[count - 2].y;
|
|
255
|
+
const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
|
|
256
|
+
return { x: dx2 / len2, y: dy2 / len2 };
|
|
257
|
+
}
|
|
258
|
+
const dx = trail[i + 1].x - trail[i - 1].x;
|
|
259
|
+
const dy = trail[i + 1].y - trail[i - 1].y;
|
|
260
|
+
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
261
|
+
return { x: dx / len, y: dy / len };
|
|
262
|
+
}
|
|
263
|
+
function computeNormal(trail, i) {
|
|
264
|
+
const tangent = computeTangent(trail, i);
|
|
265
|
+
return { x: -tangent.y, y: tangent.x };
|
|
266
|
+
}
|
|
267
|
+
function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
|
|
268
|
+
target.style.width = `${logicalWidth}px`;
|
|
269
|
+
target.style.height = `${logicalHeight}px`;
|
|
270
|
+
target.width = logicalWidth * dpr;
|
|
271
|
+
target.height = logicalHeight * dpr;
|
|
272
|
+
}
|
|
204
273
|
function createRenderer(options) {
|
|
205
274
|
const canvas = options.canvas;
|
|
206
275
|
if (!canvas.getContext("2d")) {
|
|
@@ -212,11 +281,22 @@ function createRenderer(options) {
|
|
|
212
281
|
skeletonColor: options.skeletonColor ?? DEFAULT_SKELETON_COLOR,
|
|
213
282
|
trailColor: options.trailColor ?? "#ffffff",
|
|
214
283
|
headColor: options.headColor ?? "#ffffff",
|
|
215
|
-
headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS
|
|
216
|
-
glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE
|
|
284
|
+
headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS
|
|
217
285
|
};
|
|
286
|
+
const trailStyle = options.trailStyle ?? "default";
|
|
287
|
+
const palette = resolvePalette(options.palette, trailStyle);
|
|
218
288
|
const trailRgb = hexToRgbComponents(opts.trailColor);
|
|
219
|
-
const
|
|
289
|
+
const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
290
|
+
function setupCanvas() {
|
|
291
|
+
const rect = canvas.getBoundingClientRect();
|
|
292
|
+
const lw = rect.width || 200;
|
|
293
|
+
const lh = rect.height || 200;
|
|
294
|
+
applyDprSizing(canvas, lw, lh, dpr);
|
|
295
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
296
|
+
}
|
|
297
|
+
setupCanvas();
|
|
298
|
+
let logicalWidth = canvas.width / dpr;
|
|
299
|
+
let logicalHeight = canvas.height / dpr;
|
|
220
300
|
let skeleton = [];
|
|
221
301
|
let skeletonCanvas = null;
|
|
222
302
|
let trail = [];
|
|
@@ -230,6 +310,7 @@ function createRenderer(options) {
|
|
|
230
310
|
let morphResolve = null;
|
|
231
311
|
let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
|
|
232
312
|
let morphAlpha = 0;
|
|
313
|
+
let gradientAnimTime = 0;
|
|
233
314
|
function computeBoundaries(pts) {
|
|
234
315
|
if (pts.length === 0) return null;
|
|
235
316
|
const first = pts[0];
|
|
@@ -242,17 +323,15 @@ function createRenderer(options) {
|
|
|
242
323
|
}
|
|
243
324
|
const width = maxX - minX;
|
|
244
325
|
const height = maxY - minY;
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
const scaleX = canvasWidth / (width * (1 + FIT_PADDING * 2));
|
|
248
|
-
const scaleY = canvasHeight / (height * (1 + FIT_PADDING * 2));
|
|
326
|
+
const scaleX = logicalWidth / (width * (1 + FIT_PADDING * 2));
|
|
327
|
+
const scaleY = logicalHeight / (height * (1 + FIT_PADDING * 2));
|
|
249
328
|
const s = Math.min(scaleX, scaleY);
|
|
250
329
|
const boundsWidth = width * s;
|
|
251
330
|
const boundsHeight = height * s;
|
|
252
331
|
return {
|
|
253
332
|
scale: s,
|
|
254
|
-
offsetX: (
|
|
255
|
-
offsetY: (
|
|
333
|
+
offsetX: (logicalWidth - boundsWidth) / 2 - minX * s,
|
|
334
|
+
offsetY: (logicalHeight - boundsHeight) / 2 - minY * s
|
|
256
335
|
};
|
|
257
336
|
}
|
|
258
337
|
function calculateBoundaries() {
|
|
@@ -267,6 +346,7 @@ function createRenderer(options) {
|
|
|
267
346
|
if (skeleton.length < 2) return;
|
|
268
347
|
skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
|
|
269
348
|
const skeletonCtx = skeletonCanvas.getContext("2d");
|
|
349
|
+
skeletonCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
270
350
|
skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
|
|
271
351
|
skeletonCtx.lineWidth = 1.5;
|
|
272
352
|
skeletonCtx.beginPath();
|
|
@@ -312,32 +392,47 @@ function createRenderer(options) {
|
|
|
312
392
|
}
|
|
313
393
|
ctx.stroke();
|
|
314
394
|
} else if (skeletonCanvas) {
|
|
315
|
-
ctx.drawImage(skeletonCanvas, 0, 0);
|
|
395
|
+
ctx.drawImage(skeletonCanvas, 0, 0, logicalWidth, logicalHeight);
|
|
316
396
|
}
|
|
317
397
|
}
|
|
318
398
|
function drawTrail() {
|
|
319
399
|
if (trailCount < 2) {
|
|
320
400
|
return;
|
|
321
401
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const bEnd = Math.min(batchIndex + TRAIL_BATCH_SIZE, trailCount - 1);
|
|
326
|
-
const progress = (batchIndex + bEnd) / 2 / (trailCount - 1);
|
|
402
|
+
for (let i = 0; i < trailCount - 1; i++) {
|
|
403
|
+
const progress = i / (trailCount - 1);
|
|
404
|
+
const nextProgress = (i + 1) / (trailCount - 1);
|
|
327
405
|
const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
406
|
+
const width = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
|
|
407
|
+
const nextWidth = TRAIL_MIN_WIDTH + nextProgress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
|
|
408
|
+
const curr = trail[i];
|
|
409
|
+
const next = trail[i + 1];
|
|
410
|
+
const n0 = computeNormal(trail, i);
|
|
411
|
+
const n1 = computeNormal(trail, i + 1);
|
|
412
|
+
const halfW0 = width / 2;
|
|
413
|
+
const halfW1 = nextWidth / 2;
|
|
414
|
+
const l0x = curr.x * scale + offsetX + n0.x * halfW0;
|
|
415
|
+
const l0y = curr.y * scale + offsetY + n0.y * halfW0;
|
|
416
|
+
const r0x = curr.x * scale + offsetX - n0.x * halfW0;
|
|
417
|
+
const r0y = curr.y * scale + offsetY - n0.y * halfW0;
|
|
418
|
+
const l1x = next.x * scale + offsetX + n1.x * halfW1;
|
|
419
|
+
const l1y = next.y * scale + offsetY + n1.y * halfW1;
|
|
420
|
+
const r1x = next.x * scale + offsetX - n1.x * halfW1;
|
|
421
|
+
const r1y = next.y * scale + offsetY - n1.y * halfW1;
|
|
422
|
+
if (trailStyle === "default") {
|
|
423
|
+
ctx.fillStyle = `rgba(${trailRgb},${alpha})`;
|
|
424
|
+
} else {
|
|
425
|
+
const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
|
|
426
|
+
const color = getPaletteColor(palette, progress, timeOffset);
|
|
427
|
+
ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${alpha})`;
|
|
337
428
|
}
|
|
338
|
-
ctx.
|
|
339
|
-
ctx.
|
|
340
|
-
ctx.
|
|
429
|
+
ctx.beginPath();
|
|
430
|
+
ctx.moveTo(l0x, l0y);
|
|
431
|
+
ctx.lineTo(l1x, l1y);
|
|
432
|
+
ctx.lineTo(r1x, r1y);
|
|
433
|
+
ctx.lineTo(r0x, r0y);
|
|
434
|
+
ctx.closePath();
|
|
435
|
+
ctx.fill();
|
|
341
436
|
}
|
|
342
437
|
}
|
|
343
438
|
function drawHead() {
|
|
@@ -346,14 +441,6 @@ function createRenderer(options) {
|
|
|
346
441
|
}
|
|
347
442
|
const x = head.x * scale + offsetX;
|
|
348
443
|
const y = head.y * scale + offsetY;
|
|
349
|
-
const gradient = ctx.createRadialGradient(x, y, 0, x, y, opts.glowSize);
|
|
350
|
-
gradient.addColorStop(0, opts.headColor);
|
|
351
|
-
gradient.addColorStop(GLOW_INNER_EDGE, headRgbFalloff);
|
|
352
|
-
gradient.addColorStop(1, "transparent");
|
|
353
|
-
ctx.fillStyle = gradient;
|
|
354
|
-
ctx.beginPath();
|
|
355
|
-
ctx.arc(x, y, opts.glowSize, 0, Math.PI * 2);
|
|
356
|
-
ctx.fill();
|
|
357
444
|
ctx.fillStyle = opts.headColor;
|
|
358
445
|
ctx.beginPath();
|
|
359
446
|
ctx.arc(x, y, opts.headRadius, 0, Math.PI * 2);
|
|
@@ -363,6 +450,9 @@ function createRenderer(options) {
|
|
|
363
450
|
const now = performance.now();
|
|
364
451
|
const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
|
|
365
452
|
lastTime = now;
|
|
453
|
+
if (trailStyle === "gradient-animated") {
|
|
454
|
+
gradientAnimTime += deltaTime * 1e3;
|
|
455
|
+
}
|
|
366
456
|
if (engine.morphAlpha !== null) {
|
|
367
457
|
morphAlpha = Math.min(1, morphAlpha + deltaTime / (morphDurationMs / 1e3));
|
|
368
458
|
engine.setMorphAlpha(morphAlpha);
|
|
@@ -387,7 +477,7 @@ function createRenderer(options) {
|
|
|
387
477
|
trail = engine.tick(deltaTime);
|
|
388
478
|
trailCount = engine.trailCount;
|
|
389
479
|
head = trailCount > 0 ? trail[trailCount - 1] : null;
|
|
390
|
-
ctx.clearRect(0, 0,
|
|
480
|
+
ctx.clearRect(0, 0, logicalWidth, logicalHeight);
|
|
391
481
|
if (engine.isLiveSkeleton && engine.morphAlpha === null) {
|
|
392
482
|
skeleton = engine.getSarmalSkeleton();
|
|
393
483
|
calculateBoundaries();
|
|
@@ -453,16 +543,13 @@ function createRenderer(options) {
|
|
|
453
543
|
|
|
454
544
|
// src/renderer-svg.ts
|
|
455
545
|
var DEFAULT_MORPH_DURATION_MS2 = 300;
|
|
456
|
-
var
|
|
546
|
+
var MAX_TRAIL_SEGMENTS = 200;
|
|
457
547
|
var TRAIL_FADE_CURVE2 = 1.5;
|
|
458
548
|
var TRAIL_MAX_OPACITY2 = 0.88;
|
|
459
549
|
var TRAIL_MIN_WIDTH2 = 0.5;
|
|
460
550
|
var TRAIL_MAX_WIDTH2 = 2.5;
|
|
461
551
|
var DEFAULT_SKELETON_OPACITY2 = 0.15;
|
|
462
|
-
var DEFAULT_GLOW_INNER_STOP = 0.4;
|
|
463
|
-
var DEFAULT_GLOW_FALLOFF_OPACITY = 0.53;
|
|
464
552
|
var FIT_PADDING2 = 0.1;
|
|
465
|
-
var instanceCount = 0;
|
|
466
553
|
function el(tag) {
|
|
467
554
|
return document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
468
555
|
}
|
|
@@ -473,11 +560,8 @@ function createSVGRenderer(options) {
|
|
|
473
560
|
trailColor: options.trailColor ?? "#ffffff",
|
|
474
561
|
headColor: options.headColor ?? "#ffffff",
|
|
475
562
|
headRadius: options.headRadius ?? 4,
|
|
476
|
-
glowSize: options.glowSize ?? 20,
|
|
477
563
|
ariaLabel: options.ariaLabel ?? "Loading"
|
|
478
564
|
};
|
|
479
|
-
const uid = ++instanceCount;
|
|
480
|
-
const gradientId = `sarmal-glow-${uid}`;
|
|
481
565
|
const rect = container.getBoundingClientRect();
|
|
482
566
|
const width = rect.width || 200;
|
|
483
567
|
const height = rect.height || 200;
|
|
@@ -490,27 +574,6 @@ function createSVGRenderer(options) {
|
|
|
490
574
|
const titleEl = el("title");
|
|
491
575
|
titleEl.textContent = opts.ariaLabel;
|
|
492
576
|
svg.appendChild(titleEl);
|
|
493
|
-
const defs = el("defs");
|
|
494
|
-
const gradient = el("radialGradient");
|
|
495
|
-
gradient.id = gradientId;
|
|
496
|
-
gradient.setAttribute("cx", "50%");
|
|
497
|
-
gradient.setAttribute("cy", "50%");
|
|
498
|
-
gradient.setAttribute("r", "50%");
|
|
499
|
-
const stop0 = el("stop");
|
|
500
|
-
stop0.setAttribute("offset", "0%");
|
|
501
|
-
stop0.setAttribute("stop-color", opts.headColor);
|
|
502
|
-
stop0.setAttribute("stop-opacity", "1");
|
|
503
|
-
const stopMid = el("stop");
|
|
504
|
-
stopMid.setAttribute("offset", `${DEFAULT_GLOW_INNER_STOP * 100}%`);
|
|
505
|
-
stopMid.setAttribute("stop-color", opts.headColor);
|
|
506
|
-
stopMid.setAttribute("stop-opacity", String(DEFAULT_GLOW_FALLOFF_OPACITY));
|
|
507
|
-
const stop1 = el("stop");
|
|
508
|
-
stop1.setAttribute("offset", "100%");
|
|
509
|
-
stop1.setAttribute("stop-color", opts.headColor);
|
|
510
|
-
stop1.setAttribute("stop-opacity", "0");
|
|
511
|
-
gradient.append(stop0, stopMid, stop1);
|
|
512
|
-
defs.appendChild(gradient);
|
|
513
|
-
svg.appendChild(defs);
|
|
514
577
|
const skeletonPath = el("path");
|
|
515
578
|
skeletonPath.setAttribute("fill", "none");
|
|
516
579
|
skeletonPath.setAttribute("stroke", opts.skeletonColor);
|
|
@@ -532,19 +595,12 @@ function createSVGRenderer(options) {
|
|
|
532
595
|
let morphPathABuilt = "";
|
|
533
596
|
let morphPathBBuilt = "";
|
|
534
597
|
const trailPaths = [];
|
|
535
|
-
for (let i = 0; i <
|
|
598
|
+
for (let i = 0; i < MAX_TRAIL_SEGMENTS; i++) {
|
|
536
599
|
const path = el("path");
|
|
537
|
-
path.setAttribute("fill",
|
|
538
|
-
path.setAttribute("stroke", opts.trailColor);
|
|
539
|
-
path.setAttribute("stroke-linecap", "round");
|
|
540
|
-
path.setAttribute("stroke-linejoin", "round");
|
|
600
|
+
path.setAttribute("fill", opts.trailColor);
|
|
541
601
|
svg.appendChild(path);
|
|
542
602
|
trailPaths.push(path);
|
|
543
603
|
}
|
|
544
|
-
const glowCircle = el("circle");
|
|
545
|
-
glowCircle.setAttribute("fill", `url(#${gradientId})`);
|
|
546
|
-
glowCircle.setAttribute("r", String(opts.glowSize));
|
|
547
|
-
svg.appendChild(glowCircle);
|
|
548
604
|
const headCircle = el("circle");
|
|
549
605
|
headCircle.setAttribute("fill", opts.headColor);
|
|
550
606
|
headCircle.setAttribute("r", String(opts.headRadius));
|
|
@@ -582,19 +638,25 @@ function createSVGRenderer(options) {
|
|
|
582
638
|
offsetY = (height - h * scale) / 2 - minY * scale;
|
|
583
639
|
}
|
|
584
640
|
function px(p) {
|
|
585
|
-
return
|
|
641
|
+
return p.x * scale + offsetX;
|
|
586
642
|
}
|
|
587
643
|
function py(p) {
|
|
588
|
-
return
|
|
644
|
+
return p.y * scale + offsetY;
|
|
645
|
+
}
|
|
646
|
+
function pxStr(p) {
|
|
647
|
+
return px(p).toFixed(2);
|
|
648
|
+
}
|
|
649
|
+
function pyStr(p) {
|
|
650
|
+
return py(p).toFixed(2);
|
|
589
651
|
}
|
|
590
652
|
function updateSkeleton(skeleton2) {
|
|
591
653
|
if (skeleton2.length < 2) {
|
|
592
654
|
skeletonPath.setAttribute("d", "");
|
|
593
655
|
return;
|
|
594
656
|
}
|
|
595
|
-
let d = `M${
|
|
657
|
+
let d = `M${pxStr(skeleton2[0])} ${pyStr(skeleton2[0])}`;
|
|
596
658
|
for (let i = 1; i < skeleton2.length; i++) {
|
|
597
|
-
d += ` L${
|
|
659
|
+
d += ` L${pxStr(skeleton2[i])} ${pyStr(skeleton2[i])}`;
|
|
598
660
|
}
|
|
599
661
|
d += " Z";
|
|
600
662
|
skeletonPath.setAttribute("d", d);
|
|
@@ -611,24 +673,32 @@ function createSVGRenderer(options) {
|
|
|
611
673
|
}
|
|
612
674
|
return;
|
|
613
675
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const
|
|
617
|
-
const end = Math.min(start + batchSize, trailCount - 1);
|
|
618
|
-
if (start >= trailCount - 1) {
|
|
619
|
-
trailPaths[b].setAttribute("d", "");
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
622
|
-
const progress = (start + end) / 2 / (trailCount - 1);
|
|
676
|
+
for (let i = 0; i < trailCount - 1; i++) {
|
|
677
|
+
const progress = i / (trailCount - 1);
|
|
678
|
+
const nextProgress = (i + 1) / (trailCount - 1);
|
|
623
679
|
const opacity = Math.pow(progress, TRAIL_FADE_CURVE2) * TRAIL_MAX_OPACITY2;
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
680
|
+
const width2 = TRAIL_MIN_WIDTH2 + progress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
|
|
681
|
+
const nextWidth = TRAIL_MIN_WIDTH2 + nextProgress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
|
|
682
|
+
const curr = trail[i];
|
|
683
|
+
const next = trail[i + 1];
|
|
684
|
+
const n0 = computeNormal(trail, i);
|
|
685
|
+
const n1 = computeNormal(trail, i + 1);
|
|
686
|
+
const halfW0 = width2 / 2;
|
|
687
|
+
const halfW1 = nextWidth / 2;
|
|
688
|
+
const l0x = px(curr) + n0.x * halfW0;
|
|
689
|
+
const l0y = py(curr) + n0.y * halfW0;
|
|
690
|
+
const r0x = px(curr) - n0.x * halfW0;
|
|
691
|
+
const r0y = py(curr) - n0.y * halfW0;
|
|
692
|
+
const l1x = px(next) + n1.x * halfW1;
|
|
693
|
+
const l1y = py(next) + n1.y * halfW1;
|
|
694
|
+
const r1x = px(next) - n1.x * halfW1;
|
|
695
|
+
const r1y = py(next) - n1.y * halfW1;
|
|
696
|
+
const d = `M${l0x.toFixed(2)} ${l0y.toFixed(2)} L${l1x.toFixed(2)} ${l1y.toFixed(2)} L${r1x.toFixed(2)} ${r1y.toFixed(2)} L${r0x.toFixed(2)} ${r0y.toFixed(2)} Z`;
|
|
697
|
+
trailPaths[i].setAttribute("d", d);
|
|
698
|
+
trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
|
|
699
|
+
}
|
|
700
|
+
for (let i = trailCount - 1; i < trailPaths.length; i++) {
|
|
701
|
+
trailPaths[i].setAttribute("d", "");
|
|
632
702
|
}
|
|
633
703
|
}
|
|
634
704
|
function updateHead(trail, trailCount) {
|
|
@@ -638,10 +708,8 @@ function createSVGRenderer(options) {
|
|
|
638
708
|
const head = trail[trailCount - 1];
|
|
639
709
|
const x = px(head);
|
|
640
710
|
const y = py(head);
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
headCircle.setAttribute("cx", x);
|
|
644
|
-
headCircle.setAttribute("cy", y);
|
|
711
|
+
headCircle.setAttribute("cx", String(x));
|
|
712
|
+
headCircle.setAttribute("cy", String(y));
|
|
645
713
|
}
|
|
646
714
|
let animationId = null;
|
|
647
715
|
let lastTime = 0;
|