starflock 0.2.3 → 0.2.4
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/index.d.ts +3 -1
- package/package.json +1 -1
- package/src/Node.js +1 -0
- package/src/World.js +44 -29
- package/src/adapters/react.js +2 -2
- package/src/forces/attract.js +2 -2
package/index.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export declare class Node {
|
|
|
24
24
|
angle?: number
|
|
25
25
|
angularVelocity?: number
|
|
26
26
|
shape?: string | ShapeFn
|
|
27
|
+
/** @internal assigned by World for spatial index lookups */
|
|
28
|
+
_index: number
|
|
27
29
|
constructor(opts: NodeOptions)
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -174,4 +176,4 @@ export interface AttractOptions {
|
|
|
174
176
|
export declare function attract(opts?: AttractOptions): Force
|
|
175
177
|
|
|
176
178
|
// React adapter
|
|
177
|
-
export declare function
|
|
179
|
+
export declare function useStarflock(options?: Omit<WorldOptions, 'canvas'>): import('react').RefObject<HTMLCanvasElement>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starflock",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Lightweight, zero-dependency canvas particle library. Composable forces, configurable everything.",
|
|
5
5
|
"keywords": ["canvas", "particles", "animation", "constellation", "webgl", "background", "force", "interactive"],
|
|
6
6
|
"author": "Corvin Burmeister",
|
package/src/Node.js
CHANGED
package/src/World.js
CHANGED
|
@@ -21,6 +21,7 @@ function hexToRgb(hex) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function lerpColor(colors, t) {
|
|
24
|
+
if (colors.length === 0) return '#ffffff'
|
|
24
25
|
if (colors.length === 1) return colors[0]
|
|
25
26
|
const scaled = Math.max(0, Math.min(1, t)) * (colors.length - 1)
|
|
26
27
|
const i = Math.min(Math.floor(scaled), colors.length - 2)
|
|
@@ -110,6 +111,7 @@ export class World {
|
|
|
110
111
|
this.ctx = canvas.getContext('2d')
|
|
111
112
|
this.forces = forces
|
|
112
113
|
this.options = { ...DEFAULTS, ...options }
|
|
114
|
+
this._edgeColorsExplicit = !!options.edgeColors
|
|
113
115
|
if (!this.options.edgeColors) {
|
|
114
116
|
this.options.edgeColors = this.options.colors
|
|
115
117
|
}
|
|
@@ -118,6 +120,7 @@ export class World {
|
|
|
118
120
|
this.raf = null
|
|
119
121
|
this.mouse = null
|
|
120
122
|
this.scrollY = 0
|
|
123
|
+
this._started = false
|
|
121
124
|
this._hoveredNode = null
|
|
122
125
|
|
|
123
126
|
this._onMouseMove = this._onMouseMove.bind(this)
|
|
@@ -221,8 +224,10 @@ export class World {
|
|
|
221
224
|
my = e.clientY + window.scrollY
|
|
222
225
|
} else {
|
|
223
226
|
const rect = this.canvas.getBoundingClientRect()
|
|
224
|
-
|
|
225
|
-
|
|
227
|
+
const scaleX = this.canvas.width / rect.width
|
|
228
|
+
const scaleY = this.canvas.height / rect.height
|
|
229
|
+
mx = (e.clientX - rect.left) * scaleX
|
|
230
|
+
my = (e.clientY - rect.top) * scaleY
|
|
226
231
|
}
|
|
227
232
|
this.mouse = { x: mx, y: my }
|
|
228
233
|
|
|
@@ -266,8 +271,10 @@ export class World {
|
|
|
266
271
|
my = e.clientY + window.scrollY
|
|
267
272
|
} else {
|
|
268
273
|
const rect = this.canvas.getBoundingClientRect()
|
|
269
|
-
|
|
270
|
-
|
|
274
|
+
const scaleX = this.canvas.width / rect.width
|
|
275
|
+
const scaleY = this.canvas.height / rect.height
|
|
276
|
+
mx = (e.clientX - rect.left) * scaleX
|
|
277
|
+
my = (e.clientY - rect.top) * scaleY
|
|
271
278
|
}
|
|
272
279
|
for (const node of this.nodes) {
|
|
273
280
|
if (Math.hypot(node.x - mx, node.y - my) < node.r * 3) {
|
|
@@ -283,15 +290,18 @@ export class World {
|
|
|
283
290
|
|
|
284
291
|
update(newOptions) {
|
|
285
292
|
Object.assign(this.options, newOptions)
|
|
286
|
-
if (newOptions.colors && !newOptions.edgeColors) {
|
|
293
|
+
if (newOptions.colors && !newOptions.edgeColors && !this._edgeColorsExplicit) {
|
|
287
294
|
this.options.edgeColors = newOptions.colors
|
|
288
295
|
}
|
|
296
|
+
if (newOptions.edgeColors) {
|
|
297
|
+
this._edgeColorsExplicit = true
|
|
298
|
+
}
|
|
289
299
|
if (newOptions.forces !== undefined) {
|
|
290
300
|
this.forces = newOptions.forces
|
|
291
301
|
}
|
|
292
302
|
}
|
|
293
303
|
|
|
294
|
-
_resolveEdgeColor(
|
|
304
|
+
_resolveEdgeColor(a, b, i, j, edgeColors) {
|
|
295
305
|
const mode = this.options.edgeColorMode
|
|
296
306
|
if (typeof mode === 'function') return mode(a, b, i, j)
|
|
297
307
|
if (mode === 'source') return a.color
|
|
@@ -305,7 +315,7 @@ export class World {
|
|
|
305
315
|
if (dist >= edgeMaxDist) return false
|
|
306
316
|
|
|
307
317
|
const opacity = (1 - dist / edgeMaxDist) * edgeMaxOpacity
|
|
308
|
-
const color = this._resolveEdgeColor(
|
|
318
|
+
const color = this._resolveEdgeColor(a, b, i, j, edgeColors)
|
|
309
319
|
|
|
310
320
|
ctx.beginPath()
|
|
311
321
|
ctx.moveTo(a.x, a.y)
|
|
@@ -352,29 +362,31 @@ export class World {
|
|
|
352
362
|
|
|
353
363
|
if (opts.edgeStyle === 'dashed') ctx.setLineDash([4, 6])
|
|
354
364
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
365
|
+
mainPass: {
|
|
366
|
+
if (spatialIndex) {
|
|
367
|
+
const qt = new QuadTree(-1, -1, width + 2, height + 2)
|
|
368
|
+
for (const node of nodes) qt.insert(node)
|
|
369
|
+
|
|
370
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
371
|
+
if (maxEdgesPerFrame !== null && totalEdges >= maxEdgesPerFrame) break mainPass
|
|
372
|
+
if (edgeCounts && maxEdgesPerNode !== null && edgeCounts[i] >= maxEdgesPerNode) continue
|
|
373
|
+
const a = nodes[i]
|
|
374
|
+
const candidates = qt.queryRadius(a.x, a.y, edgeMaxDist)
|
|
375
|
+
for (const b of candidates) {
|
|
376
|
+
const j = b._index
|
|
377
|
+
if (j <= i) continue
|
|
378
|
+
if (maxEdgesPerFrame !== null && totalEdges >= maxEdgesPerFrame) break mainPass
|
|
379
|
+
if (edgeCounts && maxEdgesPerNode !== null && (edgeCounts[i] >= maxEdgesPerNode || edgeCounts[j] >= maxEdgesPerNode)) continue
|
|
380
|
+
if (this._drawEdge(ctx, a, b, i, j, opts, edgeCounts)) totalEdges++
|
|
381
|
+
}
|
|
370
382
|
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
383
|
+
} else {
|
|
384
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
385
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
386
|
+
if (maxEdgesPerFrame !== null && totalEdges >= maxEdgesPerFrame) break mainPass
|
|
387
|
+
if (edgeCounts && maxEdgesPerNode !== null && (edgeCounts[i] >= maxEdgesPerNode || edgeCounts[j] >= maxEdgesPerNode)) continue
|
|
388
|
+
if (this._drawEdge(ctx, nodes[i], nodes[j], i, j, opts, edgeCounts)) totalEdges++
|
|
389
|
+
}
|
|
378
390
|
}
|
|
379
391
|
}
|
|
380
392
|
}
|
|
@@ -494,6 +506,8 @@ export class World {
|
|
|
494
506
|
}
|
|
495
507
|
|
|
496
508
|
start() {
|
|
509
|
+
if (this._started) return
|
|
510
|
+
this._started = true
|
|
497
511
|
this._resize()
|
|
498
512
|
window.addEventListener('resize', this._onResize)
|
|
499
513
|
window.addEventListener('mousemove', this._onMouseMove)
|
|
@@ -522,6 +536,7 @@ export class World {
|
|
|
522
536
|
}
|
|
523
537
|
|
|
524
538
|
stop() {
|
|
539
|
+
this._started = false
|
|
525
540
|
cancelAnimationFrame(this.raf)
|
|
526
541
|
this.raf = null
|
|
527
542
|
if (this._io) { this._io.disconnect(); this._io = null }
|
package/src/adapters/react.js
CHANGED
|
@@ -8,10 +8,10 @@ import { World } from '../World.js'
|
|
|
8
8
|
* All World options are supported as props.
|
|
9
9
|
*
|
|
10
10
|
* Example:
|
|
11
|
-
* const ref =
|
|
11
|
+
* const ref = useStarflock({ nodeCount: 60, colors: ['#fff'], forces: [drift()] })
|
|
12
12
|
* return <canvas ref={ref} style={{ width: '100%', height: '100%' }} />
|
|
13
13
|
*/
|
|
14
|
-
export function
|
|
14
|
+
export function useStarflock(options = {}) {
|
|
15
15
|
const canvasRef = useRef(null)
|
|
16
16
|
const worldRef = useRef(null)
|
|
17
17
|
|
package/src/forces/attract.js
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
export function attract({ x = 0.5, y = 0.5, radius = 200, strength = 0.001 } = {}) {
|
|
12
12
|
return (nodes, context) => {
|
|
13
13
|
const { width, height } = context
|
|
14
|
-
const tx = typeof x === 'function' ? x(width, height) : (x <= 1 ? x * width : x)
|
|
15
|
-
const ty = typeof y === 'function' ? y(width, height) : (y <= 1 ? y * height : y)
|
|
14
|
+
const tx = typeof x === 'function' ? x(width, height) : (x >= 0 && x <= 1 ? x * width : x)
|
|
15
|
+
const ty = typeof y === 'function' ? y(width, height) : (y >= 0 && y <= 1 ? y * height : y)
|
|
16
16
|
|
|
17
17
|
for (const node of nodes) {
|
|
18
18
|
const dx = tx - node.x
|