@khesira/textflow 1.0.4 → 1.1.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 CHANGED
@@ -95,30 +95,56 @@ const settings: Settings = {
95
95
 
96
96
  ## Configuration (Settings)
97
97
 
98
- | Property | Type | Default Value | Description |
99
- |:------------------|:-----------|:------------------|:-----------------------------------------------------------------------|
100
- | `width` | `string` | `"100%"` | CSS width of the slider component (e.g., `"100%"`, `"400px"`). |
101
- | `height` | `string` | `"150px"` | CSS height of the slider component. |
102
- | `font` | `string` | `"sans-serif"` | Font family used inside the Canvas rendering context. |
103
- | `color` | `string` | `"#ffffff"` | Text color (accepts hex, rgb, rgba, or CSS color names). |
104
- | `background` | `string` | `"transparent"` | Background color of the canvas container. |
105
- | `maxTexts` | `number` | `10` | Maximum number of text elements allowed on screen simultaneously. |
106
- | `maxAcceleration` | `number` | `3` | Maximum velocity cap for the text particles. |
107
- | `headings` | `string[]` | `['EAST','WEST']` | Flow directions of the texts picked randomly on each page load. |
108
- | `marginTop` | `number` | `0` | Top boundary padding (in pixels) to restrict the spawn area. |
109
- | `marginBottom` | `number` | `0` | Bottom boundary padding (in pixels) to restrict the spawn area. |
110
- | `debug` | `boolean` | `false` | Enables red AABB bounding boxes and highlights the canvas clear zones. |
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 - feel free to use it in your personal portfolio or commercial projects!
150
+ MIT License feel free to use it in your personal portfolio or commercial projects!
@@ -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;
@@ -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 = this._settings.width, this._target.style.height = this._settings.height, this._target.style.background = this._settings.background;
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(), 1e3, 2500);
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 - 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));
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(), 1e3, 2500);
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 c(e) {
361
- let t = { ...s };
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: ${s.maxTexts}`), typeof e.maxAcceleration == "number" && e.maxAcceleration > 0 && (t.maxAcceleration = e.maxAcceleration), Array.isArray(e.headings)) {
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 l = class extends HTMLElement {
371
+ var u = class extends HTMLElement {
371
372
  active = !1;
372
373
  resizeObserver = null;
373
374
  world;
@@ -381,8 +382,8 @@ var l = class extends HTMLElement {
381
382
  console.error("Could not get context2d");
382
383
  return;
383
384
  }
384
- let a = this.dataset.settings, l = c(a ? JSON.parse(a) : s), u = this.dataset.texts, d = u ? JSON.parse(u) : l.texts, f = o.randomDirection(l.headings);
385
- this.world = new t(f), this.view = new n(l, this, e, r, this.world), this.textWriter = new i(l, f, this.offsetWidth, this.offsetHeight, this.world, this.view, d), this.startLoop(), document.addEventListener("visibilitychange", this.visibilityChange), this.resizeObserver = new ResizeObserver(() => {
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 = o.randomDirection(s.headings);
386
+ this.world = new t(f), this.view = new n(s, this, e, r, this.world), this.textWriter = new i(s, f, this.offsetWidth, this.offsetHeight, this.world, this.view, d), this.startLoop(), document.addEventListener("visibilitychange", this.visibilityChange), this.resizeObserver = new ResizeObserver(() => {
386
387
  this.resize();
387
388
  }), this.resizeObserver.observe(this);
388
389
  }
@@ -405,8 +406,8 @@ var l = class extends HTMLElement {
405
406
  document.hidden ? this.stopLoop() : this.startLoop();
406
407
  };
407
408
  };
408
- function u(e = "textflow-container") {
409
- typeof window < "u" && !customElements.get(e) && customElements.define(e, l);
409
+ function d(e = "textflow-container") {
410
+ typeof window < "u" && !customElements.get(e) && customElements.define(e, u);
410
411
  }
411
412
  //#endregion
412
- export { l as TextFlowElement, u as registerTextFlowSlider };
413
+ export { u as TextFlowElement, d as registerTextFlowSlider };
@@ -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=l(i?JSON.parse(i):c),u=this.dataset.texts,d=u?JSON.parse(u):o.texts,f=s.randomDirection(o.headings);this.world=new n(f),this.view=new r(o,this,e,t,this.world),this.textWriter=new a(o,f,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()}};function d(e=`textflow-container`){typeof window<`u`&&!customElements.get(e)&&customElements.define(e,u)}e.TextFlowElement=u,e.registerTextFlowSlider=d});
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=s.randomDirection(o.headings);this.world=new n(f),this.view=new r(o,this,e,t,this.world),this.textWriter=new a(o,f,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()}};function f(e=`textflow-container`){typeof window<`u`&&!customElements.get(e)&&customElements.define(e,d)}e.TextFlowElement=d,e.registerTextFlowSlider=f});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khesira/textflow",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "license": "MIT",
5
5
  "author": "Mika Jünger",
6
6
  "description": "A framework agnostic canvas physics text slider",