@khesira/textflow 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/README.md +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/src/TextElement.d.ts +44 -0
- package/dist/src/TextFlowView.d.ts +18 -0
- package/dist/src/TextWriter.d.ts +28 -0
- package/dist/src/World.d.ts +23 -0
- package/dist/src/directions.d.ts +71 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/settings.d.ts +3 -0
- package/dist/src/types.d.ts +19 -0
- package/dist/textflow.es.js +363 -0
- package/dist/textflow.umd.js +5 -0
- package/dist/vite.config.d.ts +2 -0
- package/package.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# textflow
|
|
2
|
+
|
|
3
|
+
To install dependencies:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
To run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun run index.ts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This project was created using `bun init` in bun v1.3.14. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { BoundingBox, Settings } from './types';
|
|
2
|
+
import { Direction } from './directions';
|
|
3
|
+
export declare class TextElement {
|
|
4
|
+
private _positionX;
|
|
5
|
+
private _positionY;
|
|
6
|
+
private readonly _maxAccelerationX;
|
|
7
|
+
private readonly _width;
|
|
8
|
+
private readonly _height;
|
|
9
|
+
private readonly _text;
|
|
10
|
+
private readonly _fontSize;
|
|
11
|
+
private readonly _textDirection;
|
|
12
|
+
private _accelerateId;
|
|
13
|
+
private _breakDownId;
|
|
14
|
+
private _accelerationX;
|
|
15
|
+
private _accelerationY;
|
|
16
|
+
private _velocityX;
|
|
17
|
+
private _velocityY;
|
|
18
|
+
private readonly _acceleration;
|
|
19
|
+
private _boundingBox;
|
|
20
|
+
private readonly _font;
|
|
21
|
+
private readonly _color;
|
|
22
|
+
constructor(_positionX: number, _positionY: number, _maxAccelerationX: number, _width: number, _height: number, _text: string, _fontSize: number, _textDirection: Direction, _settings: Settings);
|
|
23
|
+
get width(): number;
|
|
24
|
+
get height(): number;
|
|
25
|
+
get positionX(): number;
|
|
26
|
+
set positionX(value: number);
|
|
27
|
+
get positionY(): number;
|
|
28
|
+
set positionY(value: number);
|
|
29
|
+
get boundingBox(): BoundingBox;
|
|
30
|
+
set boundingBox(value: BoundingBox);
|
|
31
|
+
get accelerationX(): number;
|
|
32
|
+
get fontSize(): number;
|
|
33
|
+
get font(): string;
|
|
34
|
+
get color(): string;
|
|
35
|
+
get text(): string;
|
|
36
|
+
isCollidingWithBorder(direction: number, width: number): boolean;
|
|
37
|
+
private calculateBoundingBox;
|
|
38
|
+
private accelerate;
|
|
39
|
+
private breakDown;
|
|
40
|
+
boostUpByCollision: (textElement: TextElement) => void;
|
|
41
|
+
breakDownByCollision: (textElement: TextElement) => void;
|
|
42
|
+
start(): void;
|
|
43
|
+
move(): void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { World } from './World';
|
|
2
|
+
import { Settings } from './types';
|
|
3
|
+
export declare class TextFlowView {
|
|
4
|
+
private readonly _settings;
|
|
5
|
+
private readonly _target;
|
|
6
|
+
private readonly _canvas;
|
|
7
|
+
private readonly _context2d;
|
|
8
|
+
private readonly _world;
|
|
9
|
+
private _renderId;
|
|
10
|
+
constructor(_settings: Settings, _target: HTMLElement, _canvas: HTMLCanvasElement, _context2d: CanvasRenderingContext2D, _world: World);
|
|
11
|
+
setFontSize(fontSize: number): void;
|
|
12
|
+
measureText(text: string): number;
|
|
13
|
+
resize(): void;
|
|
14
|
+
clear(): void;
|
|
15
|
+
start(): void;
|
|
16
|
+
stop(): void;
|
|
17
|
+
private renderView;
|
|
18
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Settings } from './types';
|
|
2
|
+
import { World } from './World';
|
|
3
|
+
import { TextFlowView } from './TextFlowView';
|
|
4
|
+
import { Direction } from './directions';
|
|
5
|
+
export declare class TextWriter {
|
|
6
|
+
private readonly _settings;
|
|
7
|
+
private readonly _textDirection;
|
|
8
|
+
private _width;
|
|
9
|
+
private _height;
|
|
10
|
+
private readonly _world;
|
|
11
|
+
private readonly _view;
|
|
12
|
+
private readonly _texts;
|
|
13
|
+
private _textId;
|
|
14
|
+
private _isRunning;
|
|
15
|
+
private _minFontSize;
|
|
16
|
+
private _maxFontSize;
|
|
17
|
+
private readonly _marginTop;
|
|
18
|
+
private readonly _marginBottom;
|
|
19
|
+
private readonly _maxTexts;
|
|
20
|
+
constructor(_settings: Settings, _textDirection: Direction, _width: number, _height: number, _world: World, _view: TextFlowView, _texts: string[]);
|
|
21
|
+
addText(): void;
|
|
22
|
+
resize(width: number, height: number): void;
|
|
23
|
+
start(): void;
|
|
24
|
+
stop(): void;
|
|
25
|
+
private updateFontSizes;
|
|
26
|
+
private random;
|
|
27
|
+
private randomSetTimeout;
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Direction } from './directions';
|
|
2
|
+
import { TextElement } from './TextElement';
|
|
3
|
+
export declare class World {
|
|
4
|
+
private readonly _textDirection;
|
|
5
|
+
private updateId;
|
|
6
|
+
private _isRunning;
|
|
7
|
+
private _elements;
|
|
8
|
+
private _width;
|
|
9
|
+
constructor(_textDirection: Direction);
|
|
10
|
+
get elements(): TextElement[];
|
|
11
|
+
addElement(element: TextElement): void;
|
|
12
|
+
resize(width: number): void;
|
|
13
|
+
start(): void;
|
|
14
|
+
stop(): void;
|
|
15
|
+
private areObjectsColliding;
|
|
16
|
+
private getCollisionAxis;
|
|
17
|
+
private handleCollision;
|
|
18
|
+
private handleXCollision;
|
|
19
|
+
private handleYCollision;
|
|
20
|
+
private detectCollisions;
|
|
21
|
+
private updateElements;
|
|
22
|
+
private updateWorld;
|
|
23
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export interface Direction {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
}
|
|
5
|
+
export declare const directions: {
|
|
6
|
+
readonly none: {
|
|
7
|
+
readonly x: 0;
|
|
8
|
+
readonly y: 0;
|
|
9
|
+
};
|
|
10
|
+
readonly north: {
|
|
11
|
+
readonly x: 0;
|
|
12
|
+
readonly y: -1;
|
|
13
|
+
};
|
|
14
|
+
readonly northEast: {
|
|
15
|
+
readonly x: 1;
|
|
16
|
+
readonly y: -1;
|
|
17
|
+
};
|
|
18
|
+
readonly east: {
|
|
19
|
+
readonly x: 1;
|
|
20
|
+
readonly y: 0;
|
|
21
|
+
};
|
|
22
|
+
readonly southEast: {
|
|
23
|
+
readonly x: 1;
|
|
24
|
+
readonly y: 1;
|
|
25
|
+
};
|
|
26
|
+
readonly south: {
|
|
27
|
+
readonly x: 0;
|
|
28
|
+
readonly y: 1;
|
|
29
|
+
};
|
|
30
|
+
readonly southWest: {
|
|
31
|
+
readonly x: -1;
|
|
32
|
+
readonly y: 1;
|
|
33
|
+
};
|
|
34
|
+
readonly west: {
|
|
35
|
+
readonly x: -1;
|
|
36
|
+
readonly y: 0;
|
|
37
|
+
};
|
|
38
|
+
readonly northWest: {
|
|
39
|
+
readonly x: -1;
|
|
40
|
+
readonly y: -1;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export declare const defaultDirections: ({
|
|
44
|
+
readonly x: 0;
|
|
45
|
+
readonly y: 0;
|
|
46
|
+
} | {
|
|
47
|
+
readonly x: 0;
|
|
48
|
+
readonly y: -1;
|
|
49
|
+
} | {
|
|
50
|
+
readonly x: 1;
|
|
51
|
+
readonly y: -1;
|
|
52
|
+
} | {
|
|
53
|
+
readonly x: 1;
|
|
54
|
+
readonly y: 0;
|
|
55
|
+
} | {
|
|
56
|
+
readonly x: 1;
|
|
57
|
+
readonly y: 1;
|
|
58
|
+
} | {
|
|
59
|
+
readonly x: 0;
|
|
60
|
+
readonly y: 1;
|
|
61
|
+
} | {
|
|
62
|
+
readonly x: -1;
|
|
63
|
+
readonly y: 1;
|
|
64
|
+
} | {
|
|
65
|
+
readonly x: -1;
|
|
66
|
+
readonly y: 0;
|
|
67
|
+
} | {
|
|
68
|
+
readonly x: -1;
|
|
69
|
+
readonly y: -1;
|
|
70
|
+
})[];
|
|
71
|
+
export declare function randomDirection(availableDirections?: readonly Direction[]): Direction;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Settings } from './types';
|
|
2
|
+
export type { Settings };
|
|
3
|
+
export declare class TextFlowElement extends HTMLElement {
|
|
4
|
+
private active;
|
|
5
|
+
private resizeObserver;
|
|
6
|
+
private world;
|
|
7
|
+
private view;
|
|
8
|
+
private textWriter;
|
|
9
|
+
private animationFrameId;
|
|
10
|
+
connectedCallback(): void;
|
|
11
|
+
disconnectedCallback(): void;
|
|
12
|
+
private loop;
|
|
13
|
+
private startLoop;
|
|
14
|
+
private stopLoop;
|
|
15
|
+
private resize;
|
|
16
|
+
private visibilityChange;
|
|
17
|
+
}
|
|
18
|
+
export declare function registerTextFlowSlider(tagName?: string): void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface BoundingBox {
|
|
2
|
+
top: number;
|
|
3
|
+
right: number;
|
|
4
|
+
bottom: number;
|
|
5
|
+
left: number;
|
|
6
|
+
}
|
|
7
|
+
export interface Settings {
|
|
8
|
+
width: string;
|
|
9
|
+
height: string;
|
|
10
|
+
maxTexts: number;
|
|
11
|
+
maxAcceleration: number;
|
|
12
|
+
marginTop: number;
|
|
13
|
+
marginBottom: number;
|
|
14
|
+
texts: string[];
|
|
15
|
+
color: string;
|
|
16
|
+
background: string;
|
|
17
|
+
font: string;
|
|
18
|
+
debug: boolean;
|
|
19
|
+
}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
//#region src/World.ts
|
|
2
|
+
var e = class {
|
|
3
|
+
_textDirection;
|
|
4
|
+
updateId = void 0;
|
|
5
|
+
_isRunning = !1;
|
|
6
|
+
_elements = [];
|
|
7
|
+
_width = 0;
|
|
8
|
+
constructor(e) {
|
|
9
|
+
this._textDirection = e;
|
|
10
|
+
}
|
|
11
|
+
get elements() {
|
|
12
|
+
return this._elements;
|
|
13
|
+
}
|
|
14
|
+
addElement(e) {
|
|
15
|
+
this._elements.push(e);
|
|
16
|
+
}
|
|
17
|
+
resize(e) {
|
|
18
|
+
this._width = e;
|
|
19
|
+
}
|
|
20
|
+
start() {
|
|
21
|
+
this._isRunning || (this._isRunning = !0, this.updateWorld());
|
|
22
|
+
}
|
|
23
|
+
stop() {
|
|
24
|
+
this._isRunning = !1, this.updateId !== void 0 && (window.clearTimeout(this.updateId), this.updateId = void 0);
|
|
25
|
+
}
|
|
26
|
+
areObjectsColliding(e, t) {
|
|
27
|
+
let n = e.boundingBox.left > t.boundingBox.right || t.boundingBox.left > e.boundingBox.right, r = e.boundingBox.top > t.boundingBox.bottom || t.boundingBox.top > e.boundingBox.bottom;
|
|
28
|
+
return !(n || r);
|
|
29
|
+
}
|
|
30
|
+
getCollisionAxis(e, t) {
|
|
31
|
+
let n = [
|
|
32
|
+
e.positionX,
|
|
33
|
+
e.positionX + e.width,
|
|
34
|
+
t.positionX,
|
|
35
|
+
t.positionX + t.width
|
|
36
|
+
], r = [
|
|
37
|
+
e.positionY,
|
|
38
|
+
e.positionY + e.height,
|
|
39
|
+
t.positionY,
|
|
40
|
+
t.positionY + t.height
|
|
41
|
+
], i = (e, t) => e - t;
|
|
42
|
+
return n.sort(i), r.sort(i), n[2] - n[1] < r[2] - r[1] ? "x" : "y";
|
|
43
|
+
}
|
|
44
|
+
handleCollision(e, t) {
|
|
45
|
+
this.getCollisionAxis(e, t) === "x" ? this.handleXCollision(e, t) : this.handleYCollision(e, t);
|
|
46
|
+
}
|
|
47
|
+
handleXCollision(e, t) {
|
|
48
|
+
if (this._textDirection.x === 0) return;
|
|
49
|
+
let n = e.boundingBox.left < t.boundingBox.left, r = n ? e : t, i = n ? t : e;
|
|
50
|
+
this._textDirection.x < 0 ? (r.boostUpByCollision(i), i.breakDownByCollision(r)) : (i.boostUpByCollision(r), r.breakDownByCollision(i));
|
|
51
|
+
}
|
|
52
|
+
handleYCollision(e, t) {
|
|
53
|
+
if (this._textDirection.y === 0) return;
|
|
54
|
+
let n = e.boundingBox.top < t.boundingBox.top, r = n ? e : t, i = n ? t : e;
|
|
55
|
+
this._textDirection.y < 0 ? (r.boostUpByCollision(i), i.breakDownByCollision(r)) : (i.boostUpByCollision(r), r.breakDownByCollision(i));
|
|
56
|
+
}
|
|
57
|
+
detectCollisions() {
|
|
58
|
+
for (let e = 0; e < this._elements.length; e++) {
|
|
59
|
+
let t = this._elements[e];
|
|
60
|
+
for (let n = e + 1; n < this._elements.length; n++) {
|
|
61
|
+
let e = this._elements[n];
|
|
62
|
+
this.areObjectsColliding(t, e) && this.handleCollision(t, e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
updateElements() {
|
|
67
|
+
for (let e = this._elements.length - 1; e >= 0; e--) {
|
|
68
|
+
let t = this._elements[e];
|
|
69
|
+
t.move(), t.isCollidingWithBorder(this._textDirection.x, this._width) && this._elements.splice(e, 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
updateWorld() {
|
|
73
|
+
this._isRunning && (this.detectCollisions(), this.updateElements(), this.updateId = window.setTimeout(() => this.updateWorld(), 1e3 / 60));
|
|
74
|
+
}
|
|
75
|
+
}, t = class {
|
|
76
|
+
_settings;
|
|
77
|
+
_target;
|
|
78
|
+
_canvas;
|
|
79
|
+
_context2d;
|
|
80
|
+
_world;
|
|
81
|
+
_renderId = 0;
|
|
82
|
+
constructor(e, t, n, r, i) {
|
|
83
|
+
this._settings = e, this._target = t, this._canvas = n, this._context2d = r, this._world = i, this._target.style.display = "block", this._target.style.width = this._settings.width, this._target.style.height = this._settings.height, this._target.style.background = this._settings.background;
|
|
84
|
+
}
|
|
85
|
+
setFontSize(e) {
|
|
86
|
+
this._context2d.font = `${e}pt ${this._settings.font}`;
|
|
87
|
+
}
|
|
88
|
+
measureText(e) {
|
|
89
|
+
return this._context2d.measureText(e).width;
|
|
90
|
+
}
|
|
91
|
+
resize() {
|
|
92
|
+
let e = typeof window < "u" && window.devicePixelRatio || 1;
|
|
93
|
+
this._canvas.width = this._target.clientWidth * e, this._canvas.height = this._target.clientHeight * e, this._canvas.style.width = `${this._target.clientWidth}px`, this._canvas.style.height = `${this._target.clientHeight}px`, this._context2d.scale(e, e);
|
|
94
|
+
}
|
|
95
|
+
clear() {
|
|
96
|
+
if (!this._context2d) return;
|
|
97
|
+
let e = this._context2d.fillStyle;
|
|
98
|
+
this._context2d.fillStyle = this._settings.debug ? "rgba(255, 255, 0, 1)" : this._settings.background, this._context2d.fillRect(0, 0, this._target.clientWidth, this._target.clientHeight), this._context2d.fillStyle = e;
|
|
99
|
+
}
|
|
100
|
+
start() {
|
|
101
|
+
this._context2d && (this._context2d.textAlign = "start", this._renderId === 0 && this.renderView());
|
|
102
|
+
}
|
|
103
|
+
stop() {
|
|
104
|
+
this._renderId !== 0 && (cancelAnimationFrame(this._renderId), this._renderId = 0), this.clear();
|
|
105
|
+
}
|
|
106
|
+
renderView = () => {
|
|
107
|
+
if (this._context2d) {
|
|
108
|
+
this.clear();
|
|
109
|
+
for (let e of this._world.elements) this._context2d.font = e.font, this._settings.debug && (this._context2d.fillStyle = "#ff0000", this._context2d.fillRect(e.positionX, e.positionY - e.height, e.width, e.height)), this._context2d.fillStyle = e.color, this._context2d.fillText(e.text, e.positionX, e.positionY);
|
|
110
|
+
this._renderId = requestAnimationFrame(this.renderView);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}, n = class {
|
|
114
|
+
_positionX;
|
|
115
|
+
_positionY;
|
|
116
|
+
_maxAccelerationX;
|
|
117
|
+
_width;
|
|
118
|
+
_height;
|
|
119
|
+
_text;
|
|
120
|
+
_fontSize;
|
|
121
|
+
_textDirection;
|
|
122
|
+
_accelerateId = void 0;
|
|
123
|
+
_breakDownId = void 0;
|
|
124
|
+
_accelerationX = 0;
|
|
125
|
+
_accelerationY = 0;
|
|
126
|
+
_velocityX = 0;
|
|
127
|
+
_velocityY = 0;
|
|
128
|
+
_acceleration = 0;
|
|
129
|
+
_boundingBox;
|
|
130
|
+
_font;
|
|
131
|
+
_color;
|
|
132
|
+
constructor(e, t, n, r, i, a, o, s, c) {
|
|
133
|
+
this._positionX = e, this._positionY = t, this._maxAccelerationX = n, this._width = r, this._height = i, this._text = a, this._fontSize = o, this._textDirection = s, this._acceleration = 1 / this._fontSize, this._boundingBox = this.calculateBoundingBox(), this._font = `${o}pt ${c.font}`, this._color = c.color;
|
|
134
|
+
}
|
|
135
|
+
get width() {
|
|
136
|
+
return this._width;
|
|
137
|
+
}
|
|
138
|
+
get height() {
|
|
139
|
+
return this._height;
|
|
140
|
+
}
|
|
141
|
+
get positionX() {
|
|
142
|
+
return this._positionX;
|
|
143
|
+
}
|
|
144
|
+
set positionX(e) {
|
|
145
|
+
this._positionX = e;
|
|
146
|
+
}
|
|
147
|
+
get positionY() {
|
|
148
|
+
return this._positionY;
|
|
149
|
+
}
|
|
150
|
+
set positionY(e) {
|
|
151
|
+
this._positionY = e;
|
|
152
|
+
}
|
|
153
|
+
get boundingBox() {
|
|
154
|
+
return this._boundingBox;
|
|
155
|
+
}
|
|
156
|
+
set boundingBox(e) {
|
|
157
|
+
this._boundingBox = e;
|
|
158
|
+
}
|
|
159
|
+
get accelerationX() {
|
|
160
|
+
return this._accelerationX;
|
|
161
|
+
}
|
|
162
|
+
get fontSize() {
|
|
163
|
+
return this._fontSize;
|
|
164
|
+
}
|
|
165
|
+
get font() {
|
|
166
|
+
return this._font;
|
|
167
|
+
}
|
|
168
|
+
get color() {
|
|
169
|
+
return this._color;
|
|
170
|
+
}
|
|
171
|
+
get text() {
|
|
172
|
+
return this._text;
|
|
173
|
+
}
|
|
174
|
+
isCollidingWithBorder(e, t) {
|
|
175
|
+
if (e < 0) {
|
|
176
|
+
if (this._boundingBox.right < 0) return !0;
|
|
177
|
+
} else if (this._boundingBox.left > t) return !0;
|
|
178
|
+
return !1;
|
|
179
|
+
}
|
|
180
|
+
calculateBoundingBox() {
|
|
181
|
+
return {
|
|
182
|
+
top: this._positionY - this._height,
|
|
183
|
+
right: this._positionX + this._width,
|
|
184
|
+
bottom: this._positionY,
|
|
185
|
+
left: this._positionX
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
accelerate() {
|
|
189
|
+
this._accelerationX += this._acceleration * this._textDirection.x, Math.abs(this._accelerationX) < Math.abs(this._maxAccelerationX) ? this._accelerateId = window.setTimeout(() => this.accelerate(), 40) : (this._accelerationX = this._maxAccelerationX, this._accelerateId = void 0);
|
|
190
|
+
}
|
|
191
|
+
breakDown() {
|
|
192
|
+
this._accelerationX += .1 * this._textDirection.x, Math.abs(this._accelerationX) > Math.abs(this._maxAccelerationX) ? this._breakDownId = window.setTimeout(() => this.breakDown(), 40) : (this._accelerationX = this._maxAccelerationX, this._breakDownId = void 0, this._accelerateId = void 0);
|
|
193
|
+
}
|
|
194
|
+
boostUpByCollision = (e) => {
|
|
195
|
+
this._accelerationX += (e.accelerationX - this._accelerationX) * (e.fontSize / this._fontSize), clearTimeout(this._accelerateId), clearTimeout(this._breakDownId), this.breakDown();
|
|
196
|
+
};
|
|
197
|
+
breakDownByCollision = (e) => {
|
|
198
|
+
this._accelerationX -= this._accelerationX * (e.fontSize / this._fontSize), clearTimeout(this._accelerateId), clearTimeout(this._breakDownId), this.accelerate();
|
|
199
|
+
};
|
|
200
|
+
start() {
|
|
201
|
+
this.accelerate();
|
|
202
|
+
}
|
|
203
|
+
move() {
|
|
204
|
+
this._velocityX = this._accelerationX, this._velocityY = this._accelerationY, this._positionX += this._velocityX, this._positionY += this._velocityY, this._boundingBox = this.calculateBoundingBox();
|
|
205
|
+
}
|
|
206
|
+
}, r = class {
|
|
207
|
+
_settings;
|
|
208
|
+
_textDirection;
|
|
209
|
+
_width;
|
|
210
|
+
_height;
|
|
211
|
+
_world;
|
|
212
|
+
_view;
|
|
213
|
+
_texts;
|
|
214
|
+
_textId = void 0;
|
|
215
|
+
_isRunning = !1;
|
|
216
|
+
_minFontSize = 16;
|
|
217
|
+
_maxFontSize = 20;
|
|
218
|
+
_marginTop;
|
|
219
|
+
_marginBottom;
|
|
220
|
+
_maxTexts;
|
|
221
|
+
constructor(e, t, n, r, i, a, o) {
|
|
222
|
+
this._settings = e, this._textDirection = t, this._width = n, this._height = r, this._world = i, this._view = a, this._texts = o, this._marginTop = this._settings.marginTop, this._marginBottom = this._settings.marginBottom, this._maxTexts = this._settings.maxTexts, this.updateFontSizes();
|
|
223
|
+
}
|
|
224
|
+
addText() {
|
|
225
|
+
if (!this._isRunning) return;
|
|
226
|
+
if (this._world.elements.length >= this._maxTexts) {
|
|
227
|
+
this._textId = this.randomSetTimeout(() => this.addText(), 1e3, 2500);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
let e = this.random(this._minFontSize, this._maxFontSize), t = this._texts[this.random(this._texts.length)], r = this.random(1, this._settings.maxAcceleration + 1) * this._textDirection.x;
|
|
231
|
+
this._view.setFontSize(e);
|
|
232
|
+
let i = this._view.measureText(t), a = new n(this._textDirection.x < 0 ? this._width : -i, this.random(this._marginTop, this._height - this._marginBottom - this._marginTop), r, i, e, t, e, this._textDirection, this._settings);
|
|
233
|
+
this._world.addElement(a), a.start(), this._textId = this.randomSetTimeout(() => this.addText(), 1e3, 2500);
|
|
234
|
+
}
|
|
235
|
+
resize(e, t) {
|
|
236
|
+
this._width = e, this._height = t, this.updateFontSizes();
|
|
237
|
+
}
|
|
238
|
+
start() {
|
|
239
|
+
this._isRunning || (this._isRunning = !0, this.addText());
|
|
240
|
+
}
|
|
241
|
+
stop() {
|
|
242
|
+
this._isRunning = !1, this._textId !== void 0 && (window.clearTimeout(this._textId), this._textId = void 0);
|
|
243
|
+
}
|
|
244
|
+
updateFontSizes() {
|
|
245
|
+
typeof window < "u" && (this._minFontSize = window.innerWidth <= 800 ? 12 : 16, this._maxFontSize = window.innerWidth <= 800 ? 16 : 20);
|
|
246
|
+
}
|
|
247
|
+
random(e, t) {
|
|
248
|
+
return t === void 0 ? Math.floor(Math.random() * e) : e + Math.floor(Math.random() * (t - e + 1));
|
|
249
|
+
}
|
|
250
|
+
randomSetTimeout(e, t, n) {
|
|
251
|
+
return window.setTimeout(() => e(), this.random(t, n));
|
|
252
|
+
}
|
|
253
|
+
}, i = {
|
|
254
|
+
width: "100%",
|
|
255
|
+
height: "200px",
|
|
256
|
+
maxTexts: 15,
|
|
257
|
+
maxAcceleration: 15,
|
|
258
|
+
marginTop: 0,
|
|
259
|
+
marginBottom: 0,
|
|
260
|
+
texts: [
|
|
261
|
+
"Add",
|
|
262
|
+
"your",
|
|
263
|
+
"own",
|
|
264
|
+
"texts",
|
|
265
|
+
"here"
|
|
266
|
+
],
|
|
267
|
+
color: "#333",
|
|
268
|
+
background: "rgba(255, 255, 255, 1)",
|
|
269
|
+
font: "sans-serif",
|
|
270
|
+
debug: !1
|
|
271
|
+
};
|
|
272
|
+
function a(e) {
|
|
273
|
+
let t = { ...i };
|
|
274
|
+
return typeof e.maxTexts == "number" && e.maxTexts > 0 ? t.maxTexts = Math.floor(e.maxTexts) : e.maxTexts !== void 0 && console.warn(`[textflow] Invalid maxTexts "${e.maxTexts}". Using default: ${i.maxTexts}`), typeof e.maxAcceleration == "number" && e.maxAcceleration > 0 && (t.maxAcceleration = e.maxAcceleration), typeof e.marginTop == "number" && e.marginTop >= 0 && (t.marginTop = e.marginTop), typeof e.marginBottom == "number" && e.marginBottom >= 0 && (t.marginBottom = e.marginBottom), typeof e.font == "string" && e.font.trim() !== "" && (t.font = e.font), typeof e.color == "string" && e.color.trim() !== "" && (t.color = e.color), typeof e.background == "string" && e.background.trim() !== "" && (t.background = e.background), e.debug !== void 0 && (t.debug = !!e.debug), t;
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/directions.ts
|
|
278
|
+
var o = {
|
|
279
|
+
none: {
|
|
280
|
+
x: 0,
|
|
281
|
+
y: 0
|
|
282
|
+
},
|
|
283
|
+
north: {
|
|
284
|
+
x: 0,
|
|
285
|
+
y: -1
|
|
286
|
+
},
|
|
287
|
+
northEast: {
|
|
288
|
+
x: 1,
|
|
289
|
+
y: -1
|
|
290
|
+
},
|
|
291
|
+
east: {
|
|
292
|
+
x: 1,
|
|
293
|
+
y: 0
|
|
294
|
+
},
|
|
295
|
+
southEast: {
|
|
296
|
+
x: 1,
|
|
297
|
+
y: 1
|
|
298
|
+
},
|
|
299
|
+
south: {
|
|
300
|
+
x: 0,
|
|
301
|
+
y: 1
|
|
302
|
+
},
|
|
303
|
+
southWest: {
|
|
304
|
+
x: -1,
|
|
305
|
+
y: 1
|
|
306
|
+
},
|
|
307
|
+
west: {
|
|
308
|
+
x: -1,
|
|
309
|
+
y: 0
|
|
310
|
+
},
|
|
311
|
+
northWest: {
|
|
312
|
+
x: -1,
|
|
313
|
+
y: -1
|
|
314
|
+
}
|
|
315
|
+
}, s = Object.values(o);
|
|
316
|
+
function c(e = s) {
|
|
317
|
+
return e[Math.floor(Math.random() * e.length)];
|
|
318
|
+
}
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/index.ts
|
|
321
|
+
var l = class extends HTMLElement {
|
|
322
|
+
active = !1;
|
|
323
|
+
resizeObserver = null;
|
|
324
|
+
world;
|
|
325
|
+
view;
|
|
326
|
+
textWriter;
|
|
327
|
+
animationFrameId = null;
|
|
328
|
+
connectedCallback() {
|
|
329
|
+
this.innerHTML = "\n <div class=\"slider-container\">\n <canvas class=\"physics-canvas\"></canvas>\n </div>\n ";
|
|
330
|
+
let n = this.querySelector(".physics-canvas"), s = n.getContext("2d");
|
|
331
|
+
if (!s) {
|
|
332
|
+
console.error("Could not get context2d");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
let l = this.dataset.settings, u = a(l ? JSON.parse(l) : i), d = this.dataset.texts, f = d ? JSON.parse(d) : u.texts, p = c([o.east, o.west]);
|
|
336
|
+
this.world = new e(p), this.view = new t(u, this, n, s, this.world), this.textWriter = new r(u, p, this.offsetWidth, this.offsetHeight, this.world, this.view, f), this.startLoop(), document.addEventListener("visibilitychange", this.visibilityChange), this.resizeObserver = new ResizeObserver(() => {
|
|
337
|
+
this.resize();
|
|
338
|
+
}), this.resizeObserver.observe(this);
|
|
339
|
+
}
|
|
340
|
+
disconnectedCallback() {
|
|
341
|
+
this.stopLoop(), document.removeEventListener("visibilitychange", this.visibilityChange), this.resizeObserver && this.resizeObserver.disconnect();
|
|
342
|
+
}
|
|
343
|
+
loop = () => {
|
|
344
|
+
this.active && (this.animationFrameId = window.requestAnimationFrame(this.loop));
|
|
345
|
+
};
|
|
346
|
+
startLoop = () => {
|
|
347
|
+
this.active || (this.view.start(), this.world.start(), this.textWriter.start(), this.active = !0, this.animationFrameId = window.requestAnimationFrame(this.loop));
|
|
348
|
+
};
|
|
349
|
+
stopLoop() {
|
|
350
|
+
this.active && (this.view.stop(), this.world.stop(), this.textWriter.stop(), this.active = !1, this.animationFrameId &&= (window.cancelAnimationFrame(this.animationFrameId), null));
|
|
351
|
+
}
|
|
352
|
+
resize() {
|
|
353
|
+
this.view.resize(), this.world.resize(this.offsetWidth), this.textWriter.resize(this.offsetWidth, this.offsetHeight);
|
|
354
|
+
}
|
|
355
|
+
visibilityChange = () => {
|
|
356
|
+
document.hidden ? this.stopLoop() : this.startLoop();
|
|
357
|
+
};
|
|
358
|
+
};
|
|
359
|
+
function u(e = "textflow-container") {
|
|
360
|
+
typeof window < "u" && !customElements.get(e) && customElements.define(e, l);
|
|
361
|
+
}
|
|
362
|
+
//#endregion
|
|
363
|
+
export { l as TextFlowElement, u as registerTextFlowSlider };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.TextFlow={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class{_textDirection;updateId=void 0;_isRunning=!1;_elements=[];_width=0;constructor(e){this._textDirection=e}get elements(){return this._elements}addElement(e){this._elements.push(e)}resize(e){this._width=e}start(){this._isRunning||(this._isRunning=!0,this.updateWorld())}stop(){this._isRunning=!1,this.updateId!==void 0&&(window.clearTimeout(this.updateId),this.updateId=void 0)}areObjectsColliding(e,t){let n=e.boundingBox.left>t.boundingBox.right||t.boundingBox.left>e.boundingBox.right,r=e.boundingBox.top>t.boundingBox.bottom||t.boundingBox.top>e.boundingBox.bottom;return!(n||r)}getCollisionAxis(e,t){let n=[e.positionX,e.positionX+e.width,t.positionX,t.positionX+t.width],r=[e.positionY,e.positionY+e.height,t.positionY,t.positionY+t.height],i=(e,t)=>e-t;return n.sort(i),r.sort(i),n[2]-n[1]<r[2]-r[1]?`x`:`y`}handleCollision(e,t){this.getCollisionAxis(e,t)===`x`?this.handleXCollision(e,t):this.handleYCollision(e,t)}handleXCollision(e,t){if(this._textDirection.x===0)return;let n=e.boundingBox.left<t.boundingBox.left,r=n?e:t,i=n?t:e;this._textDirection.x<0?(r.boostUpByCollision(i),i.breakDownByCollision(r)):(i.boostUpByCollision(r),r.breakDownByCollision(i))}handleYCollision(e,t){if(this._textDirection.y===0)return;let n=e.boundingBox.top<t.boundingBox.top,r=n?e:t,i=n?t:e;this._textDirection.y<0?(r.boostUpByCollision(i),i.breakDownByCollision(r)):(i.boostUpByCollision(r),r.breakDownByCollision(i))}detectCollisions(){for(let e=0;e<this._elements.length;e++){let t=this._elements[e];for(let n=e+1;n<this._elements.length;n++){let e=this._elements[n];this.areObjectsColliding(t,e)&&this.handleCollision(t,e)}}}updateElements(){for(let e=this._elements.length-1;e>=0;e--){let t=this._elements[e];t.move(),t.isCollidingWithBorder(this._textDirection.x,this._width)&&this._elements.splice(e,1)}}updateWorld(){this._isRunning&&(this.detectCollisions(),this.updateElements(),this.updateId=window.setTimeout(()=>this.updateWorld(),1e3/60))}},n=class{_settings;_target;_canvas;_context2d;_world;_renderId=0;constructor(e,t,n,r,i){this._settings=e,this._target=t,this._canvas=n,this._context2d=r,this._world=i,this._target.style.display=`block`,this._target.style.width=this._settings.width,this._target.style.height=this._settings.height,this._target.style.background=this._settings.background}setFontSize(e){this._context2d.font=`${e}pt ${this._settings.font}`}measureText(e){return this._context2d.measureText(e).width}resize(){let e=typeof window<`u`&&window.devicePixelRatio||1;this._canvas.width=this._target.clientWidth*e,this._canvas.height=this._target.clientHeight*e,this._canvas.style.width=`${this._target.clientWidth}px`,this._canvas.style.height=`${this._target.clientHeight}px`,this._context2d.scale(e,e)}clear(){if(!this._context2d)return;let e=this._context2d.fillStyle;this._context2d.fillStyle=this._settings.debug?`rgba(255, 255, 0, 1)`:this._settings.background,this._context2d.fillRect(0,0,this._target.clientWidth,this._target.clientHeight),this._context2d.fillStyle=e}start(){this._context2d&&(this._context2d.textAlign=`start`,this._renderId===0&&this.renderView())}stop(){this._renderId!==0&&(cancelAnimationFrame(this._renderId),this._renderId=0),this.clear()}renderView=()=>{if(this._context2d){this.clear();for(let e of this._world.elements)this._context2d.font=e.font,this._settings.debug&&(this._context2d.fillStyle=`#ff0000`,this._context2d.fillRect(e.positionX,e.positionY-e.height,e.width,e.height)),this._context2d.fillStyle=e.color,this._context2d.fillText(e.text,e.positionX,e.positionY);this._renderId=requestAnimationFrame(this.renderView)}}},r=class{_positionX;_positionY;_maxAccelerationX;_width;_height;_text;_fontSize;_textDirection;_accelerateId=void 0;_breakDownId=void 0;_accelerationX=0;_accelerationY=0;_velocityX=0;_velocityY=0;_acceleration=0;_boundingBox;_font;_color;constructor(e,t,n,r,i,a,o,s,c){this._positionX=e,this._positionY=t,this._maxAccelerationX=n,this._width=r,this._height=i,this._text=a,this._fontSize=o,this._textDirection=s,this._acceleration=1/this._fontSize,this._boundingBox=this.calculateBoundingBox(),this._font=`${o}pt ${c.font}`,this._color=c.color}get width(){return this._width}get height(){return this._height}get positionX(){return this._positionX}set positionX(e){this._positionX=e}get positionY(){return this._positionY}set positionY(e){this._positionY=e}get boundingBox(){return this._boundingBox}set boundingBox(e){this._boundingBox=e}get accelerationX(){return this._accelerationX}get fontSize(){return this._fontSize}get font(){return this._font}get color(){return this._color}get text(){return this._text}isCollidingWithBorder(e,t){if(e<0){if(this._boundingBox.right<0)return!0}else if(this._boundingBox.left>t)return!0;return!1}calculateBoundingBox(){return{top:this._positionY-this._height,right:this._positionX+this._width,bottom:this._positionY,left:this._positionX}}accelerate(){this._accelerationX+=this._acceleration*this._textDirection.x,Math.abs(this._accelerationX)<Math.abs(this._maxAccelerationX)?this._accelerateId=window.setTimeout(()=>this.accelerate(),40):(this._accelerationX=this._maxAccelerationX,this._accelerateId=void 0)}breakDown(){this._accelerationX+=.1*this._textDirection.x,Math.abs(this._accelerationX)>Math.abs(this._maxAccelerationX)?this._breakDownId=window.setTimeout(()=>this.breakDown(),40):(this._accelerationX=this._maxAccelerationX,this._breakDownId=void 0,this._accelerateId=void 0)}boostUpByCollision=e=>{this._accelerationX+=(e.accelerationX-this._accelerationX)*(e.fontSize/this._fontSize),clearTimeout(this._accelerateId),clearTimeout(this._breakDownId),this.breakDown()};breakDownByCollision=e=>{this._accelerationX-=this._accelerationX*(e.fontSize/this._fontSize),clearTimeout(this._accelerateId),clearTimeout(this._breakDownId),this.accelerate()};start(){this.accelerate()}move(){this._velocityX=this._accelerationX,this._velocityY=this._accelerationY,this._positionX+=this._velocityX,this._positionY+=this._velocityY,this._boundingBox=this.calculateBoundingBox()}},i=class{_settings;_textDirection;_width;_height;_world;_view;_texts;_textId=void 0;_isRunning=!1;_minFontSize=16;_maxFontSize=20;_marginTop;_marginBottom;_maxTexts;constructor(e,t,n,r,i,a,o){this._settings=e,this._textDirection=t,this._width=n,this._height=r,this._world=i,this._view=a,this._texts=o,this._marginTop=this._settings.marginTop,this._marginBottom=this._settings.marginBottom,this._maxTexts=this._settings.maxTexts,this.updateFontSizes()}addText(){if(!this._isRunning)return;if(this._world.elements.length>=this._maxTexts){this._textId=this.randomSetTimeout(()=>this.addText(),1e3,2500);return}let e=this.random(this._minFontSize,this._maxFontSize),t=this._texts[this.random(this._texts.length)],n=this.random(1,this._settings.maxAcceleration+1)*this._textDirection.x;this._view.setFontSize(e);let i=this._view.measureText(t),a=new r(this._textDirection.x<0?this._width:-i,this.random(this._marginTop,this._height-this._marginBottom-this._marginTop),n,i,e,t,e,this._textDirection,this._settings);this._world.addElement(a),a.start(),this._textId=this.randomSetTimeout(()=>this.addText(),1e3,2500)}resize(e,t){this._width=e,this._height=t,this.updateFontSizes()}start(){this._isRunning||(this._isRunning=!0,this.addText())}stop(){this._isRunning=!1,this._textId!==void 0&&(window.clearTimeout(this._textId),this._textId=void 0)}updateFontSizes(){typeof window<`u`&&(this._minFontSize=window.innerWidth<=800?12:16,this._maxFontSize=window.innerWidth<=800?16:20)}random(e,t){return t===void 0?Math.floor(Math.random()*e):e+Math.floor(Math.random()*(t-e+1))}randomSetTimeout(e,t,n){return window.setTimeout(()=>e(),this.random(t,n))}},a={width:`100%`,height:`200px`,maxTexts:15,maxAcceleration:15,marginTop:0,marginBottom:0,texts:[`Add`,`your`,`own`,`texts`,`here`],color:`#333`,background:`rgba(255, 255, 255, 1)`,font:`sans-serif`,debug:!1};function o(e){let t={...a};return typeof e.maxTexts==`number`&&e.maxTexts>0?t.maxTexts=Math.floor(e.maxTexts):e.maxTexts!==void 0&&console.warn(`[textflow] Invalid maxTexts "${e.maxTexts}". Using default: ${a.maxTexts}`),typeof e.maxAcceleration==`number`&&e.maxAcceleration>0&&(t.maxAcceleration=e.maxAcceleration),typeof e.marginTop==`number`&&e.marginTop>=0&&(t.marginTop=e.marginTop),typeof e.marginBottom==`number`&&e.marginBottom>=0&&(t.marginBottom=e.marginBottom),typeof e.font==`string`&&e.font.trim()!==``&&(t.font=e.font),typeof e.color==`string`&&e.color.trim()!==``&&(t.color=e.color),typeof e.background==`string`&&e.background.trim()!==``&&(t.background=e.background),e.debug!==void 0&&(t.debug=!!e.debug),t}var s={none:{x:0,y:0},north:{x:0,y:-1},northEast:{x:1,y:-1},east:{x:1,y:0},southEast:{x:1,y:1},south:{x:0,y:1},southWest:{x:-1,y:1},west:{x:-1,y:0},northWest:{x:-1,y:-1}},c=Object.values(s);function l(e=c){return e[Math.floor(Math.random()*e.length)]}var u=class extends HTMLElement{active=!1;resizeObserver=null;world;view;textWriter;animationFrameId=null;connectedCallback(){this.innerHTML=`
|
|
2
|
+
<div class="slider-container">
|
|
3
|
+
<canvas class="physics-canvas"></canvas>
|
|
4
|
+
</div>
|
|
5
|
+
`;let e=this.querySelector(`.physics-canvas`),r=e.getContext(`2d`);if(!r){console.error(`Could not get context2d`);return}let c=this.dataset.settings,u=o(c?JSON.parse(c):a),d=this.dataset.texts,f=d?JSON.parse(d):u.texts,p=l([s.east,s.west]);this.world=new t(p),this.view=new n(u,this,e,r,this.world),this.textWriter=new i(u,p,this.offsetWidth,this.offsetHeight,this.world,this.view,f),this.startLoop(),document.addEventListener(`visibilitychange`,this.visibilityChange),this.resizeObserver=new ResizeObserver(()=>{this.resize()}),this.resizeObserver.observe(this)}disconnectedCallback(){this.stopLoop(),document.removeEventListener(`visibilitychange`,this.visibilityChange),this.resizeObserver&&this.resizeObserver.disconnect()}loop=()=>{this.active&&(this.animationFrameId=window.requestAnimationFrame(this.loop))};startLoop=()=>{this.active||(this.view.start(),this.world.start(),this.textWriter.start(),this.active=!0,this.animationFrameId=window.requestAnimationFrame(this.loop))};stopLoop(){this.active&&(this.view.stop(),this.world.stop(),this.textWriter.stop(),this.active=!1,this.animationFrameId&&=(window.cancelAnimationFrame(this.animationFrameId),null))}resize(){this.view.resize(),this.world.resize(this.offsetWidth),this.textWriter.resize(this.offsetWidth,this.offsetHeight)}visibilityChange=()=>{document.hidden?this.stopLoop():this.startLoop()}};function d(e=`textflow-container`){typeof window<`u`&&!customElements.get(e)&&customElements.define(e,u)}e.TextFlowElement=u,e.registerTextFlowSlider=d});
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@khesira/textflow",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A framework agnostic canvas physics text slider",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/textflow.umd.cjs",
|
|
7
|
+
"module": "./dist/textflow.es.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/textflow.es.js",
|
|
12
|
+
"require": "./dist/textflow.umd.cjs",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "vite build"
|
|
21
|
+
}
|
|
22
|
+
}
|