text-slicer 1.4.0-dev.9 → 1.4.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 +139 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
<br>
|
|
2
1
|
<p align="center"><strong>text-slicer</strong></p>
|
|
3
2
|
|
|
4
3
|
<div align="center">
|
|
@@ -9,71 +8,168 @@
|
|
|
9
8
|
|
|
10
9
|
</div>
|
|
11
10
|
|
|
12
|
-
<p align="center">
|
|
13
|
-
<p align="center"><sup>1.5kB gzipped</sup></p>
|
|
11
|
+
<p align="center">Split text inside an HTML element into words and/or characters, wrapping each in a dedicated <code><span></code>. Built for robust animation pipelines and i18n-safe rendering.</p>
|
|
14
12
|
<p align="center"><a href="https://codepen.io/ux-ui/full/vYMoGoG">Demo</a></p>
|
|
15
13
|
<br>
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
## Install
|
|
18
16
|
|
|
19
|
-
```
|
|
17
|
+
```bash
|
|
20
18
|
yarn add text-slicer
|
|
19
|
+
# or
|
|
20
|
+
npm i text-slicer
|
|
21
21
|
```
|
|
22
22
|
<br>
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { TextSlicer } from 'text-slicer';
|
|
28
|
+
|
|
29
|
+
const slicer = new TextSlicer({ container: '.text-slicer' });
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
slicer.init();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Initialize per element:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
document.querySelectorAll('.text-slicer').forEach((el) => {
|
|
38
|
+
const slicer = new TextSlicer({ container: el });
|
|
39
|
+
|
|
40
|
+
slicer.init();
|
|
41
|
+
});
|
|
28
42
|
```
|
|
29
43
|
<br>
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
### Types
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
export type SplitMode = 'words' | 'chars' | 'both';
|
|
51
|
+
|
|
52
|
+
export interface TextSlicerOptions {
|
|
53
|
+
container?: HTMLElement | string;
|
|
54
|
+
splitMode?: SplitMode;
|
|
55
|
+
cssVariables?: boolean;
|
|
56
|
+
dataAttributes?: boolean;
|
|
57
|
+
/** Keep dedicated whitespace nodes between words (for precise animations). Default: true */
|
|
58
|
+
keepWhitespaceNodes?: boolean;
|
|
59
|
+
/** Freeze measured word widths to avoid reflow jitter on responsive layouts. Default: false */
|
|
60
|
+
freezeWordWidths?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface TextSlicerMetrics {
|
|
64
|
+
wordTotal: number;
|
|
65
|
+
charTotal: number;
|
|
66
|
+
renderedAt: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface TextSlicerCallbacks {
|
|
70
|
+
onAfterRender?: (metrics: TextSlicerMetrics) => void;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Classnames & CSS vars
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { CLASSNAMES } from 'text-slicer';
|
|
32
78
|
|
|
33
|
-
|
|
34
|
-
|
|
79
|
+
// Classes applied to generated spans
|
|
80
|
+
CLASSNAMES.word // 'ts-word'
|
|
81
|
+
CLASSNAMES.char // 'ts-char'
|
|
82
|
+
CLASSNAMES.whitespace // 'ts-whitespace'
|
|
35
83
|
|
|
36
|
-
|
|
84
|
+
// CSS variables placed on container and items (when cssVariables: true)
|
|
85
|
+
--word-total
|
|
86
|
+
--char-total
|
|
87
|
+
--word-index
|
|
88
|
+
--char-index
|
|
37
89
|
```
|
|
38
90
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
container: '.text-slicer',
|
|
44
|
-
splitMode: 'both',
|
|
45
|
-
cssVariables: true,
|
|
46
|
-
dataAttributes: true,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
textSlicer.init();
|
|
50
|
-
});
|
|
91
|
+
### Constructor
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
new TextSlicer(options?: TextSlicerOptions, callbacks?: TextSlicerCallbacks)
|
|
51
95
|
```
|
|
52
96
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
97
|
+
### Methods
|
|
98
|
+
|
|
99
|
+
- `init(): void` – Perform initial split.
|
|
100
|
+
- `reinit(newText?: string, nextOpts?: Partial<TextSlicerOptions>): void` – Update text and/or options and re-split.
|
|
101
|
+
- `updateOptions(next: Partial<TextSlicerOptions>): void` – Merge options and re-split (if mounted).
|
|
102
|
+
- `clear(): void` – Remove generated nodes and unfreeze widths.
|
|
103
|
+
- `split(): void` – (Re)build DOM (called internally by `init`/`reinit`/`updateOptions`).
|
|
104
|
+
- `destroy(): void` – Detach observers, clear DOM, and mark unmounted.
|
|
105
|
+
- `get metrics(): TextSlicerMetrics` – Read-only metrics collected on the last render.
|
|
106
|
+
|
|
107
|
+
### Options in detail
|
|
108
|
+
|
|
109
|
+
- `splitMode` – `'words' | 'chars' | 'both'`. When `'both'`, each word is wrapped and further split into graphemes.
|
|
110
|
+
- `cssVariables` – When `true`, indexes and totals are exposed as CSS custom properties for stagger animations.
|
|
111
|
+
- `dataAttributes` – When `true`, adds `data-word` / `data-char` attributes.
|
|
112
|
+
- `keepWhitespaceNodes` – When `true`, explicit whitespace nodes are inserted between words (class `ts-whitespace`).
|
|
113
|
+
- `freezeWordWidths` – When `true`, measured widths of `.ts-word` nodes are frozen (after fonts load + next frame) and
|
|
114
|
+
kept in sync on container/window resize to prevent layout jitter during animations.
|
|
115
|
+
|
|
116
|
+
### i18n-friendly grapheme splitting
|
|
117
|
+
|
|
118
|
+
Characters are split using `Intl.Segmenter` (when available) with `{ granularity: 'grapheme' }`, so compound emoji and
|
|
119
|
+
grapheme clusters render as expected. Environments without `Intl.Segmenter` gracefully fall back to `Array.from(text)`.
|
|
120
|
+
|
|
121
|
+
### Callbacks
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const slicer = new TextSlicer(
|
|
125
|
+
{ container: '.title', cssVariables: true },
|
|
126
|
+
{
|
|
127
|
+
onAfterRender(metrics) {
|
|
128
|
+
// e.g. attach animation based on metrics.charTotal
|
|
129
|
+
console.log(metrics);
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
slicer.init();
|
|
134
|
+
```
|
|
60
135
|
|
|
61
|
-
|
|
62
|
-
|
|
136
|
+
### Responsive width freezing
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const slicer = new TextSlicer({
|
|
140
|
+
container: '.headline',
|
|
141
|
+
splitMode: 'both',
|
|
142
|
+
freezeWordWidths: true,
|
|
63
143
|
});
|
|
144
|
+
slicer.init();
|
|
64
145
|
```
|
|
65
|
-
<br>
|
|
66
146
|
|
|
67
|
-
|
|
147
|
+
When enabled, widths are measured after fonts are ready and then frozen (`flex: 0 0 auto; width: <px>`). A `ResizeObserver`
|
|
148
|
+
watches the container and a `resize` handler remeasures on viewport changes.
|
|
149
|
+
<br>
|
|
68
150
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
151
|
+
## CSS usage example
|
|
152
|
+
|
|
153
|
+
```css
|
|
154
|
+
.ts-char {
|
|
155
|
+
display: inline-block;
|
|
156
|
+
transform: translateY(0.75em);
|
|
157
|
+
opacity: 0;
|
|
158
|
+
transition: transform 400ms ease, opacity 400ms ease;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.ts-char.appear {
|
|
162
|
+
transform: translateY(0);
|
|
163
|
+
opacity: 1;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* stagger via CSS variables */
|
|
167
|
+
.ts-char {
|
|
168
|
+
transition-delay: calc(var(--char-index, 0) * 10ms);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
75
171
|
<br>
|
|
76
172
|
|
|
77
|
-
|
|
173
|
+
## License
|
|
78
174
|
|
|
79
|
-
|
|
175
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "text-slicer",
|
|
3
|
-
"version": "1.4.0
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "TextSlicer is designed to split text within an HTML element into separate words and/or characters, wrapping each word and/or character in separate span elements.",
|
|
5
5
|
"author": "ux-ui.pro",
|
|
6
6
|
"license": "MIT",
|