magic-canvas-text 1.0.0 → 1.0.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 +129 -46
- package/package.json +1 -1
- package/src/index.js +133 -135
package/README.md
CHANGED
|
@@ -1,36 +1,49 @@
|
|
|
1
|
-
# ✨ Magic
|
|
1
|
+
# ✨ Magic Canvas Text
|
|
2
2
|
|
|
3
|
-
**Magic
|
|
4
|
-
It supports mouse and touch interactions, gradients, multiple animation start modes, and mobile
|
|
3
|
+
**Magic Canvas Text** is a lightweight npm library for rendering animated, interactive particle-based text using the HTML5 canvas.
|
|
4
|
+
It supports mouse and touch interactions, gradients, multiple animation start modes, and mobile‑optimized behavior.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Perfect for landing pages, hero sections, and playful UI elements.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
## 📦 Installation
|
|
11
11
|
|
|
12
|
-
Install the package via npm:
|
|
12
|
+
Install the package via npm or yarn:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
npm install magic-canvas-text
|
|
16
|
-
|
|
16
|
+
```
|
|
17
|
+
|
|
17
18
|
```bash
|
|
18
19
|
yarn add magic-canvas-text
|
|
19
20
|
```
|
|
20
21
|
|
|
22
|
+
---
|
|
23
|
+
|
|
21
24
|
## 🚀 Usage
|
|
22
|
-
Import
|
|
23
|
-
import { initializeText } from "magic-canvas-text;
|
|
24
25
|
|
|
25
26
|
### HTML
|
|
26
27
|
|
|
27
28
|
Create a container element where the canvas will be injected:
|
|
28
29
|
|
|
30
|
+
```html
|
|
29
31
|
<div class="text-con"></div>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> ⚠️ The container should be empty and have a defined width & height.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
### JavaScript
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
import { initializeText } from "magic-canvas-text";
|
|
42
|
+
|
|
43
|
+
const element = document.querySelector(".your-class");
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
textContainerClass: "text-con",
|
|
45
|
+
const magicText = initializeText({
|
|
46
|
+
element,
|
|
34
47
|
text: "Magic Text",
|
|
35
48
|
fontSize: 100,
|
|
36
49
|
fontSizeMobile: 30,
|
|
@@ -44,62 +57,132 @@ initializeText({
|
|
|
44
57
|
colorOne: "#ff0000",
|
|
45
58
|
colorTwo: "#00ff00",
|
|
46
59
|
colorThree: "#0000ff",
|
|
47
|
-
startMode: "
|
|
60
|
+
startMode: "random",
|
|
48
61
|
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### ✅ Important API Change
|
|
65
|
+
|
|
66
|
+
Magic Canvas Text now **expects a DOM element**, not a class name or selector string.
|
|
49
67
|
|
|
50
|
-
|
|
68
|
+
This makes the API:
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
| `text` | `string` | ❌ No | Defaults to `"Magic Text"` |
|
|
56
|
-
| `fontSize` | `number` | ✅ Yes | Required for proper font rendering |
|
|
57
|
-
| `fontSizeMobile` | `number` | ✅ Yes | Required for mobile rendering |
|
|
58
|
-
| `textColor` | `string` | ⚠️ Conditional | Defaults to `#000000` |
|
|
59
|
-
| `bgColor` | `string` | ❌ No | Defaults to `#ffffff` |
|
|
60
|
-
| `effectColorApplied` | `boolean` | ⚠️ Recommended | Enables hover color effect |
|
|
61
|
-
| `effectColor` | `string` | ⚠️ Conditional | Required when `effectColorApplied === true` |
|
|
62
|
-
| `effectRadius` | `number` | ❌ No | Defaults to `80` (mobile capped at `100`) |
|
|
63
|
-
| `duration` | `number` | ❌ No | Defaults internally to `0.05` |
|
|
64
|
-
| `gradient` | `boolean` | ⚠️ Recommended | Enables gradient text |
|
|
65
|
-
| `colorOne` | `string` | ⚠️ Conditional | Required when `gradient === true` |
|
|
66
|
-
| `colorTwo` | `string` | ⚠️ Conditional | Required when `gradient === true` |
|
|
67
|
-
| `colorThree` | `string` | ⚠️ Conditional | Required when `gradient === true` |
|
|
68
|
-
| `startMode` | `string` | ❌ No | Defaults to `random` |
|
|
70
|
+
* more predictable
|
|
71
|
+
* framework‑friendly (React, Vue, Svelte)
|
|
72
|
+
* safer against double initialization
|
|
69
73
|
|
|
74
|
+
---
|
|
70
75
|
|
|
71
|
-
|
|
76
|
+
## 🔧 Configuration Options
|
|
77
|
+
|
|
78
|
+
| Option | Type | Required | Description |
|
|
79
|
+
| -------------------- | ------------- | -------------- | ---------------------------------------------------- |
|
|
80
|
+
| `element` | `HTMLElement` | ✅ Yes | Target element where the canvas will be mounted |
|
|
81
|
+
| `text` | `string` | ❌ No | Text to render (default: `"Magic Text"`) |
|
|
82
|
+
| `fontSize` | `number` | ❌ No | Desktop font size (default: `100`) |
|
|
83
|
+
| `fontSizeMobile` | `number` | ❌ No | Mobile font size (default: `30`) |
|
|
84
|
+
| `textColor` | `string` | ❌ No | Solid text color (default: `#000000`) |
|
|
85
|
+
| `bgColor` | `string` | ❌ No | Canvas background color (default: `#ffffff`) |
|
|
86
|
+
| `effectColorApplied` | `boolean` | ❌ No | Enables hover color effect |
|
|
87
|
+
| `effectColor` | `string` | ⚠️ Conditional | Required if `effectColorApplied === true` |
|
|
88
|
+
| `effectRadius` | `number` | ❌ No | Interaction radius (default: `80`, mobile max `100`) |
|
|
89
|
+
| `duration` | `number` | ❌ No | Particle easing speed (default: `0.05`) |
|
|
90
|
+
| `gradient` | `boolean` | ❌ No | Enables gradient text |
|
|
91
|
+
| `colorOne` | `string` | ⚠️ Conditional | Required when `gradient === true` |
|
|
92
|
+
| `colorTwo` | `string` | ⚠️ Conditional | Required when `gradient === true` |
|
|
93
|
+
| `colorThree` | `string` | ⚠️ Conditional | Required when `gradient === true` |
|
|
94
|
+
| `startMode` | `string` | ❌ No | Particle start animation mode (default: `random`) |
|
|
72
95
|
|
|
73
|
-
|
|
96
|
+
---
|
|
74
97
|
|
|
75
|
-
|
|
98
|
+
## 🎬 Start Modes
|
|
76
99
|
|
|
77
|
-
|
|
100
|
+
* `random` – particles spawn at random positions
|
|
101
|
+
* `left` – particles animate in from the left
|
|
102
|
+
* `center` – particles animate from the center
|
|
103
|
+
* `top` – particles animate in from top
|
|
104
|
+
* `bottom` – particles animate in from below
|
|
78
105
|
|
|
79
|
-
|
|
106
|
+
---
|
|
80
107
|
|
|
81
|
-
|
|
108
|
+
## 🧹 Cleanup
|
|
82
109
|
|
|
83
|
-
|
|
110
|
+
Each initialization returns an instance with a `destroy()` method.
|
|
84
111
|
|
|
85
|
-
|
|
112
|
+
```js
|
|
113
|
+
const magicText = initializeText({ element, text: "Hello" });
|
|
86
114
|
|
|
87
115
|
// later
|
|
88
116
|
magicText.destroy();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This removes:
|
|
120
|
+
|
|
121
|
+
* the canvas
|
|
122
|
+
* animation loop
|
|
123
|
+
* event listeners
|
|
124
|
+
* internal instance reference
|
|
125
|
+
|
|
126
|
+
---
|
|
89
127
|
|
|
128
|
+
## 📱 Mobile Support
|
|
90
129
|
|
|
91
|
-
|
|
130
|
+
* Touch interaction support
|
|
131
|
+
* Optimized interaction radius
|
|
132
|
+
* Separate mobile font sizing
|
|
92
133
|
|
|
93
|
-
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 🧩 Framework Usage
|
|
137
|
+
|
|
138
|
+
### React
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
const ref = useRef(null);
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
const instance = initializeText({
|
|
145
|
+
element: ref.current,
|
|
146
|
+
text: "React Magic",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return () => instance?.destroy();
|
|
150
|
+
}, []);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Vue
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
const title = ref(null);
|
|
157
|
+
|
|
158
|
+
onMounted(() => {
|
|
159
|
+
initializeText({
|
|
160
|
+
element: title.value,
|
|
161
|
+
text: "Vue Magic",
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
94
167
|
|
|
95
|
-
|
|
168
|
+
## 🌐 Demo
|
|
169
|
+
|
|
170
|
+
[Magic Canvas Text Demo](https://luayabbas1981.github.io/magic-text/)
|
|
171
|
+
|
|
172
|
+
---
|
|
96
173
|
|
|
97
|
-
|
|
174
|
+
## 📦 Github
|
|
98
175
|
|
|
99
|
-
|
|
176
|
+
[magic-canvas-text on npm](https://github.com/Luayabbas1981/magic-canvas-text)
|
|
100
177
|
|
|
101
|
-
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 👤 Portfolio
|
|
181
|
+
|
|
182
|
+
[Portfolio](https://luay-portfolio.interflowcode.de/)
|
|
183
|
+
|
|
184
|
+
---
|
|
102
185
|
|
|
103
|
-
|
|
186
|
+
## 📄 License
|
|
104
187
|
|
|
105
|
-
MIT License
|
|
188
|
+
MIT License
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,250 +1,248 @@
|
|
|
1
1
|
// Initialize Magic Text
|
|
2
2
|
function initializeText({
|
|
3
|
-
|
|
4
|
-
text,
|
|
5
|
-
fontSize,
|
|
6
|
-
fontSizeMobile,
|
|
7
|
-
textColor,
|
|
8
|
-
bgColor,
|
|
9
|
-
effectColorApplied,
|
|
10
|
-
effectColor,
|
|
11
|
-
effectRadius,
|
|
12
|
-
duration,
|
|
13
|
-
gradient,
|
|
3
|
+
element,
|
|
4
|
+
text = "Magic Text",
|
|
5
|
+
fontSize = 100,
|
|
6
|
+
fontSizeMobile = 30,
|
|
7
|
+
textColor = "#000",
|
|
8
|
+
bgColor = "#fff",
|
|
9
|
+
effectColorApplied = false,
|
|
10
|
+
effectColor = "#0088ff",
|
|
11
|
+
effectRadius = 80,
|
|
12
|
+
duration = 0.05,
|
|
13
|
+
gradient = false,
|
|
14
14
|
colorOne,
|
|
15
15
|
colorTwo,
|
|
16
16
|
colorThree,
|
|
17
|
-
startMode,
|
|
17
|
+
startMode = "random",
|
|
18
18
|
}) {
|
|
19
|
-
|
|
19
|
+
if (typeof window === "undefined") return;
|
|
20
|
+
|
|
21
|
+
if (!(element instanceof HTMLElement)) {
|
|
22
|
+
console.error("MagicText: element must be a DOM element");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// prevent double init
|
|
27
|
+
if (element._magicTextInstance) {
|
|
28
|
+
return element._magicTextInstance;
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
const gap = 1;
|
|
21
32
|
let animationId;
|
|
22
33
|
let destroyed = false;
|
|
23
34
|
const particles = [];
|
|
35
|
+
|
|
24
36
|
const mouse = {
|
|
25
37
|
x: null,
|
|
26
38
|
y: null,
|
|
27
39
|
radius: effectRadius,
|
|
28
40
|
};
|
|
29
|
-
|
|
30
|
-
if (typeof window === "undefined") return;
|
|
41
|
+
|
|
31
42
|
const isMobile =
|
|
32
43
|
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
|
33
44
|
navigator.userAgent,
|
|
34
45
|
);
|
|
35
46
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.error(`No element found with class name "${textContainerClass}".`);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
textContainer.classList.add("magic-text-container");
|
|
47
|
+
element.classList.add("magic-text-container");
|
|
48
|
+
|
|
42
49
|
const canvas = document.createElement("canvas");
|
|
43
50
|
canvas.classList.add("magic-text-canvas");
|
|
44
|
-
|
|
51
|
+
element.appendChild(canvas);
|
|
52
|
+
|
|
45
53
|
const ctx = canvas.getContext("2d");
|
|
46
|
-
canvas.width = textContainer.clientWidth;
|
|
47
|
-
canvas.height = textContainer.clientHeight;
|
|
48
|
-
const canvasWidth = canvas.width;
|
|
49
|
-
const canvasHeight = canvas.height;
|
|
50
|
-
canvas.style.backgroundColor = bgColor || "#ffffff";
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
function resizeCanvas() {
|
|
56
|
+
canvas.width = element.clientWidth;
|
|
57
|
+
canvas.height = element.clientHeight;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
resizeCanvas();
|
|
61
|
+
canvas.style.backgroundColor = bgColor;
|
|
62
|
+
|
|
63
|
+
const canvasWidth = () => canvas.width;
|
|
64
|
+
const canvasHeight = () => canvas.height;
|
|
65
|
+
|
|
66
|
+
// ---------- Particle ----------
|
|
53
67
|
class Particle {
|
|
54
|
-
constructor(
|
|
55
|
-
this.ctx = ctx;
|
|
68
|
+
constructor(x, y, color) {
|
|
56
69
|
this.originX = x;
|
|
57
70
|
this.originY = y;
|
|
71
|
+
|
|
58
72
|
const start = getStartPosition(startMode, x, y);
|
|
59
73
|
this.x = start.x;
|
|
60
74
|
this.y = start.y;
|
|
61
75
|
|
|
62
76
|
this.color = color;
|
|
63
77
|
this.baseColor = color;
|
|
64
|
-
this.secondColor = effectColor
|
|
78
|
+
this.secondColor = effectColor;
|
|
65
79
|
this.size = gap;
|
|
66
|
-
this.ease = Math.random() * 0.1 +
|
|
80
|
+
this.ease = Math.random() * 0.1 + duration;
|
|
67
81
|
this.pushX = 0;
|
|
68
82
|
this.pushY = 0;
|
|
69
83
|
this.friction = 0.9;
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
update() {
|
|
73
|
-
let
|
|
87
|
+
let hovering = false;
|
|
88
|
+
|
|
74
89
|
if (mouse.x !== null) {
|
|
75
90
|
const dx = this.x - mouse.x;
|
|
76
91
|
const dy = this.y - mouse.y;
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
93
|
+
|
|
94
|
+
if (dist < mouse.radius) {
|
|
95
|
+
hovering = true;
|
|
96
|
+
const force = (mouse.radius - dist) / mouse.radius;
|
|
81
97
|
const angle = Math.atan2(dy, dx);
|
|
98
|
+
|
|
82
99
|
this.pushX += Math.cos(angle) * force * 7 * this.ease * 5;
|
|
83
100
|
this.pushY += Math.sin(angle) * force * 7 * this.ease * 5;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
effectColor &&
|
|
87
|
-
this.originX > mouse.x &&
|
|
88
|
-
this.originY > mouse.y
|
|
89
|
-
) {
|
|
101
|
+
|
|
102
|
+
if (effectColorApplied) {
|
|
90
103
|
this.color = this.secondColor;
|
|
91
104
|
}
|
|
92
105
|
}
|
|
93
106
|
}
|
|
94
107
|
|
|
95
|
-
if (!
|
|
108
|
+
if (!hovering) {
|
|
96
109
|
this.color = this.baseColor;
|
|
97
110
|
}
|
|
98
|
-
|
|
111
|
+
|
|
99
112
|
this.pushX *= this.friction;
|
|
100
113
|
this.pushY *= this.friction;
|
|
101
114
|
|
|
102
115
|
this.x += this.pushX;
|
|
103
116
|
this.y += this.pushY;
|
|
104
117
|
|
|
105
|
-
// Ease back to original text position
|
|
106
118
|
this.x += (this.originX - this.x) * this.ease;
|
|
107
119
|
this.y += (this.originY - this.y) * this.ease;
|
|
108
120
|
}
|
|
109
121
|
|
|
110
122
|
draw() {
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
ctx.fillStyle = this.color;
|
|
124
|
+
ctx.fillRect(this.x, this.y, this.size, this.size);
|
|
113
125
|
}
|
|
114
126
|
}
|
|
115
|
-
|
|
127
|
+
|
|
128
|
+
// ---------- Text ----------
|
|
116
129
|
function drawText() {
|
|
117
|
-
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
118
|
-
|
|
130
|
+
ctx.clearRect(0, 0, canvasWidth(), canvasHeight());
|
|
131
|
+
|
|
119
132
|
const activeFontSize = isMobile ? fontSizeMobile : fontSize;
|
|
120
|
-
ctx.font = `italic bold ${activeFontSize}px
|
|
133
|
+
ctx.font = `italic bold ${activeFontSize}px Arial Black, sans-serif`;
|
|
121
134
|
ctx.textAlign = "center";
|
|
122
135
|
ctx.textBaseline = "middle";
|
|
136
|
+
|
|
123
137
|
if (gradient && colorOne && colorTwo && colorThree) {
|
|
124
|
-
const grad = ctx.createLinearGradient(
|
|
138
|
+
const grad = ctx.createLinearGradient(
|
|
139
|
+
0,
|
|
140
|
+
0,
|
|
141
|
+
canvasWidth(),
|
|
142
|
+
canvasHeight(),
|
|
143
|
+
);
|
|
125
144
|
grad.addColorStop(0.3, colorOne);
|
|
126
145
|
grad.addColorStop(0.5, colorTwo);
|
|
127
146
|
grad.addColorStop(0.8, colorThree);
|
|
128
147
|
ctx.fillStyle = grad;
|
|
129
148
|
} else {
|
|
130
|
-
ctx.fillStyle = textColor
|
|
149
|
+
ctx.fillStyle = textColor;
|
|
131
150
|
}
|
|
132
|
-
|
|
151
|
+
|
|
152
|
+
ctx.fillText(text, canvasWidth() / 2, canvasHeight() / 2);
|
|
133
153
|
}
|
|
154
|
+
|
|
134
155
|
function createParticlesFromText() {
|
|
135
|
-
mouse.radius = effectRadius
|
|
136
|
-
if (isMobile && mouse.radius > 100) {
|
|
137
|
-
mouse.radius = 100;
|
|
138
|
-
}
|
|
156
|
+
mouse.radius = isMobile ? Math.min(effectRadius, 100) : effectRadius;
|
|
139
157
|
particles.length = 0;
|
|
140
|
-
|
|
158
|
+
|
|
141
159
|
drawText();
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
if (
|
|
150
|
-
const r =
|
|
151
|
-
const g =
|
|
152
|
-
const b =
|
|
153
|
-
|
|
154
|
-
particles.push(new Particle(ctx, x, y, color, startMode));
|
|
160
|
+
|
|
161
|
+
const imageData = ctx.getImageData(0, 0, canvasWidth(), canvasHeight());
|
|
162
|
+
ctx.clearRect(0, 0, canvasWidth(), canvasHeight());
|
|
163
|
+
|
|
164
|
+
for (let y = 0; y < canvasHeight(); y += gap) {
|
|
165
|
+
for (let x = 0; x < canvasWidth(); x += gap) {
|
|
166
|
+
const i = (y * canvasWidth() + x) * 4;
|
|
167
|
+
if (imageData.data[i + 3] > 0) {
|
|
168
|
+
const r = imageData.data[i];
|
|
169
|
+
const g = imageData.data[i + 1];
|
|
170
|
+
const b = imageData.data[i + 2];
|
|
171
|
+
particles.push(new Particle(x, y, `rgb(${r},${g},${b})`));
|
|
155
172
|
}
|
|
156
173
|
}
|
|
157
174
|
}
|
|
158
175
|
}
|
|
159
|
-
|
|
176
|
+
|
|
160
177
|
function animate() {
|
|
161
178
|
if (destroyed) return;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
179
|
+
|
|
180
|
+
ctx.clearRect(0, 0, canvasWidth(), canvasHeight());
|
|
181
|
+
particles.forEach((p) => {
|
|
182
|
+
p.update();
|
|
183
|
+
p.draw();
|
|
166
184
|
});
|
|
167
185
|
|
|
168
186
|
animationId = requestAnimationFrame(animate);
|
|
169
187
|
}
|
|
170
|
-
|
|
171
|
-
function getStartPosition(mode,
|
|
188
|
+
|
|
189
|
+
function getStartPosition(mode, ox, oy) {
|
|
172
190
|
switch (mode) {
|
|
173
191
|
case "center":
|
|
174
|
-
return {
|
|
175
|
-
x: canvasWidth / 2,
|
|
176
|
-
y: canvasHeight / 2,
|
|
177
|
-
};
|
|
192
|
+
return { x: canvasWidth() / 2, y: canvasHeight() / 2 };
|
|
178
193
|
case "bottom":
|
|
179
|
-
return {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
};
|
|
183
|
-
|
|
194
|
+
return { x: ox, y: canvasHeight() + 20 };
|
|
195
|
+
case "top":
|
|
196
|
+
return { x: ox, y: -20 };
|
|
184
197
|
case "left":
|
|
185
|
-
return {
|
|
186
|
-
x: -20,
|
|
187
|
-
y: originY,
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
case "random":
|
|
198
|
+
return { x: -20, y: oy };
|
|
191
199
|
default:
|
|
192
200
|
return {
|
|
193
|
-
x: Math.random() * canvasWidth,
|
|
194
|
-
y: Math.random() * canvasHeight,
|
|
201
|
+
x: Math.random() * canvasWidth(),
|
|
202
|
+
y: Math.random() * canvasHeight(),
|
|
195
203
|
};
|
|
196
204
|
}
|
|
197
205
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
mouse.x = e.clientX - rect.left;
|
|
202
|
-
mouse.y = e.clientY - rect.top;
|
|
203
|
-
}
|
|
204
|
-
function onMouseLeave() {
|
|
205
|
-
mouse.x = null;
|
|
206
|
-
mouse.y = null;
|
|
207
|
-
}
|
|
208
|
-
function onTouchStart(e) {
|
|
209
|
-
const rect = canvas.getBoundingClientRect();
|
|
210
|
-
const touch = e.touches[0];
|
|
211
|
-
mouse.x = touch.clientX - rect.left;
|
|
212
|
-
mouse.y = touch.clientY - rect.top;
|
|
213
|
-
}
|
|
214
|
-
function onTouchMove(e) {
|
|
206
|
+
|
|
207
|
+
// ---------- Events ----------
|
|
208
|
+
function updateMouse(e, touch = false) {
|
|
215
209
|
const rect = canvas.getBoundingClientRect();
|
|
216
|
-
const
|
|
217
|
-
mouse.x =
|
|
218
|
-
mouse.y =
|
|
210
|
+
const p = touch ? e.touches[0] : e;
|
|
211
|
+
mouse.x = p.clientX - rect.left;
|
|
212
|
+
mouse.y = p.clientY - rect.top;
|
|
219
213
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
mouse.y = null;
|
|
214
|
+
|
|
215
|
+
function resetMouse() {
|
|
216
|
+
mouse.x = mouse.y = null;
|
|
223
217
|
}
|
|
224
218
|
|
|
225
|
-
canvas.addEventListener("mousemove",
|
|
226
|
-
canvas.addEventListener("mouseleave",
|
|
227
|
-
canvas.addEventListener("touchstart",
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
219
|
+
canvas.addEventListener("mousemove", updateMouse);
|
|
220
|
+
canvas.addEventListener("mouseleave", resetMouse);
|
|
221
|
+
canvas.addEventListener("touchstart", (e) => updateMouse(e, true), {
|
|
222
|
+
passive: true,
|
|
223
|
+
});
|
|
224
|
+
canvas.addEventListener("touchmove", (e) => updateMouse(e, true), {
|
|
225
|
+
passive: true,
|
|
226
|
+
});
|
|
227
|
+
canvas.addEventListener("touchend", resetMouse);
|
|
228
|
+
|
|
229
|
+
// ---------- Destroy ----------
|
|
231
230
|
function destroy() {
|
|
232
231
|
if (destroyed) return;
|
|
233
232
|
destroyed = true;
|
|
234
|
-
cancelAnimationFrame(animationId);
|
|
235
|
-
|
|
236
|
-
canvas.removeEventListener("mousemove", onMouseMove);
|
|
237
|
-
canvas.removeEventListener("mouseleave", onMouseLeave);
|
|
238
|
-
canvas.removeEventListener("touchstart", onTouchStart);
|
|
239
|
-
canvas.removeEventListener("touchmove", onTouchMove);
|
|
240
|
-
canvas.removeEventListener("touchend", onTouchEnd);
|
|
241
233
|
|
|
234
|
+
cancelAnimationFrame(animationId);
|
|
242
235
|
canvas.remove();
|
|
243
236
|
particles.length = 0;
|
|
237
|
+
|
|
238
|
+
delete element._magicTextInstance;
|
|
244
239
|
}
|
|
245
|
-
|
|
240
|
+
|
|
241
|
+
// ---------- Init ----------
|
|
246
242
|
createParticlesFromText();
|
|
247
243
|
animate();
|
|
248
|
-
|
|
244
|
+
|
|
245
|
+
element._magicTextInstance = { destroy };
|
|
246
|
+
return element._magicTextInstance;
|
|
249
247
|
}
|
|
250
248
|
export { initializeText };
|