@evqovs/danmaku 1.0.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 +15 -0
- package/README.md +113 -0
- package/dist/collision_detection.d.ts +2 -0
- package/dist/danmaku.js +635 -0
- package/dist/danmaku_container.d.ts +13 -0
- package/dist/danmaku_engine.d.ts +35 -0
- package/dist/danmaku_item.d.ts +47 -0
- package/dist/danmaku_manager.d.ts +28 -0
- package/dist/danmaku_track.d.ts +14 -0
- package/dist/index.d.ts +5 -0
- package/dist/lifecycle.d.ts +50 -0
- package/dist/utils.d.ts +2 -0
- package/package.json +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 evqovv
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
12
|
+
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
|
15
|
+
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Danmaku
|
|
2
|
+
|
|
3
|
+
A lightweight ESM-based danmaku library.
|
|
4
|
+
|
|
5
|
+
## Usage:
|
|
6
|
+
|
|
7
|
+
* ### Step 1:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const container: HTMLDivElement | null = document.querySelector('#danmaku-container');
|
|
11
|
+
if (!container) {
|
|
12
|
+
throw new Error("Danmaku container doesn't exist.");
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
In the first place, you always need to get an element as the danmaku container (most often a `<div>`) using `document.getElementById()` or `document.querySelector()`.
|
|
17
|
+
|
|
18
|
+
* ### Step 2:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
const manager = new DanmakuManager(info);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Create a `DanmakuManager` object.
|
|
25
|
+
|
|
26
|
+
This is the parameter type for this `DanmakuManager` constructor:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
interface ManagerOptions {
|
|
30
|
+
min_vertical_gap: number;
|
|
31
|
+
min_horizontal_gap: number;
|
|
32
|
+
track_height: number;
|
|
33
|
+
max_launch_count_per_tick: number;
|
|
34
|
+
max_row_count: number;
|
|
35
|
+
alignment: DanmakuAlignment;
|
|
36
|
+
interval: number;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
* min_vertical_gap: It determines the minimum vertical spacing of danmaku rows.
|
|
41
|
+
|
|
42
|
+
* min_horizontal_gap: It determines the minimum horizontal spacing of danmakus (e.g. the spacing between the previous danmaku and the subsequent one).
|
|
43
|
+
|
|
44
|
+
* track_height: It determines the height of one danmaku row.
|
|
45
|
+
|
|
46
|
+
* max_launch_count_per_tick: The maximum number of launching danmakus per tick.
|
|
47
|
+
|
|
48
|
+
* max_row_count: It determines the maximum number of danmaku rows.
|
|
49
|
+
|
|
50
|
+
* alignment: It determines the position where the content of a danmaku should be placed.
|
|
51
|
+
|
|
52
|
+
* top: The content will be placed at the top of the row.
|
|
53
|
+
|
|
54
|
+
* center: The content will be placed at the middle of the row.
|
|
55
|
+
|
|
56
|
+
* bottom: The content will be placed at the bottom of the row.
|
|
57
|
+
|
|
58
|
+
* interval: Interval time for each rendering (e.g. each tick).
|
|
59
|
+
|
|
60
|
+
* ### Step 3:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
manager.mount(container);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Calling mount() method to mount the danmaku system to the container that we got before.
|
|
67
|
+
|
|
68
|
+
* ### Step 4:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
manager.push(danmakus);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`push()` accepts an array whose type is `DanmakuOptions[]` from which each of elements describe a danmaku's behavior.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
interface DanmakuOptions {
|
|
78
|
+
duration: number;
|
|
79
|
+
style: Partial<CSSStyleDeclaration>;
|
|
80
|
+
loop: boolean;
|
|
81
|
+
direction: DanmakuDirection;
|
|
82
|
+
node: Node[];
|
|
83
|
+
clone_node: boolean;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
* duration: It specifies the duration of the danmaku from one side to the other.
|
|
88
|
+
|
|
89
|
+
* style: You can specifies the style of this container which is a wrapper containing all content you gave of this danmaku.
|
|
90
|
+
|
|
91
|
+
* loop: After playing done, drop its information or keep it for the next playing.
|
|
92
|
+
|
|
93
|
+
* true: Keep it for the next playing.
|
|
94
|
+
|
|
95
|
+
* false: Drop it.
|
|
96
|
+
|
|
97
|
+
* direction: It specifies the horizontal movement direction of this danmaku.
|
|
98
|
+
|
|
99
|
+
* node: All content you want to be wrapped.
|
|
100
|
+
|
|
101
|
+
* clone_node: Copy the nodes you provided or not.
|
|
102
|
+
|
|
103
|
+
* ### Step 5:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
manager.start_render();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
It starts rendering danmaku.
|
|
110
|
+
|
|
111
|
+
* ### Something else:
|
|
112
|
+
|
|
113
|
+
If you want a promise which will be settled when all danmakus you provided have been done with rendering, call `manager.manager.until_all_done()` which returns a promise that will be settled when all done with rendering.
|
package/dist/danmaku.js
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
class DanmakuTrack {
|
|
2
|
+
top;
|
|
3
|
+
height;
|
|
4
|
+
items = [];
|
|
5
|
+
bottom = 0;
|
|
6
|
+
constructor(top, height) {
|
|
7
|
+
this.top = top;
|
|
8
|
+
this.height = height;
|
|
9
|
+
this.bottom = top + height;
|
|
10
|
+
}
|
|
11
|
+
attach(d, alignment) {
|
|
12
|
+
this.items.push(d);
|
|
13
|
+
d.set_style('top', this.compute_position(d, alignment));
|
|
14
|
+
}
|
|
15
|
+
detach(d) {
|
|
16
|
+
this.items = this.items.filter(item => item !== d);
|
|
17
|
+
}
|
|
18
|
+
peek_last() {
|
|
19
|
+
return this.items.length !== 0 ? this.items[this.items.length - 1] : null;
|
|
20
|
+
}
|
|
21
|
+
cancel() {
|
|
22
|
+
for (const d of [...this.items]) {
|
|
23
|
+
d.plugin_system.lifecycle.end.emit(d);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
compute_position(d, alignment) {
|
|
27
|
+
let pos = 0;
|
|
28
|
+
switch (alignment) {
|
|
29
|
+
case 'top':
|
|
30
|
+
pos = this.top;
|
|
31
|
+
break;
|
|
32
|
+
case 'center':
|
|
33
|
+
pos = this.top + (this.height - d.height) / 2;
|
|
34
|
+
break;
|
|
35
|
+
case 'bottom':
|
|
36
|
+
pos = this.top + this.height - d.height;
|
|
37
|
+
break;
|
|
38
|
+
default: throw new Error('Unexpected alignment.');
|
|
39
|
+
}
|
|
40
|
+
return `${pos}px`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class DanmakuContainer {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.node = document.createElement("div");
|
|
47
|
+
this.apply_style({
|
|
48
|
+
"overflow": "hidden",
|
|
49
|
+
"position": "relative",
|
|
50
|
+
"top": "0",
|
|
51
|
+
"left": "0",
|
|
52
|
+
"width": "100%",
|
|
53
|
+
"height": "100%",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
apply_style(style) {
|
|
57
|
+
Object.assign(this.node.style, style);
|
|
58
|
+
}
|
|
59
|
+
mount(node) {
|
|
60
|
+
if (this.is_mounted()) {
|
|
61
|
+
throw new Error("Container already mounted. Unmount before remounting.");
|
|
62
|
+
}
|
|
63
|
+
node.appendChild(this.node);
|
|
64
|
+
this.parent_node = node;
|
|
65
|
+
this.format();
|
|
66
|
+
}
|
|
67
|
+
unmount() {
|
|
68
|
+
if (!this.is_mounted()) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.node.parentNode.removeChild(this.node);
|
|
72
|
+
this.parent_node = null;
|
|
73
|
+
}
|
|
74
|
+
is_mounted() {
|
|
75
|
+
return this.parent_node !== null;
|
|
76
|
+
}
|
|
77
|
+
resize() {
|
|
78
|
+
this.format();
|
|
79
|
+
}
|
|
80
|
+
format() {
|
|
81
|
+
const rect = this.node.getBoundingClientRect();
|
|
82
|
+
Object.assign(this, {
|
|
83
|
+
width: rect.width,
|
|
84
|
+
height: rect.height,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
node;
|
|
88
|
+
parent_node = null;
|
|
89
|
+
width = 0;
|
|
90
|
+
height = 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function collision_detection_impl(prev, cur, min_gap) {
|
|
94
|
+
const pv = prev.speed;
|
|
95
|
+
const cv = cur.speed;
|
|
96
|
+
const dv = cv - pv;
|
|
97
|
+
const moved = prev.get_moved_distance();
|
|
98
|
+
const gap = moved - prev.width;
|
|
99
|
+
if (gap < 0) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (pv >= cv) {
|
|
103
|
+
return gap >= min_gap;
|
|
104
|
+
}
|
|
105
|
+
const remain_dis = prev.container_width - gap + min_gap;
|
|
106
|
+
const remain_time = remain_dis / pv;
|
|
107
|
+
const catchup_time = (gap - min_gap) / dv;
|
|
108
|
+
return catchup_time > remain_time;
|
|
109
|
+
}
|
|
110
|
+
function collision_detection(prev, cur, min_gap) {
|
|
111
|
+
return collision_detection_impl(prev, cur, min_gap);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
class SyncHook {
|
|
115
|
+
callbacks = [];
|
|
116
|
+
tap(cb) {
|
|
117
|
+
this.callbacks.push(cb);
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
untap(target) {
|
|
121
|
+
this.callbacks = this.callbacks.filter(cb => cb !== target);
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
emit(...args) {
|
|
125
|
+
[...this.callbacks].forEach(cb => cb(...args));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
class PluginSystem {
|
|
129
|
+
lifecycle;
|
|
130
|
+
constructor(lifecycle) {
|
|
131
|
+
this.lifecycle = lifecycle;
|
|
132
|
+
}
|
|
133
|
+
use(plugin) {
|
|
134
|
+
for (const key in plugin) {
|
|
135
|
+
const hook = this.lifecycle[key];
|
|
136
|
+
const fn = plugin[key];
|
|
137
|
+
if (hook && fn) {
|
|
138
|
+
hook.tap(fn);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function create_danmaku_plugin_system() {
|
|
144
|
+
return new PluginSystem({
|
|
145
|
+
start: new SyncHook,
|
|
146
|
+
end: new SyncHook,
|
|
147
|
+
pause: new SyncHook,
|
|
148
|
+
resume: new SyncHook,
|
|
149
|
+
cancel: new SyncHook,
|
|
150
|
+
mount: new SyncHook,
|
|
151
|
+
unmount: new SyncHook,
|
|
152
|
+
format: new SyncHook,
|
|
153
|
+
resize: new SyncHook,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function create_manager_plugin_system() {
|
|
157
|
+
return new PluginSystem({
|
|
158
|
+
$start: new SyncHook,
|
|
159
|
+
$end: new SyncHook,
|
|
160
|
+
$pause: new SyncHook,
|
|
161
|
+
$resume: new SyncHook,
|
|
162
|
+
$cancel: new SyncHook,
|
|
163
|
+
$mount: new SyncHook,
|
|
164
|
+
$unmount: new SyncHook,
|
|
165
|
+
$format: new SyncHook,
|
|
166
|
+
$resize: new SyncHook,
|
|
167
|
+
start: new SyncHook,
|
|
168
|
+
stop: new SyncHook,
|
|
169
|
+
cancel: new SyncHook,
|
|
170
|
+
freeze: new SyncHook,
|
|
171
|
+
unfreeze: new SyncHook,
|
|
172
|
+
mount: new SyncHook,
|
|
173
|
+
unmount: new SyncHook,
|
|
174
|
+
format: new SyncHook,
|
|
175
|
+
resize: new SyncHook,
|
|
176
|
+
finish: new SyncHook,
|
|
177
|
+
screen_empty: new SyncHook,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function create_danmaku_bridge_plugin(m) {
|
|
181
|
+
const plugin = {};
|
|
182
|
+
for (const key of Object.keys(m.lifecycle)) {
|
|
183
|
+
if (key.startsWith('$')) {
|
|
184
|
+
const short_name = key.slice(1);
|
|
185
|
+
plugin[short_name] = (...args) => {
|
|
186
|
+
m.lifecycle[key].emit(...args);
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return plugin;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const now = () => performance.now();
|
|
194
|
+
const next_frame = requestAnimationFrame;
|
|
195
|
+
|
|
196
|
+
class DanmakuItem {
|
|
197
|
+
options;
|
|
198
|
+
constructor(options) {
|
|
199
|
+
this.options = options;
|
|
200
|
+
this.apply_style(DanmakuItem.init_style);
|
|
201
|
+
this.apply_style(options.style);
|
|
202
|
+
this.duration = options.duration;
|
|
203
|
+
this.direction = options.direction;
|
|
204
|
+
for (const node of options.node) {
|
|
205
|
+
this.node.appendChild(options.clone_node ? node.cloneNode(true) : node);
|
|
206
|
+
}
|
|
207
|
+
this.node.addEventListener('transitionend', () => this.plugin_system.lifecycle.end.emit(this));
|
|
208
|
+
}
|
|
209
|
+
play() {
|
|
210
|
+
if (this.status !== 'idle') {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
this.recorder.start_time = now();
|
|
214
|
+
this.set_style('transition', `transform ${this.duration}ms linear`);
|
|
215
|
+
this.set_style('transform', `translateX(${this.destination}px)`);
|
|
216
|
+
this.status = 'running';
|
|
217
|
+
this.plugin_system.lifecycle.start.emit(this);
|
|
218
|
+
}
|
|
219
|
+
pause() {
|
|
220
|
+
if (this.status !== 'running') {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.recorder.last_paused_at = now();
|
|
224
|
+
const is_to_left = this.direction === 'to_left';
|
|
225
|
+
const negative = is_to_left ? -1 : 1;
|
|
226
|
+
const moved_dis = this.get_moved_distance();
|
|
227
|
+
this.set_style('transitionDuration', '0ms');
|
|
228
|
+
this.set_style('transform', `translateX(${negative * moved_dis}px)`);
|
|
229
|
+
this.status = 'paused';
|
|
230
|
+
this.plugin_system.lifecycle.pause.emit(this);
|
|
231
|
+
}
|
|
232
|
+
resume() {
|
|
233
|
+
if (this.status !== 'paused') {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
this.recorder.paused_duration += now() - this.recorder.last_paused_at;
|
|
237
|
+
const remaining_time = this.duration - this.get_elapsed_time();
|
|
238
|
+
this.recorder.last_paused_at = 0;
|
|
239
|
+
this.set_style('transitionDuration', `${remaining_time}ms`);
|
|
240
|
+
this.set_style('transform', `translateX(${this.destination}px)`);
|
|
241
|
+
this.status = 'running';
|
|
242
|
+
this.plugin_system.lifecycle.resume.emit(this);
|
|
243
|
+
}
|
|
244
|
+
cancel() {
|
|
245
|
+
if (this.status === 'idle') {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
this.set_style('transition', '');
|
|
249
|
+
this.set_style('transform', '');
|
|
250
|
+
Object.assign(this.recorder, {
|
|
251
|
+
start_time: 0,
|
|
252
|
+
last_paused_at: 0,
|
|
253
|
+
paused_duration: 0,
|
|
254
|
+
});
|
|
255
|
+
this.status = 'idle';
|
|
256
|
+
this.plugin_system.lifecycle.cancel.emit(this);
|
|
257
|
+
}
|
|
258
|
+
resize() {
|
|
259
|
+
if (!this.container_node) {
|
|
260
|
+
throw new Error('This item is not mounted yet.');
|
|
261
|
+
}
|
|
262
|
+
const is_to_left = this.direction === 'to_left';
|
|
263
|
+
const negative = is_to_left ? -1 : 1;
|
|
264
|
+
const { width: cw, height: ch } = this.container_node.getBoundingClientRect();
|
|
265
|
+
const et = this.get_elapsed_time();
|
|
266
|
+
const total_dis = cw + this.width;
|
|
267
|
+
const prog = et / this.duration;
|
|
268
|
+
const new_x = negative * total_dis * prog;
|
|
269
|
+
const remaining_time = this.duration - et;
|
|
270
|
+
const dest = negative * total_dis;
|
|
271
|
+
this.set_style('transitionDuration', '0ms');
|
|
272
|
+
this.set_style('transform', `translateX(${new_x}px)`);
|
|
273
|
+
if (this.status === 'running') {
|
|
274
|
+
next_frame(() => {
|
|
275
|
+
this.set_style('transitionDuration', `${remaining_time}ms`);
|
|
276
|
+
this.set_style('transform', `translateX(${dest}px)`);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
Object.assign(this, {
|
|
280
|
+
container_width: cw,
|
|
281
|
+
container_height: ch,
|
|
282
|
+
destination: dest,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
get_elapsed_time() {
|
|
286
|
+
switch (this.status) {
|
|
287
|
+
case 'idle': return 0;
|
|
288
|
+
case 'running': return now() - this.recorder.start_time - this.recorder.paused_duration;
|
|
289
|
+
case 'paused': return this.recorder.last_paused_at - this.recorder.start_time - this.recorder.paused_duration;
|
|
290
|
+
default: throw new Error('Unexpected status.');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
get_moved_distance() {
|
|
294
|
+
return this.get_elapsed_time() * this.speed;
|
|
295
|
+
}
|
|
296
|
+
is_mounted() {
|
|
297
|
+
return this.container_node !== null;
|
|
298
|
+
}
|
|
299
|
+
mount(node) {
|
|
300
|
+
this.unmount();
|
|
301
|
+
this.container_node = node;
|
|
302
|
+
this.container_node.appendChild(this.node);
|
|
303
|
+
this.format();
|
|
304
|
+
}
|
|
305
|
+
unmount() {
|
|
306
|
+
if (this.node.parentNode) {
|
|
307
|
+
this.node.parentNode.removeChild(this.node);
|
|
308
|
+
}
|
|
309
|
+
this.container_node = null;
|
|
310
|
+
}
|
|
311
|
+
set_style(key, value) {
|
|
312
|
+
this.node.style[key] = value;
|
|
313
|
+
}
|
|
314
|
+
apply_style(style) {
|
|
315
|
+
Object.assign(this.node.style, style);
|
|
316
|
+
}
|
|
317
|
+
attach(track, alignment) {
|
|
318
|
+
if (this.track !== null) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
track.attach(this, alignment);
|
|
322
|
+
}
|
|
323
|
+
detach() {
|
|
324
|
+
if (this.track !== null) {
|
|
325
|
+
this.track.detach(this);
|
|
326
|
+
this.track = null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
format() {
|
|
330
|
+
if (!this.container_node) {
|
|
331
|
+
throw new Error('This item is not mounted yet.');
|
|
332
|
+
}
|
|
333
|
+
const { width: w, height: h } = this.node.getBoundingClientRect();
|
|
334
|
+
const { width: cw, height: ch } = this.container_node.getBoundingClientRect();
|
|
335
|
+
const is_to_left = this.direction === 'to_left';
|
|
336
|
+
const negative = is_to_left ? -1 : 1;
|
|
337
|
+
Object.assign(this, {
|
|
338
|
+
width: w,
|
|
339
|
+
height: h,
|
|
340
|
+
container_width: cw,
|
|
341
|
+
container_height: ch,
|
|
342
|
+
total_distance: cw + w,
|
|
343
|
+
speed: (cw + w) / this.duration,
|
|
344
|
+
destination: negative * (cw + w),
|
|
345
|
+
});
|
|
346
|
+
this.set_style(is_to_left ? 'right' : 'left', `${-this.width}px`);
|
|
347
|
+
}
|
|
348
|
+
width = 0;
|
|
349
|
+
height = 0;
|
|
350
|
+
plugin_system = create_danmaku_plugin_system();
|
|
351
|
+
container_width = 0;
|
|
352
|
+
container_height = 0;
|
|
353
|
+
duration = 0;
|
|
354
|
+
speed = 0;
|
|
355
|
+
direction;
|
|
356
|
+
destination = 0;
|
|
357
|
+
status = 'idle';
|
|
358
|
+
node = document.createElement('div');
|
|
359
|
+
container_node = null;
|
|
360
|
+
track = null;
|
|
361
|
+
recorder = {
|
|
362
|
+
start_time: 0,
|
|
363
|
+
last_paused_at: 0,
|
|
364
|
+
paused_duration: 0,
|
|
365
|
+
};
|
|
366
|
+
static init_style = {
|
|
367
|
+
'position': 'absolute',
|
|
368
|
+
'whiteSpace': 'nowrap',
|
|
369
|
+
'display': 'flex',
|
|
370
|
+
'alignItems': 'center',
|
|
371
|
+
'willChange': 'transform',
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
class DanmakuEngine {
|
|
376
|
+
options;
|
|
377
|
+
container = new DanmakuContainer();
|
|
378
|
+
tracks = [];
|
|
379
|
+
stash = [];
|
|
380
|
+
set = new Set;
|
|
381
|
+
constructor(options) {
|
|
382
|
+
this.options = options;
|
|
383
|
+
}
|
|
384
|
+
format() {
|
|
385
|
+
this.format_tracks();
|
|
386
|
+
}
|
|
387
|
+
add(info) {
|
|
388
|
+
this.stash.push(...info);
|
|
389
|
+
}
|
|
390
|
+
each(fn) {
|
|
391
|
+
for (const d of this.set) {
|
|
392
|
+
fn(d);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
clear_stash() {
|
|
396
|
+
this.stash.length = 0;
|
|
397
|
+
}
|
|
398
|
+
create_and_mount_danmaku(info) {
|
|
399
|
+
const d = new DanmakuItem(info);
|
|
400
|
+
d.mount(this.container.node);
|
|
401
|
+
return d;
|
|
402
|
+
}
|
|
403
|
+
render(bridge_plugin, hooks) {
|
|
404
|
+
if (this.tracks.length === 0 || this.stash.length === 0) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const attempts = Math.min(this.stash.length, this.options.max_launch_count_per_tick, this.tracks.length);
|
|
408
|
+
for (let i = 0; i < attempts; i++) {
|
|
409
|
+
const info = this.stash.shift();
|
|
410
|
+
if (!info) {
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
const d = this.create_and_mount_danmaku(info);
|
|
414
|
+
if (d.height > this.options.track_height) {
|
|
415
|
+
d.unmount();
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
const track = this.get_launchable_track(d);
|
|
419
|
+
if (!track) {
|
|
420
|
+
d.unmount();
|
|
421
|
+
this.stash.unshift(info);
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const cleanup = () => {
|
|
425
|
+
d.detach();
|
|
426
|
+
this.set.delete(d);
|
|
427
|
+
d.unmount();
|
|
428
|
+
};
|
|
429
|
+
const is_screen_empty = () => {
|
|
430
|
+
if (this.set.size === 0) {
|
|
431
|
+
hooks.screen_empty.emit();
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
const is_finished = () => {
|
|
435
|
+
if (this.set.size === 0 && this.stash.length === 0) {
|
|
436
|
+
hooks.finish.emit();
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
d.plugin_system.use(bridge_plugin);
|
|
440
|
+
d.plugin_system.use({
|
|
441
|
+
end: () => {
|
|
442
|
+
cleanup();
|
|
443
|
+
is_screen_empty();
|
|
444
|
+
is_finished();
|
|
445
|
+
},
|
|
446
|
+
cancel: () => {
|
|
447
|
+
cleanup();
|
|
448
|
+
is_screen_empty();
|
|
449
|
+
is_finished();
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
d.attach(track, this.options.alignment);
|
|
453
|
+
d.play();
|
|
454
|
+
this.set.add(d);
|
|
455
|
+
if (info.loop) {
|
|
456
|
+
this.stash.push(info);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
resize() {
|
|
461
|
+
this.each(d => d.resize());
|
|
462
|
+
const old_h = this.container.height;
|
|
463
|
+
this.container.resize();
|
|
464
|
+
this.resize_tracks(old_h);
|
|
465
|
+
}
|
|
466
|
+
get_launchable_track(cur) {
|
|
467
|
+
for (const track of this.tracks) {
|
|
468
|
+
const prev = track.peek_last();
|
|
469
|
+
if (!prev ||
|
|
470
|
+
collision_detection(prev, cur, this.options.min_horizontal_gap)) {
|
|
471
|
+
return track;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
format_tracks() {
|
|
477
|
+
const { max_row_count, track_height, min_vertical_gap } = this.options;
|
|
478
|
+
const { height } = this.container;
|
|
479
|
+
const rows = Math.min(max_row_count, Math.floor((height + min_vertical_gap) / (track_height + min_vertical_gap)));
|
|
480
|
+
const row_step = track_height + min_vertical_gap;
|
|
481
|
+
const total_height = (rows - 1) * row_step + track_height;
|
|
482
|
+
const remaining_gap = Math.max(0, height - total_height);
|
|
483
|
+
const start_y = remaining_gap / 2;
|
|
484
|
+
for (let i = 0; i < rows; i++) {
|
|
485
|
+
this.tracks.push(new DanmakuTrack(start_y + i * row_step, this.options.track_height));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
resize_tracks(old_h) {
|
|
489
|
+
const new_h = this.container.height;
|
|
490
|
+
if (new_h === old_h) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const is_shrinking = new_h < old_h;
|
|
494
|
+
is_shrinking ? this.shrink_tracks(new_h) : this.expand_tracks(new_h);
|
|
495
|
+
}
|
|
496
|
+
shrink_tracks(limit) {
|
|
497
|
+
for (let i = this.tracks.length - 1; i >= 0; i--) {
|
|
498
|
+
const t = this.tracks[i];
|
|
499
|
+
if (t.bottom > limit) {
|
|
500
|
+
t.cancel();
|
|
501
|
+
this.tracks.splice(i, 1);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
expand_tracks(limit) {
|
|
506
|
+
if (this.tracks.length === 0) {
|
|
507
|
+
this.format_tracks();
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const last = this.tracks[this.tracks.length - 1];
|
|
511
|
+
let top = last.bottom + this.options.min_vertical_gap;
|
|
512
|
+
while (top + this.options.track_height <= limit) {
|
|
513
|
+
this.tracks.push(new DanmakuTrack(top, this.options.track_height));
|
|
514
|
+
top += this.options.track_height + this.options.min_vertical_gap;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
class DanmakuManager {
|
|
520
|
+
options;
|
|
521
|
+
plugin_system = create_manager_plugin_system();
|
|
522
|
+
status = 'idle';
|
|
523
|
+
engine;
|
|
524
|
+
render_timer = null;
|
|
525
|
+
constructor(options) {
|
|
526
|
+
this.options = options;
|
|
527
|
+
this.engine = new DanmakuEngine(options);
|
|
528
|
+
}
|
|
529
|
+
push(info) {
|
|
530
|
+
this.engine.add(info);
|
|
531
|
+
}
|
|
532
|
+
until_all_done() {
|
|
533
|
+
return new Promise(res => this.plugin_system.lifecycle.finish.tap(() => res()));
|
|
534
|
+
}
|
|
535
|
+
start_render() {
|
|
536
|
+
if (this.status !== 'idle') {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const cycle = () => {
|
|
540
|
+
this.render_timer = setTimeout(cycle, this.options.interval);
|
|
541
|
+
this.engine.render(create_danmaku_bridge_plugin(this.plugin_system), {
|
|
542
|
+
finish: this.plugin_system.lifecycle.finish,
|
|
543
|
+
screen_empty: this.plugin_system.lifecycle.screen_empty
|
|
544
|
+
});
|
|
545
|
+
};
|
|
546
|
+
cycle();
|
|
547
|
+
this.status = 'rendering';
|
|
548
|
+
this.plugin_system.lifecycle.start.emit();
|
|
549
|
+
}
|
|
550
|
+
stop_render() {
|
|
551
|
+
if (this.status !== 'rendering') {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (this.render_timer) {
|
|
555
|
+
clearTimeout(this.render_timer);
|
|
556
|
+
}
|
|
557
|
+
this.render_timer = null;
|
|
558
|
+
this.status = 'idle';
|
|
559
|
+
this.plugin_system.lifecycle.stop.emit();
|
|
560
|
+
}
|
|
561
|
+
cancel() {
|
|
562
|
+
this.stop_render();
|
|
563
|
+
this.engine.each(d => d.cancel());
|
|
564
|
+
this.engine.clear_stash();
|
|
565
|
+
this.status = 'idle';
|
|
566
|
+
this.plugin_system.lifecycle.cancel.emit();
|
|
567
|
+
}
|
|
568
|
+
graceful_stop() {
|
|
569
|
+
this.stop_render();
|
|
570
|
+
return new Promise(res => this.plugin_system.lifecycle.screen_empty.tap(() => {
|
|
571
|
+
res();
|
|
572
|
+
this.status = 'idle';
|
|
573
|
+
this.plugin_system.lifecycle.stop.emit();
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
graceful_cancel() {
|
|
577
|
+
this.stop_render();
|
|
578
|
+
this.engine.clear_stash();
|
|
579
|
+
return new Promise(res => this.plugin_system.lifecycle.screen_empty.tap(() => {
|
|
580
|
+
res();
|
|
581
|
+
this.status = 'idle';
|
|
582
|
+
this.plugin_system.lifecycle.cancel.emit();
|
|
583
|
+
}));
|
|
584
|
+
}
|
|
585
|
+
freeze() {
|
|
586
|
+
if (this.status !== 'rendering') {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
this.stop_render();
|
|
590
|
+
this.engine.each(d => d.pause());
|
|
591
|
+
this.status = 'frozen';
|
|
592
|
+
this.plugin_system.lifecycle.freeze.emit();
|
|
593
|
+
}
|
|
594
|
+
unfreeze() {
|
|
595
|
+
if (this.status !== 'frozen') {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
this.engine.each(d => d.resume());
|
|
599
|
+
this.start_render();
|
|
600
|
+
this.status = 'rendering';
|
|
601
|
+
this.plugin_system.lifecycle.unfreeze.emit();
|
|
602
|
+
}
|
|
603
|
+
mount(node) {
|
|
604
|
+
if (this.engine.container.is_mounted()) {
|
|
605
|
+
this.unmount();
|
|
606
|
+
}
|
|
607
|
+
this.engine.container.mount(node);
|
|
608
|
+
this.format();
|
|
609
|
+
this.plugin_system.lifecycle.mount.emit(node);
|
|
610
|
+
}
|
|
611
|
+
unmount() {
|
|
612
|
+
if (!this.engine.container.is_mounted()) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const p = this.engine.container.parent_node;
|
|
616
|
+
this.engine.container.unmount();
|
|
617
|
+
this.plugin_system.lifecycle.mount.emit(p);
|
|
618
|
+
}
|
|
619
|
+
resize() {
|
|
620
|
+
this.engine.resize();
|
|
621
|
+
this.plugin_system.lifecycle.resize.emit();
|
|
622
|
+
}
|
|
623
|
+
set_style(key, value) {
|
|
624
|
+
this.engine.each(d => d.set_style(key, value));
|
|
625
|
+
}
|
|
626
|
+
apply_style(style) {
|
|
627
|
+
this.engine.each(d => d.apply_style(style));
|
|
628
|
+
}
|
|
629
|
+
format() {
|
|
630
|
+
this.engine.format();
|
|
631
|
+
this.plugin_system.lifecycle.format.emit();
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export { DanmakuManager };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default class DanmakuContainer {
|
|
2
|
+
constructor();
|
|
3
|
+
apply_style(style: Partial<CSSStyleDeclaration>): void;
|
|
4
|
+
mount(node: HTMLElement): void;
|
|
5
|
+
unmount(): void;
|
|
6
|
+
is_mounted(): boolean;
|
|
7
|
+
resize(): void;
|
|
8
|
+
private format;
|
|
9
|
+
node: HTMLDivElement;
|
|
10
|
+
parent_node: HTMLElement | null;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import DanmakuContainer from "./danmaku_container";
|
|
2
|
+
import DanmakuItem, { DanmakuOptions } from "./danmaku_item";
|
|
3
|
+
import { create_danmaku_bridge_plugin, ManagerHooks } from "./lifecycle";
|
|
4
|
+
export type DanmakuAlignment = "top" | "center" | "bottom";
|
|
5
|
+
export interface EngineOptions {
|
|
6
|
+
min_vertical_gap: number;
|
|
7
|
+
min_horizontal_gap: number;
|
|
8
|
+
track_height: number;
|
|
9
|
+
max_launch_count_per_tick: number;
|
|
10
|
+
max_row_count: number;
|
|
11
|
+
alignment: DanmakuAlignment;
|
|
12
|
+
}
|
|
13
|
+
export default class DanmakuEngine {
|
|
14
|
+
private options;
|
|
15
|
+
container: DanmakuContainer;
|
|
16
|
+
private tracks;
|
|
17
|
+
private stash;
|
|
18
|
+
private set;
|
|
19
|
+
constructor(options: EngineOptions);
|
|
20
|
+
format(): void;
|
|
21
|
+
add(info: DanmakuOptions[]): void;
|
|
22
|
+
each(fn: (d: DanmakuItem) => void): void;
|
|
23
|
+
clear_stash(): void;
|
|
24
|
+
create_and_mount_danmaku(info: DanmakuOptions): DanmakuItem;
|
|
25
|
+
render(bridge_plugin: ReturnType<typeof create_danmaku_bridge_plugin>, hooks: {
|
|
26
|
+
finish: ManagerHooks['finish'];
|
|
27
|
+
screen_empty: ManagerHooks['screen_empty'];
|
|
28
|
+
}): void;
|
|
29
|
+
resize(): void;
|
|
30
|
+
private get_launchable_track;
|
|
31
|
+
private format_tracks;
|
|
32
|
+
private resize_tracks;
|
|
33
|
+
private shrink_tracks;
|
|
34
|
+
private expand_tracks;
|
|
35
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DanmakuAlignment } from "./danmaku_engine";
|
|
2
|
+
import DanmakuTrack from "./danmaku_track";
|
|
3
|
+
export interface DanmakuOptions {
|
|
4
|
+
duration: number;
|
|
5
|
+
style: Partial<CSSStyleDeclaration>;
|
|
6
|
+
loop: boolean;
|
|
7
|
+
direction: DanmakuDirection;
|
|
8
|
+
node: Node[];
|
|
9
|
+
clone_node: boolean;
|
|
10
|
+
}
|
|
11
|
+
export type DanmakuStatus = 'idle' | 'running' | 'paused';
|
|
12
|
+
export type DanmakuDirection = 'to_left' | 'to_right';
|
|
13
|
+
export type StyleKey = keyof Omit<CSSStyleDeclaration, 'length' | 'parentRule'>;
|
|
14
|
+
export default class DanmakuItem {
|
|
15
|
+
options: DanmakuOptions;
|
|
16
|
+
constructor(options: DanmakuOptions);
|
|
17
|
+
play(): void;
|
|
18
|
+
pause(): void;
|
|
19
|
+
resume(): void;
|
|
20
|
+
cancel(): void;
|
|
21
|
+
resize(): void;
|
|
22
|
+
get_elapsed_time(): number;
|
|
23
|
+
get_moved_distance(): number;
|
|
24
|
+
is_mounted(): boolean;
|
|
25
|
+
mount(node: HTMLElement): void;
|
|
26
|
+
unmount(): void;
|
|
27
|
+
set_style<T extends StyleKey>(key: T, value: CSSStyleDeclaration[T]): void;
|
|
28
|
+
apply_style(style: Partial<CSSStyleDeclaration>): void;
|
|
29
|
+
attach(track: DanmakuTrack, alignment: DanmakuAlignment): void;
|
|
30
|
+
detach(): void;
|
|
31
|
+
private format;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
plugin_system: import("./lifecycle").PluginSystem<import("./lifecycle").DanmakuHooks>;
|
|
35
|
+
container_width: number;
|
|
36
|
+
container_height: number;
|
|
37
|
+
duration: number;
|
|
38
|
+
speed: number;
|
|
39
|
+
direction: DanmakuDirection;
|
|
40
|
+
private destination;
|
|
41
|
+
private status;
|
|
42
|
+
private node;
|
|
43
|
+
private container_node;
|
|
44
|
+
private track;
|
|
45
|
+
private recorder;
|
|
46
|
+
private static readonly init_style;
|
|
47
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { EngineOptions } from "./danmaku_engine";
|
|
2
|
+
import { DanmakuOptions, StyleKey } from "./danmaku_item";
|
|
3
|
+
export interface ManagerOptions extends EngineOptions {
|
|
4
|
+
interval: number;
|
|
5
|
+
}
|
|
6
|
+
export default class DanmakuManager {
|
|
7
|
+
options: ManagerOptions;
|
|
8
|
+
plugin_system: import("./lifecycle").PluginSystem<import("./lifecycle").ManagerHooks>;
|
|
9
|
+
private status;
|
|
10
|
+
private engine;
|
|
11
|
+
private render_timer;
|
|
12
|
+
constructor(options: ManagerOptions);
|
|
13
|
+
push(info: DanmakuOptions[]): void;
|
|
14
|
+
until_all_done(): Promise<void>;
|
|
15
|
+
start_render(): void;
|
|
16
|
+
stop_render(): void;
|
|
17
|
+
cancel(): void;
|
|
18
|
+
graceful_stop(): Promise<void>;
|
|
19
|
+
graceful_cancel(): Promise<void>;
|
|
20
|
+
freeze(): void;
|
|
21
|
+
unfreeze(): void;
|
|
22
|
+
mount(node: HTMLElement): void;
|
|
23
|
+
unmount(): void;
|
|
24
|
+
resize(): void;
|
|
25
|
+
set_style<T extends StyleKey>(key: T, value: CSSStyleDeclaration[T]): void;
|
|
26
|
+
apply_style(style: Partial<CSSStyleDeclaration>): void;
|
|
27
|
+
private format;
|
|
28
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DanmakuAlignment } from "./danmaku_engine";
|
|
2
|
+
import DanmakuItem from "./danmaku_item";
|
|
3
|
+
export default class DanmakuTrack {
|
|
4
|
+
top: number;
|
|
5
|
+
height: number;
|
|
6
|
+
private items;
|
|
7
|
+
bottom: number;
|
|
8
|
+
constructor(top: number, height: number);
|
|
9
|
+
attach(d: DanmakuItem, alignment: DanmakuAlignment): void;
|
|
10
|
+
detach(d: DanmakuItem): void;
|
|
11
|
+
peek_last(): DanmakuItem | null;
|
|
12
|
+
cancel(): void;
|
|
13
|
+
private compute_position;
|
|
14
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { DanmakuAlignment } from "./danmaku_engine";
|
|
2
|
+
import { DanmakuDirection, DanmakuOptions } from "./danmaku_item";
|
|
3
|
+
import DanmakuManager, { ManagerOptions } from "./danmaku_manager";
|
|
4
|
+
export { DanmakuManager };
|
|
5
|
+
export type { DanmakuAlignment, DanmakuDirection, DanmakuOptions, ManagerOptions, };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import DanmakuItem from "./danmaku_item";
|
|
2
|
+
export declare class SyncHook<T extends any[] = []> {
|
|
3
|
+
private callbacks;
|
|
4
|
+
tap(cb: (...args: T) => void): this;
|
|
5
|
+
untap(target: (...args: T) => void): this;
|
|
6
|
+
emit(...args: T): void;
|
|
7
|
+
}
|
|
8
|
+
export declare class PluginSystem<T extends Record<string, SyncHook<any>>> {
|
|
9
|
+
lifecycle: T;
|
|
10
|
+
constructor(lifecycle: T);
|
|
11
|
+
use(plugin: {
|
|
12
|
+
[K in keyof T]?: T[K]['emit'];
|
|
13
|
+
}): void;
|
|
14
|
+
}
|
|
15
|
+
export type DanmakuHooks = {
|
|
16
|
+
start: SyncHook<[DanmakuItem]>;
|
|
17
|
+
end: SyncHook<[DanmakuItem]>;
|
|
18
|
+
pause: SyncHook<[DanmakuItem]>;
|
|
19
|
+
resume: SyncHook<[DanmakuItem]>;
|
|
20
|
+
cancel: SyncHook<[DanmakuItem]>;
|
|
21
|
+
mount: SyncHook<[DanmakuItem]>;
|
|
22
|
+
unmount: SyncHook<[DanmakuItem]>;
|
|
23
|
+
format: SyncHook<[DanmakuItem]>;
|
|
24
|
+
resize: SyncHook<[DanmakuItem]>;
|
|
25
|
+
};
|
|
26
|
+
export type ManagerHooks = {
|
|
27
|
+
$start: SyncHook<[DanmakuItem]>;
|
|
28
|
+
$end: SyncHook<[DanmakuItem]>;
|
|
29
|
+
$pause: SyncHook<[DanmakuItem]>;
|
|
30
|
+
$resume: SyncHook<[DanmakuItem]>;
|
|
31
|
+
$cancel: SyncHook<[DanmakuItem]>;
|
|
32
|
+
$mount: SyncHook<[DanmakuItem]>;
|
|
33
|
+
$unmount: SyncHook<[DanmakuItem]>;
|
|
34
|
+
$format: SyncHook<[DanmakuItem]>;
|
|
35
|
+
$resize: SyncHook<[DanmakuItem]>;
|
|
36
|
+
start: SyncHook<[]>;
|
|
37
|
+
stop: SyncHook<[]>;
|
|
38
|
+
cancel: SyncHook<[]>;
|
|
39
|
+
freeze: SyncHook<[]>;
|
|
40
|
+
unfreeze: SyncHook<[]>;
|
|
41
|
+
mount: SyncHook<[HTMLElement]>;
|
|
42
|
+
unmount: SyncHook<[HTMLElement]>;
|
|
43
|
+
format: SyncHook<[]>;
|
|
44
|
+
resize: SyncHook<[]>;
|
|
45
|
+
finish: SyncHook<[]>;
|
|
46
|
+
screen_empty: SyncHook<[]>;
|
|
47
|
+
};
|
|
48
|
+
export declare function create_danmaku_plugin_system(): PluginSystem<DanmakuHooks>;
|
|
49
|
+
export declare function create_manager_plugin_system(): PluginSystem<ManagerHooks>;
|
|
50
|
+
export declare function create_danmaku_bridge_plugin(m: ReturnType<typeof create_manager_plugin_system>): Record<string, unknown>;
|
package/dist/utils.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@evqovs/danmaku",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight ESM-based danmaku library.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./dist/danmaku.js",
|
|
9
|
+
"types": "./dist/index.d.ts"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"clean": "npx rimraf ./dist",
|
|
14
|
+
"build": "npm run clean && npx rollup -c"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
18
|
+
"rimraf": "^6.0.1",
|
|
19
|
+
"rollup": "^4.52.5",
|
|
20
|
+
"tslib": "^2.8.1"
|
|
21
|
+
},
|
|
22
|
+
"author": "evqovv",
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"keywords": [
|
|
25
|
+
"danmaku"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/evqovv/danmaku.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/evqovv/danmaku#readme",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/evqovv/danmaku/issues"
|
|
34
|
+
}
|
|
35
|
+
}
|