pixel-flow-vue 1.0.10 → 1.0.11

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,57 +1,37 @@
1
1
  # pixel-flow-vue
2
2
 
3
- > A lightweight **Vue 3** pixel-flow animation plugin with full **TypeScript** support.
3
+ > A lightweight **Vue 3** canvas pixel-flow animation plugin with full **TypeScript** support.
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/pixel-flow-vue)](https://www.npmjs.com/package/pixel-flow-vue)
5
+ [![npm](https://img.shields.io/npm/v/pixel-flow-vue)](https://www.npmjs.com/package/pixel-flow-vue)
6
6
  [![license](https://img.shields.io/npm/l/pixel-flow-vue)](LICENSE)
7
- [![Vue 3](https://img.shields.io/badge/Vue-3.x-brightgreen)](https://vuejs.org/)
8
7
 
9
- ---
10
-
11
- ## ✨ Features
12
-
13
- - 🎨 **Pixel-art style** flowing particle animation via Canvas
14
- - ⚡ **Vite + Rollup** built — ESM & UMD bundles included
15
- - 🔷 **Full TypeScript** types exported out of the box
16
- - 🎛️ Highly configurable: colors, speed, particle count, pixel size
17
- - 🪄 `start()` / `stop()` control via component `ref`
18
-
19
- ---
20
-
21
- ## 📦 Installation
8
+ ## Install
22
9
 
23
10
  ```bash
24
11
  npm install pixel-flow-vue
25
- # or
26
- pnpm add pixel-flow-vue
27
- # or
28
- yarn add pixel-flow-vue
29
12
  ```
30
13
 
31
- ---
32
-
33
- ## 🚀 Quick Start
14
+ ## Usage
34
15
 
35
16
  ### Global registration
36
17
 
37
18
  ```ts
38
19
  // main.ts
39
20
  import { createApp } from 'vue'
40
- import App from './App.vue'
41
21
  import PixelFlowVue from 'pixel-flow-vue'
42
22
  import 'pixel-flow-vue/style.css'
23
+ import App from './App.vue'
43
24
 
44
25
  createApp(App).use(PixelFlowVue).mount('#app')
45
26
  ```
46
27
 
47
28
  ```vue
48
- <!-- anywhere in your app -->
49
29
  <template>
50
30
  <PixelFlow :width="800" :height="450" />
51
31
  </template>
52
32
  ```
53
33
 
54
- ### On-demand import
34
+ ### On-demand (Composition API)
55
35
 
56
36
  ```vue
57
37
  <template>
@@ -64,9 +44,10 @@ createApp(App).use(PixelFlowVue).mount('#app')
64
44
  :speed="1.5"
65
45
  :pixel-size="5"
66
46
  @ready="onReady"
47
+ @tick="onTick"
67
48
  />
68
- <button @click="flowRef?.stop()">Stop</button>
69
- <button @click="flowRef?.start()">Start</button>
49
+ <button @click="flowRef?.stop()">Pause</button>
50
+ <button @click="flowRef?.start()">Resume</button>
70
51
  </template>
71
52
 
72
53
  <script setup lang="ts">
@@ -75,107 +56,55 @@ import { PixelFlow } from 'pixel-flow-vue'
75
56
  import type { PixelFlowInstance } from 'pixel-flow-vue'
76
57
 
77
58
  const flowRef = ref<PixelFlowInstance | null>(null)
78
-
79
- function onReady() {
80
- console.log('Pixel flow is ready!')
81
- }
59
+ const onReady = () => console.log('ready!')
60
+ const onTick = (frame: number) => {}
82
61
  </script>
83
62
  ```
84
63
 
85
- ### Use the core engine directly (headless)
64
+ ### Headless (no component)
86
65
 
87
66
  ```ts
88
67
  import { createPixelFlow } from 'pixel-flow-vue'
89
68
 
90
- const canvas = document.querySelector<HTMLCanvasElement>('#myCanvas')!
91
- const stop = createPixelFlow({
92
- canvas,
93
- colors: ['#ff2fa0', '#ffdd00'],
94
- particleCount: 80,
95
- speed: 2,
96
- pixelSize: 6,
97
- })
98
-
99
- // Later: stop the animation
100
- stop()
69
+ const canvas = document.querySelector<HTMLCanvasElement>('#c')!
70
+ const stop = createPixelFlow({ canvas, particleCount: 80, speed: 2 })
71
+ // stop() ← call to cancel
101
72
  ```
102
73
 
103
- ---
104
-
105
- ## 🎛️ Props
106
-
107
- | Prop | Type | Default | Description |
108
- | --------------- | ---------- | --------------------------------------------------- | ------------------------------ |
109
- | `width` | `number` | `600` | Canvas width (px) |
110
- | `height` | `number` | `400` | Canvas height (px) |
111
- | `colors` | `string[]` | `['#00f5ff','#7b2fff','#ff2fa0','#ffdd00']` | Particle color palette |
112
- | `particleCount` | `number` | `120` | Number of particles |
113
- | `speed` | `number` | `1` | Speed multiplier (0.1 ~ 5) |
114
- | `pixelSize` | `number` | `4` | Base pixel block size (px) |
115
- | `background` | `string` | `'#0d0d0d'` | Canvas background color |
116
- | `autoPlay` | `boolean` | `true` | Start animation on mount |
74
+ ## Props
117
75
 
118
- ---
76
+ | Prop | Type | Default | Description |
77
+ |-----------------|------------|---------------------------------------------|--------------------------|
78
+ | `width` | `number` | `600` | Canvas width (px) |
79
+ | `height` | `number` | `400` | Canvas height (px) |
80
+ | `colors` | `string[]` | `['#00f5ff','#7b2fff','#ff2fa0','#ffdd00']` | Particle color palette |
81
+ | `particleCount` | `number` | `120` | Number of particles |
82
+ | `speed` | `number` | `1` | Speed multiplier 0.1–5 |
83
+ | `pixelSize` | `number` | `4` | Pixel block size (px) |
84
+ | `background` | `string` | `'#0d0d0d'` | Canvas background color |
85
+ | `autoPlay` | `boolean` | `true` | Auto-start on mount |
119
86
 
120
- ## 📡 Events
87
+ ## Events
121
88
 
122
- | Event | Payload | Description |
123
- | ------- | -------------- | ------------------------------------ |
124
- | `ready` | — | Fired when the animation loop starts |
125
- | `tick` | `frame: number`| Fired on every animation frame |
89
+ | Event | Payload | Description |
90
+ |---------|----------------|-----------------------------|
91
+ | `ready` | — | Fired when animation starts |
92
+ | `tick` | `frame: number`| Fired every animation frame |
126
93
 
127
- ---
128
-
129
- ## 🔌 Exposed Methods (via `ref`)
94
+ ## Exposed Methods
130
95
 
131
96
  | Method | Description |
132
- | --------- | ------------------------ |
97
+ |-----------|--------------------------|
133
98
  | `start()` | Start / restart the loop |
134
99
  | `stop()` | Stop the animation loop |
135
100
 
136
- ---
137
-
138
- ## 📁 Package Exports
139
-
140
- | Import path | Description |
141
- | ------------------------- | --------------------- |
142
- | `pixel-flow-vue` | Plugin + component |
143
- | `pixel-flow-vue/style.css`| Component styles |
144
-
145
- ---
146
-
147
- ## 🔧 Development
148
-
149
- ```bash
150
- # Clone and install
151
- git clone https://github.com/your-username/pixel-flow-vue.git
152
- cd pixel-flow-vue
153
- pnpm install
154
-
155
- # Start dev server (playground)
156
- pnpm dev
157
-
158
- # Build for production
159
- pnpm build
160
- ```
161
-
162
- ---
163
-
164
- ## 📤 Publishing to npm
101
+ ## Publish to npm
165
102
 
166
103
  ```bash
167
- # 1. Log in to npm
168
104
  npm login
169
-
170
- # 2. Build
171
- pnpm build
172
-
173
- # 3. Publish
174
105
  npm publish --access public
175
106
  ```
176
107
 
177
- ---
178
-
179
- ## 📄 License
108
+ ## License
180
109
 
181
- [MIT](LICENSE) © your-username
110
+ [MIT](LICENSE)
package/dist/index.d.ts CHANGED
@@ -27,6 +27,10 @@ declare type __VLS_WithDefaults<P, D> = {
27
27
  }> : P[K];
28
28
  };
29
29
 
30
+ /**
31
+ * 创建像素粒子流动画。
32
+ * @returns 停止函数,调用后取消动画循环。
33
+ */
30
34
  export declare function createPixelFlow(options: PixelFlowOptions): () => void;
31
35
 
32
36
  export declare const PixelFlow: DefineComponent<ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToRuntimeProps<Props>, {
@@ -39,8 +43,8 @@ pixelSize: number;
39
43
  background: string;
40
44
  autoPlay: boolean;
41
45
  }>>, {
42
- start: typeof start;
43
- stop: typeof stop_2;
46
+ start: () => void;
47
+ stop: () => void;
44
48
  }, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
45
49
  ready: () => void;
46
50
  tick: (frame: number) => void;
@@ -67,25 +71,24 @@ background: string;
67
71
  autoPlay: boolean;
68
72
  }, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
69
73
 
70
- /** PixelFlow 组件对外暴露的 ref 类型 */
74
+ /** PixelFlow 组件通过 defineExpose 暴露的实例方法 */
71
75
  export declare interface PixelFlowInstance {
72
- /** 开始 / 重启动画 */
73
76
  start: () => void;
74
- /** 暂停动画 */
75
77
  stop: () => void;
76
78
  }
77
79
 
78
- /** createPixelFlow 的配置参数 */
80
+ /** pixel-flow-vue 公共类型 */
81
+ /** createPixelFlow 配置项 */
79
82
  export declare interface PixelFlowOptions {
80
- /** 目标 Canvas 元素 */
83
+ /** 目标 HTMLCanvasElement */
81
84
  canvas: HTMLCanvasElement;
82
- /** 粒子颜色列表 */
85
+ /** 粒子颜色数组,默认 4 种霓虹色 */
83
86
  colors?: string[];
84
- /** 粒子数量 */
87
+ /** 粒子数量,默认 120 */
85
88
  particleCount?: number;
86
- /** 速度倍率 (0.1 ~ 5) */
89
+ /** 速度倍率 (0.1~5),默认 1 */
87
90
  speed?: number;
88
- /** 像素块大小(px */
91
+ /** 像素块大小 (px),默认 4 */
89
92
  pixelSize?: number;
90
93
  /** 每帧回调 */
91
94
  onTick?: () => void;
@@ -101,22 +104,18 @@ declare interface Props {
101
104
  width?: number;
102
105
  /** 画布高度,默认 400 */
103
106
  height?: number;
104
- /** 像素粒子颜色列表 */
107
+ /** 粒子颜色数组 */
105
108
  colors?: string[];
106
109
  /** 粒子数量,默认 120 */
107
110
  particleCount?: number;
108
- /** 粒子速度倍率 (0.1 ~ 5),默认 1 */
111
+ /** 速度倍率,默认 1 */
109
112
  speed?: number;
110
- /** 像素大小,默认 4 */
113
+ /** 像素块大小,默认 4 */
111
114
  pixelSize?: number;
112
- /** 背景色,默认 '#0d0d0d' */
115
+ /** 背景色,默认 #0d0d0d */
113
116
  background?: string;
114
- /** 是否自动播放,默认 true */
117
+ /** 挂载后自动开始,默认 true */
115
118
  autoPlay?: boolean;
116
119
  }
117
120
 
118
- declare function start(): void;
119
-
120
- declare function stop_2(): void;
121
-
122
121
  export { }
@@ -1,62 +1,55 @@
1
- import { defineComponent as b, ref as P, computed as _, onMounted as S, onUnmounted as z, watch as C, openBlock as D, createElementBlock as A, normalizeStyle as F } from "vue";
2
- function R(c) {
1
+ import { defineComponent as k, ref as A, onMounted as C, onUnmounted as z, watch as P, openBlock as S, createElementBlock as _, normalizeStyle as D } from "vue";
2
+ function F(n) {
3
3
  const {
4
4
  canvas: i,
5
- colors: s = ["#00f5ff", "#7b2fff", "#ff2fa0", "#ffdd00"],
5
+ colors: d = ["#00f5ff", "#7b2fff", "#ff2fa0", "#ffdd00"],
6
6
  particleCount: a = 120,
7
7
  speed: f = 1,
8
8
  pixelSize: h = 4,
9
- onTick: l
10
- } = c, x = i.getContext("2d");
11
- if (!x) throw new Error("[pixel-flow-vue] Failed to get 2D context");
12
- const e = x;
13
- let d = 0, u = !0;
9
+ onTick: o
10
+ } = n, u = i.getContext("2d");
11
+ if (!u) throw new Error("[pixel-flow-vue] Cannot get 2D context");
12
+ const e = u;
13
+ let s = 0, x = !0;
14
14
  function m() {
15
- const t = i.width, o = i.height;
16
15
  return {
17
- x: Math.random() * t,
18
- y: Math.random() * o,
19
- vx: (Math.random() - 0.5) * f * 1.5,
20
- vy: (Math.random() - 0.5) * f * 1.5,
21
- color: s[Math.floor(Math.random() * s.length)],
22
- size: h * (0.5 + Math.random() * 0.8),
16
+ x: Math.random() * i.width,
17
+ y: Math.random() * i.height,
18
+ vx: (Math.random() - 0.5) * f * 2,
19
+ vy: (Math.random() - 0.5) * f * 2,
20
+ color: d[Math.floor(Math.random() * d.length)],
21
+ size: h * (0.4 + Math.random() * 0.8),
23
22
  alpha: Math.random(),
24
23
  alphaDir: Math.random() > 0.5 ? 1 : -1
25
24
  };
26
25
  }
27
- const n = Array.from({ length: a }, m);
26
+ const l = Array.from({ length: a }, m);
28
27
  function v(t) {
29
- const o = i.width, r = i.height;
30
- t.x += t.vx * f, t.y += t.vy * f, (t.x < 0 || t.x > o) && (t.vx *= -1), (t.y < 0 || t.y > r) && (t.vy *= -1), t.alpha += 0.012 * t.alphaDir, t.alpha >= 1 && (t.alpha = 1, t.alphaDir = -1), t.alpha <= 0.1 && (t.alpha = 0.1, t.alphaDir = 1);
28
+ t.x += t.vx * f, t.y += t.vy * f, (t.x < 0 || t.x > i.width) && (t.vx *= -1), (t.y < 0 || t.y > i.height) && (t.vy *= -1), t.alpha += 0.012 * t.alphaDir, t.alpha >= 1 && (t.alpha = 1, t.alphaDir = -1), t.alpha <= 0.1 && (t.alpha = 0.1, t.alphaDir = 1);
31
29
  }
32
30
  function M(t) {
33
- e.globalAlpha = t.alpha, e.fillStyle = t.color, e.fillRect(
34
- Math.round(t.x),
35
- Math.round(t.y),
36
- Math.round(t.size),
37
- Math.round(t.size)
38
- );
31
+ e.globalAlpha = t.alpha, e.fillStyle = t.color, e.fillRect(Math.round(t.x), Math.round(t.y), Math.round(t.size), Math.round(t.size));
39
32
  }
40
- function k() {
41
- for (let o = 0; o < n.length; o++)
42
- for (let r = o + 1; r < n.length; r++) {
43
- const y = n[o].x - n[r].x, g = n[o].y - n[r].y, w = Math.sqrt(y * y + g * g);
44
- w < 80 && (e.globalAlpha = (1 - w / 80) * 0.25, e.strokeStyle = n[o].color, e.lineWidth = 0.8, e.beginPath(), e.moveTo(n[o].x, n[o].y), e.lineTo(n[r].x, n[r].y), e.stroke());
33
+ function b() {
34
+ for (let r = 0; r < l.length; r++)
35
+ for (let c = r + 1; c < l.length; c++) {
36
+ const p = l[r].x - l[c].x, g = l[r].y - l[c].y, w = Math.sqrt(p * p + g * g);
37
+ w < 80 && (e.globalAlpha = (1 - w / 80) * 0.22, e.strokeStyle = l[r].color, e.lineWidth = 0.7, e.beginPath(), e.moveTo(l[r].x, l[r].y), e.lineTo(l[c].x, l[c].y), e.stroke());
45
38
  }
46
39
  }
47
- function p() {
48
- if (!u) return;
49
- const t = i.width, o = i.height;
50
- e.globalAlpha = 0.18, e.fillStyle = "#000", e.fillRect(0, 0, t, o), e.globalAlpha = 1;
51
- for (const r of n)
52
- v(r), M(r);
53
- k(), l == null || l(), d = requestAnimationFrame(p);
40
+ function y() {
41
+ if (!x) return;
42
+ const { width: t, height: r } = i;
43
+ e.globalAlpha = 0.15, e.fillStyle = "#000", e.fillRect(0, 0, t, r), e.globalAlpha = 1;
44
+ for (const c of l)
45
+ v(c), M(c);
46
+ b(), o == null || o(), s = requestAnimationFrame(y);
54
47
  }
55
- return p(), function() {
56
- u = !1, cancelAnimationFrame(d);
48
+ return y(), function() {
49
+ x = !1, cancelAnimationFrame(s);
57
50
  };
58
51
  }
59
- const B = ["width", "height"], q = /* @__PURE__ */ b({
52
+ const R = ["width", "height"], B = /* @__PURE__ */ k({
60
53
  __name: "PixelFlow",
61
54
  props: {
62
55
  width: { default: 600 },
@@ -69,61 +62,52 @@ const B = ["width", "height"], q = /* @__PURE__ */ b({
69
62
  autoPlay: { type: Boolean, default: !0 }
70
63
  },
71
64
  emits: ["ready", "tick"],
72
- setup(c, { expose: i, emit: s }) {
73
- const a = c, f = s, h = P(null);
74
- let l = null, x = 0;
75
- const e = _(() => ({
76
- display: "block",
77
- background: a.background
78
- }));
79
- function d() {
80
- if (!h.value) return;
81
- l == null || l();
82
- const m = {
65
+ setup(n, { expose: i, emit: d }) {
66
+ const a = n, f = d, h = A(null);
67
+ let o = null, u = 0;
68
+ function e() {
69
+ h.value && (o == null || o(), o = F({
83
70
  canvas: h.value,
84
71
  colors: a.colors,
85
72
  particleCount: a.particleCount,
86
73
  speed: a.speed,
87
74
  pixelSize: a.pixelSize,
88
- onTick: () => f("tick", ++x)
89
- };
90
- l = R(m), f("ready");
75
+ onTick: () => f("tick", ++u)
76
+ }), f("ready"));
91
77
  }
92
- function u() {
93
- l == null || l(), l = null;
78
+ function s() {
79
+ o == null || o(), o = null;
94
80
  }
95
- return S(() => {
96
- a.autoPlay && d();
97
- }), z(() => {
98
- u();
99
- }), C(
81
+ return C(() => {
82
+ a.autoPlay && e();
83
+ }), z(() => s()), P(
100
84
  () => [a.particleCount, a.speed, a.pixelSize, a.colors],
101
85
  () => {
102
- l && d();
86
+ o && e();
103
87
  },
104
88
  { deep: !0 }
105
- ), i({ start: d, stop: u }), (m, n) => (D(), A("canvas", {
89
+ ), i({ start: e, stop: s }), (x, m) => (S(), _("canvas", {
106
90
  ref_key: "canvasRef",
107
91
  ref: h,
108
- width: c.width,
109
- height: c.height,
110
- style: F(e.value),
111
- class: "pixel-flow-canvas"
112
- }, null, 12, B));
92
+ width: n.width,
93
+ height: n.height,
94
+ style: D({ display: "block", background: n.background }),
95
+ class: "pixel-flow"
96
+ }, null, 12, R));
113
97
  }
114
- }), E = (c, i) => {
115
- const s = c.__vccOpts || c;
98
+ }), X = (n, i) => {
99
+ const d = n.__vccOpts || n;
116
100
  for (const [a, f] of i)
117
- s[a] = f;
118
- return s;
119
- }, I = /* @__PURE__ */ E(q, [["__scopeId", "data-v-89863d06"]]), j = {
120
- install(c) {
121
- c.component("PixelFlow", I);
101
+ d[a] = f;
102
+ return d;
103
+ }, q = /* @__PURE__ */ X(B, [["__scopeId", "data-v-02bb9f54"]]), I = {
104
+ install(n) {
105
+ n.component("PixelFlow", q);
122
106
  }
123
107
  };
124
108
  export {
125
- I as PixelFlow,
126
- R as createPixelFlow,
127
- j as default
109
+ q as PixelFlow,
110
+ F as createPixelFlow,
111
+ I as default
128
112
  };
129
113
  //# sourceMappingURL=pixel-flow-vue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pixel-flow-vue.js","sources":["../src/core/pixelFlow.ts","../src/components/PixelFlow.vue","../src/index.ts"],"sourcesContent":["import type { PixelFlowOptions } from '../types'\n\n// -------------------------------------------------------\n// 粒子结构\n// -------------------------------------------------------\ninterface Particle {\n x: number\n y: number\n vx: number\n vy: number\n color: string\n size: number\n alpha: number\n alphaDir: number\n}\n\n// -------------------------------------------------------\n// 工厂函数:createPixelFlow\n// 返回一个 stop 函数,调用后停止动画循环\n// -------------------------------------------------------\nexport function createPixelFlow(options: PixelFlowOptions): () => void {\n const {\n canvas,\n colors = ['#00f5ff', '#7b2fff', '#ff2fa0', '#ffdd00'],\n particleCount = 120,\n speed = 1,\n pixelSize = 4,\n onTick,\n } = options\n\n const rawCtx = canvas.getContext('2d')\n if (!rawCtx) throw new Error('[pixel-flow-vue] Failed to get 2D context')\n // After the null-check, rebind as non-nullable so TypeScript is satisfied\n // inside every nested closure (TS cannot narrow across function boundaries).\n const ctx: CanvasRenderingContext2D = rawCtx\n\n let rafId = 0\n let running = true\n\n // ---- 初始化粒子 ----\n function makeParticle(): Particle {\n const w = canvas.width\n const h = canvas.height\n return {\n x: Math.random() * w,\n y: Math.random() * h,\n vx: (Math.random() - 0.5) * speed * 1.5,\n vy: (Math.random() - 0.5) * speed * 1.5,\n color: colors[Math.floor(Math.random() * colors.length)],\n size: pixelSize * (0.5 + Math.random() * 0.8),\n alpha: Math.random(),\n alphaDir: Math.random() > 0.5 ? 1 : -1,\n }\n }\n\n const particles: Particle[] = Array.from({ length: particleCount }, makeParticle)\n\n // ---- 更新单个粒子 ----\n function updateParticle(p: Particle): void {\n const w = canvas.width\n const h = canvas.height\n\n p.x += p.vx * speed\n p.y += p.vy * speed\n\n // 边界回弹\n if (p.x < 0 || p.x > w) p.vx *= -1\n if (p.y < 0 || p.y > h) p.vy *= -1\n\n // 呼吸透明度\n p.alpha += 0.012 * p.alphaDir\n if (p.alpha >= 1) { p.alpha = 1; p.alphaDir = -1 }\n if (p.alpha <= 0.1) { p.alpha = 0.1; p.alphaDir = 1 }\n }\n\n // ---- 绘制单个像素块 ----\n function drawParticle(p: Particle): void {\n ctx.globalAlpha = p.alpha\n ctx.fillStyle = p.color\n ctx.fillRect(\n Math.round(p.x),\n Math.round(p.y),\n Math.round(p.size),\n Math.round(p.size),\n )\n }\n\n // ---- 连线:距离近的粒子之间画线 ----\n function drawConnections(): void {\n const maxDist = 80\n for (let i = 0; i < particles.length; i++) {\n for (let j = i + 1; j < particles.length; j++) {\n const dx = particles[i].x - particles[j].x\n const dy = particles[i].y - particles[j].y\n const dist = Math.sqrt(dx * dx + dy * dy)\n if (dist < maxDist) {\n ctx.globalAlpha = (1 - dist / maxDist) * 0.25\n ctx.strokeStyle = particles[i].color\n ctx.lineWidth = 0.8\n ctx.beginPath()\n ctx.moveTo(particles[i].x, particles[i].y)\n ctx.lineTo(particles[j].x, particles[j].y)\n ctx.stroke()\n }\n }\n }\n }\n\n // ---- 动画主循环 ----\n function loop(): void {\n if (!running) return\n\n const w = canvas.width\n const h = canvas.height\n\n // 拖尾效果:半透明清屏\n ctx.globalAlpha = 0.18\n ctx.fillStyle = '#000'\n ctx.fillRect(0, 0, w, h)\n\n ctx.globalAlpha = 1\n\n // 更新 & 绘制\n for (const p of particles) {\n updateParticle(p)\n drawParticle(p)\n }\n\n drawConnections()\n\n onTick?.()\n rafId = requestAnimationFrame(loop)\n }\n\n loop()\n\n // ---- 返回 stop 函数 ----\n return function stop() {\n running = false\n cancelAnimationFrame(rafId)\n }\n}\n","<template>\n <canvas\n ref=\"canvasRef\"\n :width=\"width\"\n :height=\"height\"\n :style=\"canvasStyle\"\n class=\"pixel-flow-canvas\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, computed, watch } from 'vue'\nimport type { CSSProperties } from 'vue'\nimport { createPixelFlow } from '../core/pixelFlow'\nimport type { PixelFlowOptions } from '../types'\n\n// ----------------------------------------------------------------\n// Props\n// ----------------------------------------------------------------\ninterface Props {\n /** 画布宽度,默认 600 */\n width?: number\n /** 画布高度,默认 400 */\n height?: number\n /** 像素粒子颜色列表 */\n colors?: string[]\n /** 粒子数量,默认 120 */\n particleCount?: number\n /** 粒子速度倍率 (0.1 ~ 5),默认 1 */\n speed?: number\n /** 像素大小,默认 4 */\n pixelSize?: number\n /** 背景色,默认 '#0d0d0d' */\n background?: string\n /** 是否自动播放,默认 true */\n autoPlay?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n width: 600,\n height: 400,\n colors: () => ['#00f5ff', '#7b2fff', '#ff2fa0', '#ffdd00'],\n particleCount: 120,\n speed: 1,\n pixelSize: 4,\n background: '#0d0d0d',\n autoPlay: true,\n})\n\n// ----------------------------------------------------------------\n// Emits\n// ----------------------------------------------------------------\nconst emit = defineEmits<{\n (e: 'ready'): void\n (e: 'tick', frame: number): void\n}>()\n\n// ----------------------------------------------------------------\n// Refs & state\n// ----------------------------------------------------------------\nconst canvasRef = ref<HTMLCanvasElement | null>(null)\nlet stopFn: (() => void) | null = null\nlet frame = 0\n\nconst canvasStyle = computed<CSSProperties>(() => ({\n display: 'block',\n background: props.background,\n}))\n\n// ----------------------------------------------------------------\n// Start / Stop helpers\n// ----------------------------------------------------------------\nfunction start() {\n if (!canvasRef.value) return\n stopFn?.()\n\n const options: PixelFlowOptions = {\n canvas: canvasRef.value,\n colors: props.colors,\n particleCount: props.particleCount,\n speed: props.speed,\n pixelSize: props.pixelSize,\n onTick: () => emit('tick', ++frame),\n }\n\n stopFn = createPixelFlow(options)\n emit('ready')\n}\n\nfunction stop() {\n stopFn?.()\n stopFn = null\n}\n\n// ----------------------------------------------------------------\n// Lifecycle\n// ----------------------------------------------------------------\nonMounted(() => {\n if (props.autoPlay) start()\n})\n\nonUnmounted(() => {\n stop()\n})\n\nwatch(\n () => [props.particleCount, props.speed, props.pixelSize, props.colors],\n () => {\n if (stopFn) start() // 重启以应用新配置\n },\n { deep: true }\n)\n\n// ----------------------------------------------------------------\n// Expose public API\n// ----------------------------------------------------------------\ndefineExpose({ start, stop })\n</script>\n\n<style scoped>\n.pixel-flow-canvas {\n border-radius: 4px;\n}\n</style>\n","import type { App } from 'vue'\nimport PixelFlow from './components/PixelFlow.vue'\nexport type { PixelFlowOptions, PixelFlowInstance } from './types'\nexport { createPixelFlow } from './core/pixelFlow'\n\n// ----------------------------------------------------------------\n// Vue 插件安装函数\n// ----------------------------------------------------------------\nconst PixelFlowVue = {\n install(app: App) {\n app.component('PixelFlow', PixelFlow)\n },\n}\n\n// ----------------------------------------------------------------\n// 导出\n// ----------------------------------------------------------------\nexport { PixelFlow }\nexport default PixelFlowVue\n"],"names":["createPixelFlow","options","canvas","colors","particleCount","speed","pixelSize","onTick","rawCtx","ctx","rafId","running","makeParticle","w","h","particles","updateParticle","p","drawParticle","drawConnections","i","j","dx","dy","dist","loop","props","__props","emit","__emit","canvasRef","ref","stopFn","frame","canvasStyle","computed","start","stop","onMounted","onUnmounted","watch","__expose","_createElementBlock","PixelFlowVue","app","PixelFlow"],"mappings":"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"}
1
+ {"version":3,"file":"pixel-flow-vue.js","sources":["../src/core/pixelFlow.ts","../src/components/PixelFlow.vue","../src/index.ts"],"sourcesContent":["import type { PixelFlowOptions } from '../types'\n\ninterface Particle {\n x: number; y: number\n vx: number; vy: number\n color: string\n size: number\n alpha: number\n alphaDir: 1 | -1\n}\n\n/**\n * 创建像素粒子流动画。\n * @returns 停止函数,调用后取消动画循环。\n */\nexport function createPixelFlow(options: PixelFlowOptions): () => void {\n const {\n canvas,\n colors = ['#00f5ff', '#7b2fff', '#ff2fa0', '#ffdd00'],\n particleCount = 120,\n speed = 1,\n pixelSize = 4,\n onTick,\n } = options\n\n // 在 null 检查之后重新赋值为非空类型,避免 TS18047 在嵌套函数中误报\n const maybeCtx = canvas.getContext('2d')\n if (!maybeCtx) throw new Error('[pixel-flow-vue] Cannot get 2D context')\n const ctx: CanvasRenderingContext2D = maybeCtx\n\n let rafId = 0\n let active = true\n\n /* ---- 粒子工厂 ---- */\n function spawn(): Particle {\n return {\n x: Math.random() * canvas.width,\n y: Math.random() * canvas.height,\n vx: (Math.random() - 0.5) * speed * 2,\n vy: (Math.random() - 0.5) * speed * 2,\n color: colors[Math.floor(Math.random() * colors.length)],\n size: pixelSize * (0.4 + Math.random() * 0.8),\n alpha: Math.random(),\n alphaDir: Math.random() > 0.5 ? 1 : -1,\n }\n }\n\n const particles: Particle[] = Array.from({ length: particleCount }, spawn)\n\n /* ---- 更新 ---- */\n function update(p: Particle): void {\n p.x += p.vx * speed\n p.y += p.vy * speed\n if (p.x < 0 || p.x > canvas.width) p.vx *= -1\n if (p.y < 0 || p.y > canvas.height) p.vy *= -1\n p.alpha += 0.012 * p.alphaDir\n if (p.alpha >= 1) { p.alpha = 1; p.alphaDir = -1 }\n if (p.alpha <= 0.1) { p.alpha = 0.1; p.alphaDir = 1 }\n }\n\n /* ---- 绘制像素块 ---- */\n function drawPixel(p: Particle): void {\n ctx.globalAlpha = p.alpha\n ctx.fillStyle = p.color\n ctx.fillRect(Math.round(p.x), Math.round(p.y), Math.round(p.size), Math.round(p.size))\n }\n\n /* ---- 连线 ---- */\n function drawLinks(): void {\n const MAX = 80\n for (let i = 0; i < particles.length; i++) {\n for (let j = i + 1; j < particles.length; j++) {\n const dx = particles[i].x - particles[j].x\n const dy = particles[i].y - particles[j].y\n const dist = Math.sqrt(dx * dx + dy * dy)\n if (dist < MAX) {\n ctx.globalAlpha = (1 - dist / MAX) * 0.22\n ctx.strokeStyle = particles[i].color\n ctx.lineWidth = 0.7\n ctx.beginPath()\n ctx.moveTo(particles[i].x, particles[i].y)\n ctx.lineTo(particles[j].x, particles[j].y)\n ctx.stroke()\n }\n }\n }\n }\n\n /* ---- 主循环 ---- */\n function loop(): void {\n if (!active) return\n const { width: w, height: h } = canvas\n // 拖尾:半透明黑色蒙层\n ctx.globalAlpha = 0.15\n ctx.fillStyle = '#000'\n ctx.fillRect(0, 0, w, h)\n ctx.globalAlpha = 1\n\n for (const p of particles) { update(p); drawPixel(p) }\n drawLinks()\n\n onTick?.()\n rafId = requestAnimationFrame(loop)\n }\n\n loop()\n\n return function stop(): void {\n active = false\n cancelAnimationFrame(rafId)\n }\n}\n","<template>\n <canvas\n ref=\"canvasRef\"\n :width=\"width\"\n :height=\"height\"\n :style=\"{ display: 'block', background: background }\"\n class=\"pixel-flow\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, watch } from 'vue'\nimport { createPixelFlow } from '../core/pixelFlow'\nimport type { PixelFlowInstance } from '../types'\n\n// ---------- props ----------\ninterface Props {\n /** 画布宽度,默认 600 */\n width?: number\n /** 画布高度,默认 400 */\n height?: number\n /** 粒子颜色数组 */\n colors?: string[]\n /** 粒子数量,默认 120 */\n particleCount?: number\n /** 速度倍率,默认 1 */\n speed?: number\n /** 像素块大小,默认 4 */\n pixelSize?: number\n /** 背景色,默认 #0d0d0d */\n background?: string\n /** 挂载后自动开始,默认 true */\n autoPlay?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n width: 600,\n height: 400,\n colors: () => ['#00f5ff', '#7b2fff', '#ff2fa0', '#ffdd00'],\n particleCount: 120,\n speed: 1,\n pixelSize: 4,\n background: '#0d0d0d',\n autoPlay: true,\n})\n\n// ---------- emits ----------\nconst emit = defineEmits<{\n (e: 'ready'): void\n (e: 'tick', frame: number): void\n}>()\n\n// ---------- internal ----------\nconst canvasRef = ref<HTMLCanvasElement | null>(null)\nlet stopFn: (() => void) | null = null\nlet frameCount = 0\n\nfunction start(): void {\n if (!canvasRef.value) return\n stopFn?.()\n stopFn = createPixelFlow({\n canvas: canvasRef.value,\n colors: props.colors,\n particleCount: props.particleCount,\n speed: props.speed,\n pixelSize: props.pixelSize,\n onTick: () => emit('tick', ++frameCount),\n })\n emit('ready')\n}\n\nfunction stop(): void {\n stopFn?.()\n stopFn = null\n}\n\nonMounted(() => { if (props.autoPlay) start() })\nonUnmounted(() => stop())\n\n// 配置变化时热重启\nwatch(\n () => [props.particleCount, props.speed, props.pixelSize, props.colors] as const,\n () => { if (stopFn) start() },\n { deep: true },\n)\n\n// ---------- expose ----------\ndefineExpose<PixelFlowInstance>({ start, stop })\n</script>\n\n<style scoped>\n.pixel-flow { border-radius: 4px; }\n</style>\n","/**\n * pixel-flow-vue\n * Vue 3 + TypeScript canvas pixel-flow animation plugin\n */\nimport type { App } from 'vue'\nimport PixelFlow from './components/PixelFlow.vue'\nimport { createPixelFlow } from './core/pixelFlow'\n\nexport type { PixelFlowOptions, PixelFlowInstance } from './types'\nexport { PixelFlow, createPixelFlow }\n\n// ---------- Vue plugin ----------\nconst PixelFlowVue = {\n install(app: App): void {\n app.component('PixelFlow', PixelFlow)\n },\n}\n\nexport default PixelFlowVue\n"],"names":["createPixelFlow","options","canvas","colors","particleCount","speed","pixelSize","onTick","maybeCtx","ctx","rafId","active","spawn","particles","update","p","drawPixel","drawLinks","i","j","dx","dy","dist","loop","w","h","props","__props","emit","__emit","canvasRef","ref","stopFn","frameCount","start","stop","onMounted","onUnmounted","watch","__expose","_createElementBlock","PixelFlowVue","app","PixelFlow"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- (function(s,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],n):(s=typeof globalThis<"u"?globalThis:s||self,n(s.PixelFlowVue={},s.Vue))})(this,function(s,n){"use strict";function w(f){const{canvas:c,colors:u=["#00f5ff","#7b2fff","#ff2fa0","#ffdd00"],particleCount:o=120,speed:d=1,pixelSize:x=4,onTick:l}=f,p=c.getContext("2d");if(!p)throw new Error("[pixel-flow-vue] Failed to get 2D context");const t=p;let h=0,m=!0;function y(){const e=c.width,a=c.height;return{x:Math.random()*e,y:Math.random()*a,vx:(Math.random()-.5)*d*1.5,vy:(Math.random()-.5)*d*1.5,color:u[Math.floor(Math.random()*u.length)],size:x*(.5+Math.random()*.8),alpha:Math.random(),alphaDir:Math.random()>.5?1:-1}}const i=Array.from({length:o},y);function S(e){const a=c.width,r=c.height;e.x+=e.vx*d,e.y+=e.vy*d,(e.x<0||e.x>a)&&(e.vx*=-1),(e.y<0||e.y>r)&&(e.vy*=-1),e.alpha+=.012*e.alphaDir,e.alpha>=1&&(e.alpha=1,e.alphaDir=-1),e.alpha<=.1&&(e.alpha=.1,e.alphaDir=1)}function z(e){t.globalAlpha=e.alpha,t.fillStyle=e.color,t.fillRect(Math.round(e.x),Math.round(e.y),Math.round(e.size),Math.round(e.size))}function C(){for(let a=0;a<i.length;a++)for(let r=a+1;r<i.length;r++){const M=i[a].x-i[r].x,_=i[a].y-i[r].y,P=Math.sqrt(M*M+_*_);P<80&&(t.globalAlpha=(1-P/80)*.25,t.strokeStyle=i[a].color,t.lineWidth=.8,t.beginPath(),t.moveTo(i[a].x,i[a].y),t.lineTo(i[r].x,i[r].y),t.stroke())}}function v(){if(!m)return;const e=c.width,a=c.height;t.globalAlpha=.18,t.fillStyle="#000",t.fillRect(0,0,e,a),t.globalAlpha=1;for(const r of i)S(r),z(r);C(),l==null||l(),h=requestAnimationFrame(v)}return v(),function(){m=!1,cancelAnimationFrame(h)}}const b=["width","height"],g=((f,c)=>{const u=f.__vccOpts||f;for(const[o,d]of c)u[o]=d;return u})(n.defineComponent({__name:"PixelFlow",props:{width:{default:600},height:{default:400},colors:{default:()=>["#00f5ff","#7b2fff","#ff2fa0","#ffdd00"]},particleCount:{default:120},speed:{default:1},pixelSize:{default:4},background:{default:"#0d0d0d"},autoPlay:{type:Boolean,default:!0}},emits:["ready","tick"],setup(f,{expose:c,emit:u}){const o=f,d=u,x=n.ref(null);let l=null,p=0;const t=n.computed(()=>({display:"block",background:o.background}));function h(){if(!x.value)return;l==null||l();const y={canvas:x.value,colors:o.colors,particleCount:o.particleCount,speed:o.speed,pixelSize:o.pixelSize,onTick:()=>d("tick",++p)};l=w(y),d("ready")}function m(){l==null||l(),l=null}return n.onMounted(()=>{o.autoPlay&&h()}),n.onUnmounted(()=>{m()}),n.watch(()=>[o.particleCount,o.speed,o.pixelSize,o.colors],()=>{l&&h()},{deep:!0}),c({start:h,stop:m}),(y,i)=>(n.openBlock(),n.createElementBlock("canvas",{ref_key:"canvasRef",ref:x,width:f.width,height:f.height,style:n.normalizeStyle(t.value),class:"pixel-flow-canvas"},null,12,b))}}),[["__scopeId","data-v-89863d06"]]),k={install(f){f.component("PixelFlow",g)}};s.PixelFlow=g,s.createPixelFlow=w,s.default=k,Object.defineProperties(s,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
1
+ (function(c,a){typeof exports=="object"&&typeof module<"u"?a(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],a):(c=typeof globalThis<"u"?globalThis:c||self,a(c.PixelFlowVue={},c.Vue))})(this,function(c,a){"use strict";function p(i){const{canvas:f,colors:u=["#00f5ff","#7b2fff","#ff2fa0","#ffdd00"],particleCount:o=120,speed:d=1,pixelSize:h=4,onTick:l}=i,y=f.getContext("2d");if(!y)throw new Error("[pixel-flow-vue] Cannot get 2D context");const t=y;let x=0,m=!0;function g(){return{x:Math.random()*f.width,y:Math.random()*f.height,vx:(Math.random()-.5)*d*2,vy:(Math.random()-.5)*d*2,color:u[Math.floor(Math.random()*u.length)],size:h*(.4+Math.random()*.8),alpha:Math.random(),alphaDir:Math.random()>.5?1:-1}}const n=Array.from({length:o},g);function S(e){e.x+=e.vx*d,e.y+=e.vy*d,(e.x<0||e.x>f.width)&&(e.vx*=-1),(e.y<0||e.y>f.height)&&(e.vy*=-1),e.alpha+=.012*e.alphaDir,e.alpha>=1&&(e.alpha=1,e.alphaDir=-1),e.alpha<=.1&&(e.alpha=.1,e.alphaDir=1)}function A(e){t.globalAlpha=e.alpha,t.fillStyle=e.color,t.fillRect(Math.round(e.x),Math.round(e.y),Math.round(e.size),Math.round(e.size))}function C(){for(let r=0;r<n.length;r++)for(let s=r+1;s<n.length;s++){const b=n[r].x-n[s].x,v=n[r].y-n[s].y,_=Math.sqrt(b*b+v*v);_<80&&(t.globalAlpha=(1-_/80)*.22,t.strokeStyle=n[r].color,t.lineWidth=.7,t.beginPath(),t.moveTo(n[r].x,n[r].y),t.lineTo(n[s].x,n[s].y),t.stroke())}}function M(){if(!m)return;const{width:e,height:r}=f;t.globalAlpha=.15,t.fillStyle="#000",t.fillRect(0,0,e,r),t.globalAlpha=1;for(const s of n)S(s),A(s);C(),l==null||l(),x=requestAnimationFrame(M)}return M(),function(){m=!1,cancelAnimationFrame(x)}}const k=["width","height"],w=((i,f)=>{const u=i.__vccOpts||i;for(const[o,d]of f)u[o]=d;return u})(a.defineComponent({__name:"PixelFlow",props:{width:{default:600},height:{default:400},colors:{default:()=>["#00f5ff","#7b2fff","#ff2fa0","#ffdd00"]},particleCount:{default:120},speed:{default:1},pixelSize:{default:4},background:{default:"#0d0d0d"},autoPlay:{type:Boolean,default:!0}},emits:["ready","tick"],setup(i,{expose:f,emit:u}){const o=i,d=u,h=a.ref(null);let l=null,y=0;function t(){h.value&&(l==null||l(),l=p({canvas:h.value,colors:o.colors,particleCount:o.particleCount,speed:o.speed,pixelSize:o.pixelSize,onTick:()=>d("tick",++y)}),d("ready"))}function x(){l==null||l(),l=null}return a.onMounted(()=>{o.autoPlay&&t()}),a.onUnmounted(()=>x()),a.watch(()=>[o.particleCount,o.speed,o.pixelSize,o.colors],()=>{l&&t()},{deep:!0}),f({start:t,stop:x}),(m,g)=>(a.openBlock(),a.createElementBlock("canvas",{ref_key:"canvasRef",ref:h,width:i.width,height:i.height,style:a.normalizeStyle({display:"block",background:i.background}),class:"pixel-flow"},null,12,k))}}),[["__scopeId","data-v-02bb9f54"]]),P={install(i){i.component("PixelFlow",w)}};c.PixelFlow=w,c.createPixelFlow=p,c.default=P,Object.defineProperties(c,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
2
2
  //# sourceMappingURL=pixel-flow-vue.umd.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"pixel-flow-vue.umd.cjs","sources":["../src/core/pixelFlow.ts","../src/components/PixelFlow.vue","../src/index.ts"],"sourcesContent":["import type { PixelFlowOptions } from '../types'\n\n// -------------------------------------------------------\n// 粒子结构\n// -------------------------------------------------------\ninterface Particle {\n x: number\n y: number\n vx: number\n vy: number\n color: string\n size: number\n alpha: number\n alphaDir: number\n}\n\n// -------------------------------------------------------\n// 工厂函数:createPixelFlow\n// 返回一个 stop 函数,调用后停止动画循环\n// -------------------------------------------------------\nexport function createPixelFlow(options: PixelFlowOptions): () => void {\n const {\n canvas,\n colors = ['#00f5ff', '#7b2fff', '#ff2fa0', '#ffdd00'],\n particleCount = 120,\n speed = 1,\n pixelSize = 4,\n onTick,\n } = options\n\n const rawCtx = canvas.getContext('2d')\n if (!rawCtx) throw new Error('[pixel-flow-vue] Failed to get 2D context')\n // After the null-check, rebind as non-nullable so TypeScript is satisfied\n // inside every nested closure (TS cannot narrow across function boundaries).\n const ctx: CanvasRenderingContext2D = rawCtx\n\n let rafId = 0\n let running = true\n\n // ---- 初始化粒子 ----\n function makeParticle(): Particle {\n const w = canvas.width\n const h = canvas.height\n return {\n x: Math.random() * w,\n y: Math.random() * h,\n vx: (Math.random() - 0.5) * speed * 1.5,\n vy: (Math.random() - 0.5) * speed * 1.5,\n color: colors[Math.floor(Math.random() * colors.length)],\n size: pixelSize * (0.5 + Math.random() * 0.8),\n alpha: Math.random(),\n alphaDir: Math.random() > 0.5 ? 1 : -1,\n }\n }\n\n const particles: Particle[] = Array.from({ length: particleCount }, makeParticle)\n\n // ---- 更新单个粒子 ----\n function updateParticle(p: Particle): void {\n const w = canvas.width\n const h = canvas.height\n\n p.x += p.vx * speed\n p.y += p.vy * speed\n\n // 边界回弹\n if (p.x < 0 || p.x > w) p.vx *= -1\n if (p.y < 0 || p.y > h) p.vy *= -1\n\n // 呼吸透明度\n p.alpha += 0.012 * p.alphaDir\n if (p.alpha >= 1) { p.alpha = 1; p.alphaDir = -1 }\n if (p.alpha <= 0.1) { p.alpha = 0.1; p.alphaDir = 1 }\n }\n\n // ---- 绘制单个像素块 ----\n function drawParticle(p: Particle): void {\n ctx.globalAlpha = p.alpha\n ctx.fillStyle = p.color\n ctx.fillRect(\n Math.round(p.x),\n Math.round(p.y),\n Math.round(p.size),\n Math.round(p.size),\n )\n }\n\n // ---- 连线:距离近的粒子之间画线 ----\n function drawConnections(): void {\n const maxDist = 80\n for (let i = 0; i < particles.length; i++) {\n for (let j = i + 1; j < particles.length; j++) {\n const dx = particles[i].x - particles[j].x\n const dy = particles[i].y - particles[j].y\n const dist = Math.sqrt(dx * dx + dy * dy)\n if (dist < maxDist) {\n ctx.globalAlpha = (1 - dist / maxDist) * 0.25\n ctx.strokeStyle = particles[i].color\n ctx.lineWidth = 0.8\n ctx.beginPath()\n ctx.moveTo(particles[i].x, particles[i].y)\n ctx.lineTo(particles[j].x, particles[j].y)\n ctx.stroke()\n }\n }\n }\n }\n\n // ---- 动画主循环 ----\n function loop(): void {\n if (!running) return\n\n const w = canvas.width\n const h = canvas.height\n\n // 拖尾效果:半透明清屏\n ctx.globalAlpha = 0.18\n ctx.fillStyle = '#000'\n ctx.fillRect(0, 0, w, h)\n\n ctx.globalAlpha = 1\n\n // 更新 & 绘制\n for (const p of particles) {\n updateParticle(p)\n drawParticle(p)\n }\n\n drawConnections()\n\n onTick?.()\n rafId = requestAnimationFrame(loop)\n }\n\n loop()\n\n // ---- 返回 stop 函数 ----\n return function stop() {\n running = false\n cancelAnimationFrame(rafId)\n }\n}\n","<template>\n <canvas\n ref=\"canvasRef\"\n :width=\"width\"\n :height=\"height\"\n :style=\"canvasStyle\"\n class=\"pixel-flow-canvas\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, computed, watch } from 'vue'\nimport type { CSSProperties } from 'vue'\nimport { createPixelFlow } from '../core/pixelFlow'\nimport type { PixelFlowOptions } from '../types'\n\n// ----------------------------------------------------------------\n// Props\n// ----------------------------------------------------------------\ninterface Props {\n /** 画布宽度,默认 600 */\n width?: number\n /** 画布高度,默认 400 */\n height?: number\n /** 像素粒子颜色列表 */\n colors?: string[]\n /** 粒子数量,默认 120 */\n particleCount?: number\n /** 粒子速度倍率 (0.1 ~ 5),默认 1 */\n speed?: number\n /** 像素大小,默认 4 */\n pixelSize?: number\n /** 背景色,默认 '#0d0d0d' */\n background?: string\n /** 是否自动播放,默认 true */\n autoPlay?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n width: 600,\n height: 400,\n colors: () => ['#00f5ff', '#7b2fff', '#ff2fa0', '#ffdd00'],\n particleCount: 120,\n speed: 1,\n pixelSize: 4,\n background: '#0d0d0d',\n autoPlay: true,\n})\n\n// ----------------------------------------------------------------\n// Emits\n// ----------------------------------------------------------------\nconst emit = defineEmits<{\n (e: 'ready'): void\n (e: 'tick', frame: number): void\n}>()\n\n// ----------------------------------------------------------------\n// Refs & state\n// ----------------------------------------------------------------\nconst canvasRef = ref<HTMLCanvasElement | null>(null)\nlet stopFn: (() => void) | null = null\nlet frame = 0\n\nconst canvasStyle = computed<CSSProperties>(() => ({\n display: 'block',\n background: props.background,\n}))\n\n// ----------------------------------------------------------------\n// Start / Stop helpers\n// ----------------------------------------------------------------\nfunction start() {\n if (!canvasRef.value) return\n stopFn?.()\n\n const options: PixelFlowOptions = {\n canvas: canvasRef.value,\n colors: props.colors,\n particleCount: props.particleCount,\n speed: props.speed,\n pixelSize: props.pixelSize,\n onTick: () => emit('tick', ++frame),\n }\n\n stopFn = createPixelFlow(options)\n emit('ready')\n}\n\nfunction stop() {\n stopFn?.()\n stopFn = null\n}\n\n// ----------------------------------------------------------------\n// Lifecycle\n// ----------------------------------------------------------------\nonMounted(() => {\n if (props.autoPlay) start()\n})\n\nonUnmounted(() => {\n stop()\n})\n\nwatch(\n () => [props.particleCount, props.speed, props.pixelSize, props.colors],\n () => {\n if (stopFn) start() // 重启以应用新配置\n },\n { deep: true }\n)\n\n// ----------------------------------------------------------------\n// Expose public API\n// ----------------------------------------------------------------\ndefineExpose({ start, stop })\n</script>\n\n<style scoped>\n.pixel-flow-canvas {\n border-radius: 4px;\n}\n</style>\n","import type { App } from 'vue'\nimport PixelFlow from './components/PixelFlow.vue'\nexport type { PixelFlowOptions, PixelFlowInstance } from './types'\nexport { createPixelFlow } from './core/pixelFlow'\n\n// ----------------------------------------------------------------\n// Vue 插件安装函数\n// ----------------------------------------------------------------\nconst PixelFlowVue = {\n install(app: App) {\n app.component('PixelFlow', PixelFlow)\n },\n}\n\n// ----------------------------------------------------------------\n// 导出\n// ----------------------------------------------------------------\nexport { PixelFlow }\nexport default PixelFlowVue\n"],"names":["createPixelFlow","options","canvas","colors","particleCount","speed","pixelSize","onTick","rawCtx","ctx","rafId","running","makeParticle","w","h","particles","updateParticle","p","drawParticle","drawConnections","i","j","dx","dy","dist","loop","props","__props","emit","__emit","canvasRef","ref","stopFn","frame","canvasStyle","computed","start","stop","onMounted","onUnmounted","watch","__expose","_createElementBlock","PixelFlowVue","app","PixelFlow"],"mappings":"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"}
1
+ {"version":3,"file":"pixel-flow-vue.umd.cjs","sources":["../src/core/pixelFlow.ts","../src/components/PixelFlow.vue","../src/index.ts"],"sourcesContent":["import type { PixelFlowOptions } from '../types'\n\ninterface Particle {\n x: number; y: number\n vx: number; vy: number\n color: string\n size: number\n alpha: number\n alphaDir: 1 | -1\n}\n\n/**\n * 创建像素粒子流动画。\n * @returns 停止函数,调用后取消动画循环。\n */\nexport function createPixelFlow(options: PixelFlowOptions): () => void {\n const {\n canvas,\n colors = ['#00f5ff', '#7b2fff', '#ff2fa0', '#ffdd00'],\n particleCount = 120,\n speed = 1,\n pixelSize = 4,\n onTick,\n } = options\n\n // 在 null 检查之后重新赋值为非空类型,避免 TS18047 在嵌套函数中误报\n const maybeCtx = canvas.getContext('2d')\n if (!maybeCtx) throw new Error('[pixel-flow-vue] Cannot get 2D context')\n const ctx: CanvasRenderingContext2D = maybeCtx\n\n let rafId = 0\n let active = true\n\n /* ---- 粒子工厂 ---- */\n function spawn(): Particle {\n return {\n x: Math.random() * canvas.width,\n y: Math.random() * canvas.height,\n vx: (Math.random() - 0.5) * speed * 2,\n vy: (Math.random() - 0.5) * speed * 2,\n color: colors[Math.floor(Math.random() * colors.length)],\n size: pixelSize * (0.4 + Math.random() * 0.8),\n alpha: Math.random(),\n alphaDir: Math.random() > 0.5 ? 1 : -1,\n }\n }\n\n const particles: Particle[] = Array.from({ length: particleCount }, spawn)\n\n /* ---- 更新 ---- */\n function update(p: Particle): void {\n p.x += p.vx * speed\n p.y += p.vy * speed\n if (p.x < 0 || p.x > canvas.width) p.vx *= -1\n if (p.y < 0 || p.y > canvas.height) p.vy *= -1\n p.alpha += 0.012 * p.alphaDir\n if (p.alpha >= 1) { p.alpha = 1; p.alphaDir = -1 }\n if (p.alpha <= 0.1) { p.alpha = 0.1; p.alphaDir = 1 }\n }\n\n /* ---- 绘制像素块 ---- */\n function drawPixel(p: Particle): void {\n ctx.globalAlpha = p.alpha\n ctx.fillStyle = p.color\n ctx.fillRect(Math.round(p.x), Math.round(p.y), Math.round(p.size), Math.round(p.size))\n }\n\n /* ---- 连线 ---- */\n function drawLinks(): void {\n const MAX = 80\n for (let i = 0; i < particles.length; i++) {\n for (let j = i + 1; j < particles.length; j++) {\n const dx = particles[i].x - particles[j].x\n const dy = particles[i].y - particles[j].y\n const dist = Math.sqrt(dx * dx + dy * dy)\n if (dist < MAX) {\n ctx.globalAlpha = (1 - dist / MAX) * 0.22\n ctx.strokeStyle = particles[i].color\n ctx.lineWidth = 0.7\n ctx.beginPath()\n ctx.moveTo(particles[i].x, particles[i].y)\n ctx.lineTo(particles[j].x, particles[j].y)\n ctx.stroke()\n }\n }\n }\n }\n\n /* ---- 主循环 ---- */\n function loop(): void {\n if (!active) return\n const { width: w, height: h } = canvas\n // 拖尾:半透明黑色蒙层\n ctx.globalAlpha = 0.15\n ctx.fillStyle = '#000'\n ctx.fillRect(0, 0, w, h)\n ctx.globalAlpha = 1\n\n for (const p of particles) { update(p); drawPixel(p) }\n drawLinks()\n\n onTick?.()\n rafId = requestAnimationFrame(loop)\n }\n\n loop()\n\n return function stop(): void {\n active = false\n cancelAnimationFrame(rafId)\n }\n}\n","<template>\n <canvas\n ref=\"canvasRef\"\n :width=\"width\"\n :height=\"height\"\n :style=\"{ display: 'block', background: background }\"\n class=\"pixel-flow\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, watch } from 'vue'\nimport { createPixelFlow } from '../core/pixelFlow'\nimport type { PixelFlowInstance } from '../types'\n\n// ---------- props ----------\ninterface Props {\n /** 画布宽度,默认 600 */\n width?: number\n /** 画布高度,默认 400 */\n height?: number\n /** 粒子颜色数组 */\n colors?: string[]\n /** 粒子数量,默认 120 */\n particleCount?: number\n /** 速度倍率,默认 1 */\n speed?: number\n /** 像素块大小,默认 4 */\n pixelSize?: number\n /** 背景色,默认 #0d0d0d */\n background?: string\n /** 挂载后自动开始,默认 true */\n autoPlay?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n width: 600,\n height: 400,\n colors: () => ['#00f5ff', '#7b2fff', '#ff2fa0', '#ffdd00'],\n particleCount: 120,\n speed: 1,\n pixelSize: 4,\n background: '#0d0d0d',\n autoPlay: true,\n})\n\n// ---------- emits ----------\nconst emit = defineEmits<{\n (e: 'ready'): void\n (e: 'tick', frame: number): void\n}>()\n\n// ---------- internal ----------\nconst canvasRef = ref<HTMLCanvasElement | null>(null)\nlet stopFn: (() => void) | null = null\nlet frameCount = 0\n\nfunction start(): void {\n if (!canvasRef.value) return\n stopFn?.()\n stopFn = createPixelFlow({\n canvas: canvasRef.value,\n colors: props.colors,\n particleCount: props.particleCount,\n speed: props.speed,\n pixelSize: props.pixelSize,\n onTick: () => emit('tick', ++frameCount),\n })\n emit('ready')\n}\n\nfunction stop(): void {\n stopFn?.()\n stopFn = null\n}\n\nonMounted(() => { if (props.autoPlay) start() })\nonUnmounted(() => stop())\n\n// 配置变化时热重启\nwatch(\n () => [props.particleCount, props.speed, props.pixelSize, props.colors] as const,\n () => { if (stopFn) start() },\n { deep: true },\n)\n\n// ---------- expose ----------\ndefineExpose<PixelFlowInstance>({ start, stop })\n</script>\n\n<style scoped>\n.pixel-flow { border-radius: 4px; }\n</style>\n","/**\n * pixel-flow-vue\n * Vue 3 + TypeScript canvas pixel-flow animation plugin\n */\nimport type { App } from 'vue'\nimport PixelFlow from './components/PixelFlow.vue'\nimport { createPixelFlow } from './core/pixelFlow'\n\nexport type { PixelFlowOptions, PixelFlowInstance } from './types'\nexport { PixelFlow, createPixelFlow }\n\n// ---------- Vue plugin ----------\nconst PixelFlowVue = {\n install(app: App): void {\n app.component('PixelFlow', PixelFlow)\n },\n}\n\nexport default PixelFlowVue\n"],"names":["createPixelFlow","options","canvas","colors","particleCount","speed","pixelSize","onTick","maybeCtx","ctx","rafId","active","spawn","particles","update","p","drawPixel","drawLinks","i","j","dx","dy","dist","loop","w","h","props","__props","emit","__emit","canvasRef","ref","stopFn","frameCount","start","stop","onMounted","onUnmounted","watch","__expose","_createElementBlock","PixelFlowVue","app","PixelFlow"],"mappings":"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"}
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .pixel-flow-canvas[data-v-89863d06]{border-radius:4px}
1
+ .pixel-flow[data-v-02bb9f54]{border-radius:4px}
package/package.json CHANGED
@@ -1,41 +1,34 @@
1
1
  {
2
2
  "name": "pixel-flow-vue",
3
- "version": "1.0.10",
4
- "description": "A lightweight Vue 3 pixel-flow animation plugin with TypeScript support",
3
+ "version": "1.0.11",
4
+ "description": "A lightweight Vue 3 pixel-flow canvas animation plugin with TypeScript support",
5
5
  "type": "module",
6
6
  "main": "./dist/pixel-flow-vue.umd.cjs",
7
7
  "module": "./dist/pixel-flow-vue.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "import": "./dist/pixel-flow-vue.js",
12
- "require": "./dist/pixel-flow-vue.umd.cjs",
13
- "types": "./dist/index.d.ts"
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/pixel-flow-vue.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/pixel-flow-vue.umd.cjs"
18
+ }
14
19
  },
15
20
  "./style.css": "./dist/style.css"
16
21
  },
17
- "files": [
18
- "dist",
19
- "README.md",
20
- "LICENSE"
21
- ],
22
+ "files": ["dist", "README.md", "LICENSE"],
23
+ "sideEffects": ["**/*.css"],
22
24
  "scripts": {
23
- "dev": "vite",
25
+ "dev": "vite --config vite.dev.config.ts",
24
26
  "build": "vue-tsc --noEmit && vite build",
25
- "build:types": "vue-tsc --declaration --emitDeclarationOnly --outDir dist",
26
- "preview": "vite preview",
27
- "lint": "eslint src --ext .ts,.vue --fix"
27
+ "prepublishOnly": "npm run build"
28
28
  },
29
29
  "keywords": [
30
- "vue",
31
- "vue3",
32
- "plugin",
33
- "pixel",
34
- "flow",
35
- "animation",
36
- "canvas",
37
- "typescript",
38
- "component"
30
+ "vue", "vue3", "plugin", "pixel", "flow",
31
+ "animation", "canvas", "typescript", "component"
39
32
  ],
40
33
  "author": "",
41
34
  "license": "MIT",
@@ -51,10 +44,10 @@
51
44
  "vue": "^3.3.0"
52
45
  },
53
46
  "devDependencies": {
54
- "@types/node": "^20.14.0",
47
+ "@types/node": "^20.0.0",
55
48
  "@vitejs/plugin-vue": "^5.0.0",
56
49
  "typescript": "^5.4.0",
57
- "vite": "^5.3.0",
50
+ "vite": "^5.0.0",
58
51
  "vite-plugin-dts": "^3.9.0",
59
52
  "vue": "^3.4.0",
60
53
  "vue-tsc": "^2.0.0"