@khesira/textflow 1.0.4 → 1.2.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 +41 -15
- package/dist/src/index.d.ts +1 -0
- package/dist/src/types.d.ts +2 -1
- package/dist/textflow.es.js +26 -16
- package/dist/textflow.umd.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,30 +95,56 @@ const settings: Settings = {
|
|
|
95
95
|
|
|
96
96
|
## Configuration (Settings)
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
|
101
|
-
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
98
|
+
The component always fills the available width of its host container. Control the width using regular CSS on the element or its parent. The height can be configured via settings.
|
|
99
|
+
|
|
100
|
+
| Property | Type | Default Value | Description |
|
|
101
|
+
|:------------------|:------------|:------------------|:-----------------------------------------------------------------------|
|
|
102
|
+
| `height` | `string` | `"200px"` | CSS height of the slider component. |
|
|
103
|
+
| `font` | `string` | `"sans-serif"` | Font family used inside the Canvas rendering context. |
|
|
104
|
+
| `color` | `string` | `"#ffffff"` | Text color (accepts hex, rgb, rgba, or CSS color names). |
|
|
105
|
+
| `background` | `string` | `"transparent"` | Background color of the canvas container. |
|
|
106
|
+
| `maxTexts` | `number` | `10` | Maximum number of text elements allowed on screen simultaneously. |
|
|
107
|
+
| `maxAcceleration` | `number` | `3` | Maximum velocity cap for the text particles. |
|
|
108
|
+
| `headings` | `Heading[]` | `['EAST','WEST']` | Flow directions of the texts picked randomly on each page load. |
|
|
109
|
+
| `minSpawnMs` | `number` | `1000` | The minimum timespan of spawning a new text. |
|
|
110
|
+
| `maxSpawnMs` | `number` | `2500` | The maximum timespan of spawning a new text. |
|
|
111
|
+
| `marginTop` | `number` | `0` | Top boundary padding (in pixels) to restrict the spawn area. |
|
|
112
|
+
| `marginBottom` | `number` | `0` | Bottom boundary padding (in pixels) to restrict the spawn area. |
|
|
113
|
+
| `debug` | `boolean` | `false` | Enables red AABB bounding boxes and highlights the canvas clear zones. |
|
|
114
|
+
|
|
115
|
+
### Headings
|
|
116
|
+
|
|
117
|
+
The `headings` setting controls the possible flow directions. On each page load, one direction is picked randomly from the provided list.
|
|
118
|
+
|
|
119
|
+
Allowed values:
|
|
120
|
+
|
|
121
|
+
'NORTH' | 'NORTHEAST' | 'EAST' | 'SOUTHEAST' | 'SOUTH' | 'SOUTHWEST' | 'WEST' | 'NORTHWEST' | 'NONE'
|
|
122
|
+
|
|
123
|
+
### Spawn rates
|
|
124
|
+
|
|
125
|
+
The values for `minSpawnMs` and `maxSpawnMs` control the timespan between text texts spawn. Each cycle any random value between `minSpawnMs` and `maxSpawnMs` is picked and a new text is spawned after that period of time.
|
|
126
|
+
|
|
127
|
+
### Usage
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const settings: Partial<Settings> = {
|
|
131
|
+
headings: ['NORTH', 'SOUTHEAST', 'WEST'],
|
|
132
|
+
// ...
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Use `Partial<Settings>` to allow for optional properties.
|
|
111
137
|
|
|
112
138
|
## Architecture
|
|
113
139
|
|
|
114
140
|
The library follows a strict Model-View-Controller (MVC) inspired architecture to decouple data, state machine, and side effects:
|
|
115
141
|
|
|
116
142
|
- TextElement: Represents the physical text particle, housing position, speed, and bounding box.
|
|
117
|
-
- World: The state machine driving the physics engine, handling backward-loop garbage collection and processing 2D broad-phase collisions.
|
|
143
|
+
- World: The state machine driving the physics engine, handling backward-loop garbage collection, and processing 2D broad-phase collisions.
|
|
118
144
|
- TextFlowView: Isolated rendering layer that handles drawing text onto the high-DPI scaled 2D context.
|
|
119
145
|
- TextWriter: Factory class that manages the pseudo-randomized generation of new TextElement batches based on viewport requirements.
|
|
120
146
|
- TextFlowElement: The HTML Custom Element orchestrating the components and bridging browser lifecycles.
|
|
121
147
|
|
|
122
148
|
## License
|
|
123
149
|
|
|
124
|
-
MIT License
|
|
150
|
+
MIT License – feel free to use it in your personal portfolio or commercial projects!
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/types.d.ts
CHANGED
|
@@ -10,13 +10,14 @@ export interface BoundingBox {
|
|
|
10
10
|
left: number;
|
|
11
11
|
}
|
|
12
12
|
export interface Settings {
|
|
13
|
-
width: string;
|
|
14
13
|
height: string;
|
|
15
14
|
maxTexts: number;
|
|
16
15
|
maxAcceleration: number;
|
|
17
16
|
headings: Heading[];
|
|
18
17
|
marginTop: number;
|
|
19
18
|
marginBottom: number;
|
|
19
|
+
minSpawnMs: number;
|
|
20
|
+
maxSpawnMs: number;
|
|
20
21
|
texts: string[];
|
|
21
22
|
color: string;
|
|
22
23
|
background: string;
|
package/dist/textflow.es.js
CHANGED
|
@@ -92,7 +92,7 @@ var e = /* @__PURE__ */ function(e) {
|
|
|
92
92
|
_world;
|
|
93
93
|
_renderId = 0;
|
|
94
94
|
constructor(e, t, n, r, i) {
|
|
95
|
-
this._settings = e, this._target = t, this._canvas = n, this._context2d = r, this._world = i, this._target.style.display = "block", this._target.style.width =
|
|
95
|
+
this._settings = e, this._target = t, this._canvas = n, this._context2d = r, this._world = i, this._target.style.display = "block", this._target.style.width = "100%", this._target.style.height = this._settings.height, this._target.style.background = this._settings.background;
|
|
96
96
|
}
|
|
97
97
|
setFontSize(e) {
|
|
98
98
|
this._context2d.font = `${e}pt ${this._settings.font}`;
|
|
@@ -245,15 +245,15 @@ var e = /* @__PURE__ */ function(e) {
|
|
|
245
245
|
addText() {
|
|
246
246
|
if (!this._isRunning) return;
|
|
247
247
|
if (this._world.elements.length >= this._maxTexts) {
|
|
248
|
-
this._textId = this.randomSetTimeout(() => this.addText(),
|
|
248
|
+
this._textId = this.randomSetTimeout(() => this.addText(), this._settings.minSpawnMs, this._settings.maxSpawnMs);
|
|
249
249
|
return;
|
|
250
250
|
}
|
|
251
251
|
let e = this.random(this._minFontSize, this._maxFontSize), t = this._texts[this.random(this._texts.length)];
|
|
252
252
|
this._view.setFontSize(e);
|
|
253
253
|
let n = this._view.measureText(t), i = this.random(1, this._settings.maxAcceleration + 1), a, o, s = this._textDirection.x, c = this._textDirection.y;
|
|
254
|
-
a = s > 0 ? -n : s < 0 ? this._width : this.random(0, this._width - n), o = c > 0 ? -e + this._marginTop : c < 0 ? this._height
|
|
254
|
+
a = s > 0 ? -n : s < 0 ? this._width : this.random(0, this._width - n), o = c > 0 ? -e + this._marginTop : c < 0 ? this._height + this._marginBottom + e : this.random(this._marginTop, this._height - this._marginBottom - e), this._textDirection.isDiagonal && (Math.random() > .5 ? o = this.random(this._marginTop, this._height - this._marginBottom - e) : a = this.random(0, this._width - n));
|
|
255
255
|
let l = new r(a, o, i, n, e, t, e, this._textDirection, this._settings);
|
|
256
|
-
this._world.addElement(l), l.start(), this._textId = this.randomSetTimeout(() => this.addText(),
|
|
256
|
+
this._world.addElement(l), l.start(), this._textId = this.randomSetTimeout(() => this.addText(), this._settings.minSpawnMs, this._settings.maxSpawnMs);
|
|
257
257
|
}
|
|
258
258
|
resize(e, t) {
|
|
259
259
|
this._width = e, this._height = t, this.updateFontSizes();
|
|
@@ -337,14 +337,15 @@ var e = /* @__PURE__ */ function(e) {
|
|
|
337
337
|
get isDiagonal() {
|
|
338
338
|
return this.x !== 0 && this.y !== 0;
|
|
339
339
|
}
|
|
340
|
-
}, s = {
|
|
341
|
-
width: "100%",
|
|
340
|
+
}, s = /^(?:0|(?:\d+(?:\.\d+)?|\.\d+)(?:px|em|rem|%|vh|vw|vmin|vmax|cm|mm|in|pt|pc|ch|ex|lh|rlh|svh|lvh|dvh|svw|lvw|dvw))$/i, c = {
|
|
342
341
|
height: "200px",
|
|
343
342
|
maxTexts: 15,
|
|
344
343
|
maxAcceleration: 15,
|
|
345
344
|
headings: ["EAST", "WEST"],
|
|
346
345
|
marginTop: 0,
|
|
347
346
|
marginBottom: 0,
|
|
347
|
+
minSpawnMs: 1e3,
|
|
348
|
+
maxSpawnMs: 2500,
|
|
348
349
|
texts: [
|
|
349
350
|
"Add",
|
|
350
351
|
"your",
|
|
@@ -357,17 +358,17 @@ var e = /* @__PURE__ */ function(e) {
|
|
|
357
358
|
font: "sans-serif",
|
|
358
359
|
debug: !1
|
|
359
360
|
};
|
|
360
|
-
function
|
|
361
|
-
let t = { ...
|
|
362
|
-
if (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: ${
|
|
361
|
+
function l(e) {
|
|
362
|
+
let t = { ...c };
|
|
363
|
+
if (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: ${c.maxTexts}`), typeof e.maxAcceleration == "number" && e.maxAcceleration > 0 && (t.maxAcceleration = e.maxAcceleration), Array.isArray(e.headings)) {
|
|
363
364
|
let n = e.headings.filter((e) => typeof e == "string" && e.toUpperCase() in a);
|
|
364
365
|
n.length > 0 && (t.headings = n);
|
|
365
366
|
}
|
|
366
|
-
return 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;
|
|
367
|
+
return typeof e.marginTop == "number" && e.marginTop >= 0 && (t.marginTop = e.marginTop), typeof e.marginBottom == "number" && e.marginBottom >= 0 && (t.marginBottom = e.marginBottom), typeof e.minSpawnMs == "number" && e.minSpawnMs >= 15 && (t.minSpawnMs = e.minSpawnMs), typeof e.maxSpawnMs == "number" && e.maxSpawnMs >= 15 && e.maxSpawnMs >= e.minSpawnMs && (t.maxSpawnMs = e.maxSpawnMs), 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), typeof e.height == "string" && s.test(e.height.trim()) && (t.height = e.height), e.debug !== void 0 && (t.debug = !!e.debug), t;
|
|
367
368
|
}
|
|
368
369
|
//#endregion
|
|
369
370
|
//#region src/index.ts
|
|
370
|
-
var
|
|
371
|
+
var u = class extends HTMLElement {
|
|
371
372
|
active = !1;
|
|
372
373
|
resizeObserver = null;
|
|
373
374
|
world;
|
|
@@ -381,8 +382,10 @@ var l = class extends HTMLElement {
|
|
|
381
382
|
console.error("Could not get context2d");
|
|
382
383
|
return;
|
|
383
384
|
}
|
|
384
|
-
let a = this.dataset.settings,
|
|
385
|
-
this.
|
|
385
|
+
let a = this.dataset.settings, s = l(a ? JSON.parse(a) : c), u = this.dataset.texts, d = u ? JSON.parse(u) : s.texts, f = this.generateScreenReaderTextList(d);
|
|
386
|
+
this.querySelector("ul.sr-only")?.remove(), e.appendChild(f);
|
|
387
|
+
let p = o.randomDirection(s.headings);
|
|
388
|
+
this.world = new t(p), this.view = new n(s, this, e, r, this.world), this.textWriter = new i(s, p, this.offsetWidth, this.offsetHeight, this.world, this.view, d), this.startLoop(), document.addEventListener("visibilitychange", this.visibilityChange), this.resizeObserver = new ResizeObserver(() => {
|
|
386
389
|
this.resize();
|
|
387
390
|
}), this.resizeObserver.observe(this);
|
|
388
391
|
}
|
|
@@ -404,9 +407,16 @@ var l = class extends HTMLElement {
|
|
|
404
407
|
visibilityChange = () => {
|
|
405
408
|
document.hidden ? this.stopLoop() : this.startLoop();
|
|
406
409
|
};
|
|
410
|
+
generateScreenReaderTextList(e) {
|
|
411
|
+
let t = document.createElement("ul");
|
|
412
|
+
return t.className = "sr-only", t.setAttribute("aria-label", "Currently flowing words on the canvas"), e.forEach((e) => {
|
|
413
|
+
let n = document.createElement("li");
|
|
414
|
+
n.textContent = e, t.appendChild(n);
|
|
415
|
+
}), t;
|
|
416
|
+
}
|
|
407
417
|
};
|
|
408
|
-
function
|
|
409
|
-
typeof window < "u" && !customElements.get(e) && customElements.define(e,
|
|
418
|
+
function d(e = "textflow-container") {
|
|
419
|
+
typeof window < "u" && !customElements.get(e) && customElements.define(e, u);
|
|
410
420
|
}
|
|
411
421
|
//#endregion
|
|
412
|
-
export {
|
|
422
|
+
export { u as TextFlowElement, d as registerTextFlowSlider };
|
package/dist/textflow.umd.js
CHANGED
|
@@ -1,5 +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=function(e){return e[e.Horizontal=0]=`Horizontal`,e[e.Vertical=1]=`Vertical`,e}({}),n=class{_textDirection;updateId=void 0;_isRunning=!1;_elements=[];_width=0;_height=0;constructor(e){this._textDirection=e}get elements(){return this._elements}addElement(e){this._elements.push(e)}resize(e,t){this._width=e,this._height=t}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,n){let r=[e.positionX,e.positionX+e.width,n.positionX,n.positionX+n.width],i=[e.positionY,e.positionY+e.height,n.positionY,n.positionY+n.height],a=(e,t)=>e-t;return r.sort(a),i.sort(a),r[2]-r[1]<i[2]-i[1]?t.Horizontal:t.Vertical}handleCollision(e,n){switch(this.getCollisionAxis(e,n)){case t.Horizontal:this.handleXCollision(e,n);break;case t.Vertical:this.handleYCollision(e,n);break}}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();let n=!1;this._textDirection.x!==0&&(n=t.isCollidingWithBorderX(this._textDirection.x,this._width)),!n&&this._textDirection.y!==0&&(n=t.isCollidingWithBorderY(this._textDirection.y,this._height)),n&&this._elements.splice(e,1)}}updateWorld(){this._isRunning&&(this.detectCollisions(),this.updateElements(),this.updateId=window.setTimeout(()=>this.updateWorld(),1e3/60))}},r=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)}}},i=class{_positionX;_positionY;_width;_height;_text;_fontSize;_textDirection;_accelerateId=void 0;_breakDownId=void 0;_accelerationX=0;_accelerationY=0;_velocityX=0;_velocityY=0;_acceleration=0;_boundingBox;_font;_color;_maxAcceleration;constructor(e,t,n,r,i,a,o,s,c){this._positionX=e,this._positionY=t,this._width=r,this._height=i,this._text=a,this._fontSize=o,this._textDirection=s,this._maxAcceleration=n,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 accelerationY(){return this._accelerationY}get fontSize(){return this._fontSize}get font(){return this._font}get color(){return this._color}get text(){return this._text}isCollidingWithBorderX(e,t){if(e<0){if(this._boundingBox.right<0)return!0}else if(this._boundingBox.left>t)return!0;return!1}isCollidingWithBorderY(e,t){if(e<0){if(this._boundingBox.bottom<0)return!0}else if(this._boundingBox.top>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,this._accelerationY+=this._acceleration*this._textDirection.y,Math.sqrt(this._accelerationX**2+this._accelerationY**2)<this._maxAcceleration?this._accelerateId=window.setTimeout(()=>this.accelerate(),40):(this._accelerationX=this._maxAcceleration*this._textDirection.x,this._accelerationY=this._maxAcceleration*this._textDirection.y,this._accelerateId=void 0)}breakDown(){this._accelerationX+=.1*this._textDirection.x,this._accelerationY+=.1*this._textDirection.y,Math.sqrt(this._accelerationX**2+this._accelerationY**2)>this._maxAcceleration?this._breakDownId=window.setTimeout(()=>this.breakDown(),40):(this._accelerationX=this._maxAcceleration*this._textDirection.x,this._accelerationY=this._maxAcceleration*this._textDirection.y,this._breakDownId=void 0,this._accelerateId=void 0)}boostUpByCollision=e=>{this._textDirection.x!==0&&(this._accelerationX+=(e.accelerationX-this._accelerationX)*(e.fontSize/this._fontSize)),this._textDirection.y!==0&&(this._accelerationY+=(e.accelerationY-this._accelerationY)*(e.fontSize/this._fontSize)),clearTimeout(this._accelerateId),clearTimeout(this._breakDownId),this.breakDown()};breakDownByCollision=e=>{this._textDirection.x!==0&&(this._accelerationX-=this._accelerationX*(e.fontSize/this._fontSize)),this._textDirection.y!==0&&(this._accelerationY-=this._accelerationY*(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()}},a=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)];this._view.setFontSize(e);let n=this._view.measureText(t),r=this.random(1,this._settings.maxAcceleration+1),a,o,s=this._textDirection.x,c=this._textDirection.y;a=s>0?-n:s<0?this._width:this.random(0,this._width-n),o=c>0?-e+this._marginTop:c<0?this._height-this._marginBottom:this.random(this._marginTop,this._height-this._marginBottom-e),this._textDirection.isDiagonal&&(Math.random()>.5?o=this.random(this._marginTop,this._height-this._marginBottom-e):a=this.random(0,this._width-n));let l=new i(a,o,r,n,e,t,e,this._textDirection,this._settings);this._world.addElement(l),l.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))}},o={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},NONE:{x:0,y:0}},s=class e{heading;constructor(e){this.heading=e}static from(t){return new e(t)}static randomDirection(t=[`EAST`,`WEST`]){let n=t[Math.floor(Math.random()*t.length)];return e.from(n)}get x(){return o[this.heading].x}get y(){return o[this.heading].y}get isVertical(){return this.x===0&&this.y!==0}get isHorizontal(){return this.y===0&&this.x!==0}get isDiagonal(){return this.x!==0&&this.y!==0}},c={width:`100%`,height:`200px`,maxTexts:15,maxAcceleration:15,headings:[`EAST`,`WEST`],marginTop:0,marginBottom:0,texts:[`Add`,`your`,`own`,`texts`,`here`],color:`#333`,background:`rgba(255, 255, 255, 1)`,font:`sans-serif`,debug:!1};function l(e){let t={...c};if(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: ${c.maxTexts}`),typeof e.maxAcceleration==`number`&&e.maxAcceleration>0&&(t.maxAcceleration=e.maxAcceleration),Array.isArray(e.headings)){let n=e.headings.filter(e=>typeof e==`string`&&e.toUpperCase()in o);n.length>0&&(t.headings=n)}return 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 u=class extends HTMLElement{active=!1;resizeObserver=null;world;view;textWriter;animationFrameId=null;connectedCallback(){this.innerHTML=`
|
|
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=function(e){return e[e.Horizontal=0]=`Horizontal`,e[e.Vertical=1]=`Vertical`,e}({}),n=class{_textDirection;updateId=void 0;_isRunning=!1;_elements=[];_width=0;_height=0;constructor(e){this._textDirection=e}get elements(){return this._elements}addElement(e){this._elements.push(e)}resize(e,t){this._width=e,this._height=t}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,n){let r=[e.positionX,e.positionX+e.width,n.positionX,n.positionX+n.width],i=[e.positionY,e.positionY+e.height,n.positionY,n.positionY+n.height],a=(e,t)=>e-t;return r.sort(a),i.sort(a),r[2]-r[1]<i[2]-i[1]?t.Horizontal:t.Vertical}handleCollision(e,n){switch(this.getCollisionAxis(e,n)){case t.Horizontal:this.handleXCollision(e,n);break;case t.Vertical:this.handleYCollision(e,n);break}}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();let n=!1;this._textDirection.x!==0&&(n=t.isCollidingWithBorderX(this._textDirection.x,this._width)),!n&&this._textDirection.y!==0&&(n=t.isCollidingWithBorderY(this._textDirection.y,this._height)),n&&this._elements.splice(e,1)}}updateWorld(){this._isRunning&&(this.detectCollisions(),this.updateElements(),this.updateId=window.setTimeout(()=>this.updateWorld(),1e3/60))}},r=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=`100%`,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)}}},i=class{_positionX;_positionY;_width;_height;_text;_fontSize;_textDirection;_accelerateId=void 0;_breakDownId=void 0;_accelerationX=0;_accelerationY=0;_velocityX=0;_velocityY=0;_acceleration=0;_boundingBox;_font;_color;_maxAcceleration;constructor(e,t,n,r,i,a,o,s,c){this._positionX=e,this._positionY=t,this._width=r,this._height=i,this._text=a,this._fontSize=o,this._textDirection=s,this._maxAcceleration=n,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 accelerationY(){return this._accelerationY}get fontSize(){return this._fontSize}get font(){return this._font}get color(){return this._color}get text(){return this._text}isCollidingWithBorderX(e,t){if(e<0){if(this._boundingBox.right<0)return!0}else if(this._boundingBox.left>t)return!0;return!1}isCollidingWithBorderY(e,t){if(e<0){if(this._boundingBox.bottom<0)return!0}else if(this._boundingBox.top>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,this._accelerationY+=this._acceleration*this._textDirection.y,Math.sqrt(this._accelerationX**2+this._accelerationY**2)<this._maxAcceleration?this._accelerateId=window.setTimeout(()=>this.accelerate(),40):(this._accelerationX=this._maxAcceleration*this._textDirection.x,this._accelerationY=this._maxAcceleration*this._textDirection.y,this._accelerateId=void 0)}breakDown(){this._accelerationX+=.1*this._textDirection.x,this._accelerationY+=.1*this._textDirection.y,Math.sqrt(this._accelerationX**2+this._accelerationY**2)>this._maxAcceleration?this._breakDownId=window.setTimeout(()=>this.breakDown(),40):(this._accelerationX=this._maxAcceleration*this._textDirection.x,this._accelerationY=this._maxAcceleration*this._textDirection.y,this._breakDownId=void 0,this._accelerateId=void 0)}boostUpByCollision=e=>{this._textDirection.x!==0&&(this._accelerationX+=(e.accelerationX-this._accelerationX)*(e.fontSize/this._fontSize)),this._textDirection.y!==0&&(this._accelerationY+=(e.accelerationY-this._accelerationY)*(e.fontSize/this._fontSize)),clearTimeout(this._accelerateId),clearTimeout(this._breakDownId),this.breakDown()};breakDownByCollision=e=>{this._textDirection.x!==0&&(this._accelerationX-=this._accelerationX*(e.fontSize/this._fontSize)),this._textDirection.y!==0&&(this._accelerationY-=this._accelerationY*(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()}},a=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(),this._settings.minSpawnMs,this._settings.maxSpawnMs);return}let e=this.random(this._minFontSize,this._maxFontSize),t=this._texts[this.random(this._texts.length)];this._view.setFontSize(e);let n=this._view.measureText(t),r=this.random(1,this._settings.maxAcceleration+1),a,o,s=this._textDirection.x,c=this._textDirection.y;a=s>0?-n:s<0?this._width:this.random(0,this._width-n),o=c>0?-e+this._marginTop:c<0?this._height+this._marginBottom+e:this.random(this._marginTop,this._height-this._marginBottom-e),this._textDirection.isDiagonal&&(Math.random()>.5?o=this.random(this._marginTop,this._height-this._marginBottom-e):a=this.random(0,this._width-n));let l=new i(a,o,r,n,e,t,e,this._textDirection,this._settings);this._world.addElement(l),l.start(),this._textId=this.randomSetTimeout(()=>this.addText(),this._settings.minSpawnMs,this._settings.maxSpawnMs)}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))}},o={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},NONE:{x:0,y:0}},s=class e{heading;constructor(e){this.heading=e}static from(t){return new e(t)}static randomDirection(t=[`EAST`,`WEST`]){let n=t[Math.floor(Math.random()*t.length)];return e.from(n)}get x(){return o[this.heading].x}get y(){return o[this.heading].y}get isVertical(){return this.x===0&&this.y!==0}get isHorizontal(){return this.y===0&&this.x!==0}get isDiagonal(){return this.x!==0&&this.y!==0}},c=/^(?:0|(?:\d+(?:\.\d+)?|\.\d+)(?:px|em|rem|%|vh|vw|vmin|vmax|cm|mm|in|pt|pc|ch|ex|lh|rlh|svh|lvh|dvh|svw|lvw|dvw))$/i,l={height:`200px`,maxTexts:15,maxAcceleration:15,headings:[`EAST`,`WEST`],marginTop:0,marginBottom:0,minSpawnMs:1e3,maxSpawnMs:2500,texts:[`Add`,`your`,`own`,`texts`,`here`],color:`#333`,background:`rgba(255, 255, 255, 1)`,font:`sans-serif`,debug:!1};function u(e){let t={...l};if(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: ${l.maxTexts}`),typeof e.maxAcceleration==`number`&&e.maxAcceleration>0&&(t.maxAcceleration=e.maxAcceleration),Array.isArray(e.headings)){let n=e.headings.filter(e=>typeof e==`string`&&e.toUpperCase()in o);n.length>0&&(t.headings=n)}return typeof e.marginTop==`number`&&e.marginTop>=0&&(t.marginTop=e.marginTop),typeof e.marginBottom==`number`&&e.marginBottom>=0&&(t.marginBottom=e.marginBottom),typeof e.minSpawnMs==`number`&&e.minSpawnMs>=15&&(t.minSpawnMs=e.minSpawnMs),typeof e.maxSpawnMs==`number`&&e.maxSpawnMs>=15&&e.maxSpawnMs>=e.minSpawnMs&&(t.maxSpawnMs=e.maxSpawnMs),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),typeof e.height==`string`&&c.test(e.height.trim())&&(t.height=e.height),e.debug!==void 0&&(t.debug=!!e.debug),t}var d=class extends HTMLElement{active=!1;resizeObserver=null;world;view;textWriter;animationFrameId=null;connectedCallback(){this.innerHTML=`
|
|
2
2
|
<div class="slider-container">
|
|
3
3
|
<canvas class="physics-canvas"></canvas>
|
|
4
4
|
</div>
|
|
5
|
-
`;let e=this.querySelector(`.physics-canvas`),t=e.getContext(`2d`);if(!t){console.error(`Could not get context2d`);return}let i=this.dataset.settings,o=
|
|
5
|
+
`;let e=this.querySelector(`.physics-canvas`),t=e.getContext(`2d`);if(!t){console.error(`Could not get context2d`);return}let i=this.dataset.settings,o=u(i?JSON.parse(i):l),c=this.dataset.texts,d=c?JSON.parse(c):o.texts,f=this.generateScreenReaderTextList(d);this.querySelector(`ul.sr-only`)?.remove(),e.appendChild(f);let p=s.randomDirection(o.headings);this.world=new n(p),this.view=new r(o,this,e,t,this.world),this.textWriter=new a(o,p,this.offsetWidth,this.offsetHeight,this.world,this.view,d),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.offsetHeight),this.textWriter.resize(this.offsetWidth,this.offsetHeight)}visibilityChange=()=>{document.hidden?this.stopLoop():this.startLoop()};generateScreenReaderTextList(e){let t=document.createElement(`ul`);return t.className=`sr-only`,t.setAttribute(`aria-label`,`Currently flowing words on the canvas`),e.forEach(e=>{let n=document.createElement(`li`);n.textContent=e,t.appendChild(n)}),t}};function f(e=`textflow-container`){typeof window<`u`&&!customElements.get(e)&&customElements.define(e,d)}e.TextFlowElement=d,e.registerTextFlowSlider=f});
|