fetta 1.4.2 → 1.4.3
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 +156 -85
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,11 +6,11 @@ Split text into characters, words, and lines while preserving the original typog
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Kerning Compensation** —
|
|
10
|
-
- **Nested Elements** — Preserves
|
|
11
|
-
- **Line Detection** —
|
|
12
|
-
- **Dash Handling** — Allows text to wrap naturally after em-dashes, en-dashes, and
|
|
13
|
-
- **Auto Re-split** —
|
|
9
|
+
- **Kerning Compensation** — Maintains original character spacing when splitting by chars
|
|
10
|
+
- **Nested Elements** — Preserves `<a>`, `<em>`, `<strong>` and other inline elements with all attributes
|
|
11
|
+
- **Line Detection** — Automatically groups words into lines
|
|
12
|
+
- **Dash Handling** — Allows text to wrap naturally after em-dashes, en-dashes, hyphens, and slashes
|
|
13
|
+
- **Auto Re-split** — Re-splits on container resize
|
|
14
14
|
- **Auto-Revert** — Restore original HTML after animations
|
|
15
15
|
- **Masking** — Wrap elements in clip containers for reveal animations
|
|
16
16
|
- **Emoji Support** — Properly handles compound emojis and complex Unicode characters
|
|
@@ -26,6 +26,8 @@ Split text into characters, words, and lines while preserving the original typog
|
|
|
26
26
|
npm install fetta
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
**Bundle size**: ~3.9 kB (`fetta/core`) / ~4.8 kB (`fetta/react`) — minified + compressed
|
|
30
|
+
|
|
29
31
|
## Quick Start
|
|
30
32
|
|
|
31
33
|
### Vanilla JavaScript
|
|
@@ -79,15 +81,15 @@ const result = splitText(element, options);
|
|
|
79
81
|
| `charClass` | `string` | `"split-char"` | CSS class for character elements |
|
|
80
82
|
| `wordClass` | `string` | `"split-word"` | CSS class for word elements |
|
|
81
83
|
| `lineClass` | `string` | `"split-line"` | CSS class for line elements |
|
|
82
|
-
| `mask` | `
|
|
84
|
+
| `mask` | `string` | — | Wrap elements in `overflow: clip` container: `"chars"`, `"words"`, or `"lines"` |
|
|
83
85
|
| `autoSplit` | `boolean` | `false` | Re-split on container resize |
|
|
84
86
|
| `onResize` | `function` | — | Callback after resize re-split |
|
|
85
87
|
| `onSplit` | `function` | — | Callback after initial split |
|
|
86
88
|
| `revertOnComplete` | `boolean` | `false` | Auto-revert when animation completes |
|
|
87
89
|
| `propIndex` | `boolean` | `false` | Add CSS custom properties: `--char-index`, `--word-index`, `--line-index` |
|
|
88
90
|
| `disableKerning` | `boolean` | `false` | Skip kerning compensation (no margin adjustments) |
|
|
89
|
-
| `initialStyles` | `object` | — | Apply initial inline styles to chars/words/lines after split |
|
|
90
|
-
| `initialClasses` | `object` | — | Apply initial CSS classes to chars/words/lines after split |
|
|
91
|
+
| `initialStyles` | `object` | — | Apply initial inline styles to chars/words/lines after split. Values can be objects or `(el, index) => object` functions |
|
|
92
|
+
| `initialClasses` | `object` | — | Apply initial CSS classes to chars/words/lines after split. Values can be strings or `(el, index) => string` functions |
|
|
91
93
|
|
|
92
94
|
#### Return Value
|
|
93
95
|
|
|
@@ -110,19 +112,36 @@ import { SplitText } from 'fetta/react';
|
|
|
110
112
|
|
|
111
113
|
| Prop | Type | Default | Description |
|
|
112
114
|
|------|------|---------|-------------|
|
|
113
|
-
| `children` | `ReactElement` | — | Single element to split |
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
115
|
+
| `children` | `ReactElement` | — | Single React element to split |
|
|
116
|
+
| `as` | `keyof JSX.IntrinsicElements` | `"div"` | Wrapper element type |
|
|
117
|
+
| `className` | `string` | — | Class name for wrapper element |
|
|
118
|
+
| `style` | `CSSProperties` | — | Additional styles for wrapper element |
|
|
119
|
+
| `ref` | `Ref<HTMLElement>` | — | Ref to container element |
|
|
120
|
+
| `onSplit` | `(result) => void` | — | Called after text is split |
|
|
121
|
+
| `onResize` | `(result) => void` | — | Called on autoSplit re-split |
|
|
122
|
+
| `options` | `SplitOptions` | — | Split options (type, classes, mask, propIndex, disableKerning) |
|
|
117
123
|
| `autoSplit` | `boolean` | `false` | Re-split on container resize |
|
|
118
124
|
| `revertOnComplete` | `boolean` | `false` | Revert after animation completes |
|
|
119
125
|
| `inView` | `boolean \| InViewOptions` | `false` | Enable viewport detection |
|
|
120
|
-
| `onInView` | `
|
|
121
|
-
| `onLeaveView` | `
|
|
122
|
-
| `initialStyles` | `object` | — | Apply initial inline styles to chars/words/lines |
|
|
123
|
-
| `initialClasses` | `object` | — | Apply initial CSS classes to chars/words/lines |
|
|
126
|
+
| `onInView` | `(result) => void` | — | Called when element enters viewport |
|
|
127
|
+
| `onLeaveView` | `(result) => void` | — | Called when element leaves viewport |
|
|
128
|
+
| `initialStyles` | `object` | — | Apply initial inline styles to chars/words/lines. Values can be objects or `(el, index) => object` functions |
|
|
129
|
+
| `initialClasses` | `object` | — | Apply initial CSS classes to chars/words/lines. Values can be strings or `(el, index) => string` functions |
|
|
124
130
|
| `resetOnLeave` | `boolean` | `false` | Re-apply initialStyles/initialClasses when leaving viewport |
|
|
125
131
|
|
|
132
|
+
#### Callback Signature
|
|
133
|
+
|
|
134
|
+
All callbacks (`onSplit`, `onResize`, `onInView`, `onLeaveView`) receive the same result object:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
{
|
|
138
|
+
chars: HTMLSpanElement[];
|
|
139
|
+
words: HTMLSpanElement[];
|
|
140
|
+
lines: HTMLSpanElement[];
|
|
141
|
+
revert: () => void;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
126
145
|
#### InView Options
|
|
127
146
|
|
|
128
147
|
```ts
|
|
@@ -135,72 +154,58 @@ import { SplitText } from 'fetta/react';
|
|
|
135
154
|
|
|
136
155
|
## Examples
|
|
137
156
|
|
|
138
|
-
###
|
|
157
|
+
### Vanilla JavaScript
|
|
139
158
|
|
|
140
|
-
|
|
141
|
-
<SplitText
|
|
142
|
-
options={{ type: 'lines', mask: 'lines' }}
|
|
143
|
-
onSplit={({ lines }) => {
|
|
144
|
-
animate(lines, { y: ['100%', 0] }, { delay: stagger(0.1) });
|
|
145
|
-
}}
|
|
146
|
-
>
|
|
147
|
-
<p>Each line reveals from below</p>
|
|
148
|
-
</SplitText>
|
|
149
|
-
```
|
|
159
|
+
#### Basic
|
|
150
160
|
|
|
151
|
-
|
|
161
|
+
```js
|
|
162
|
+
import { splitText } from 'fetta';
|
|
163
|
+
import { animate, stagger } from 'motion';
|
|
152
164
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
initialStyles={{
|
|
157
|
-
words: { opacity: '0', transform: 'translateY(20px)' }
|
|
158
|
-
}}
|
|
159
|
-
inView={{ amount: 0.5 }}
|
|
160
|
-
onInView={({ words }) => {
|
|
161
|
-
animate(words, { opacity: 1, y: 0 }, { delay: stagger(0.03) });
|
|
162
|
-
}}
|
|
163
|
-
resetOnLeave
|
|
164
|
-
>
|
|
165
|
-
<p>Animates when scrolled into view</p>
|
|
166
|
-
</SplitText>
|
|
165
|
+
const { words } = splitText(document.querySelector('h1'));
|
|
166
|
+
|
|
167
|
+
animate(words, { opacity: [0, 1], y: [20, 0] }, { delay: stagger(0.05) });
|
|
167
168
|
```
|
|
168
169
|
|
|
169
|
-
|
|
170
|
+
#### Masked Line Reveal
|
|
170
171
|
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
</SplitText>
|
|
172
|
+
```js
|
|
173
|
+
splitText(element, {
|
|
174
|
+
type: 'lines',
|
|
175
|
+
mask: 'lines',
|
|
176
|
+
onSplit: ({ lines }) => {
|
|
177
|
+
animate(lines, { y: ['100%', '0%'] }, { delay: stagger(0.1) });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
#### With GSAP
|
|
183
183
|
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
184
|
+
```js
|
|
185
|
+
import { splitText } from 'fetta';
|
|
186
|
+
import gsap from 'gsap';
|
|
187
|
+
|
|
188
|
+
splitText(element, {
|
|
189
|
+
revertOnComplete: true,
|
|
190
|
+
onSplit: ({ words }) => {
|
|
191
|
+
return gsap.from(words, {
|
|
192
|
+
opacity: 0,
|
|
193
|
+
y: 20,
|
|
194
|
+
stagger: 0.05,
|
|
195
|
+
duration: 0.6,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
});
|
|
192
199
|
```
|
|
193
200
|
|
|
194
|
-
|
|
201
|
+
#### CSS-Only with Index Props
|
|
195
202
|
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
<h1 className="stagger-fade">Hello</h1>
|
|
199
|
-
</SplitText>
|
|
203
|
+
```js
|
|
204
|
+
splitText(element, { type: 'chars', propIndex: true });
|
|
200
205
|
```
|
|
201
206
|
|
|
202
207
|
```css
|
|
203
|
-
.
|
|
208
|
+
.split-char {
|
|
204
209
|
opacity: 0;
|
|
205
210
|
animation: fade-in 0.5s forwards;
|
|
206
211
|
animation-delay: calc(var(--char-index) * 0.03s);
|
|
@@ -211,38 +216,61 @@ import { SplitText } from 'fetta/react';
|
|
|
211
216
|
}
|
|
212
217
|
```
|
|
213
218
|
|
|
214
|
-
###
|
|
219
|
+
### React
|
|
220
|
+
|
|
221
|
+
#### Basic
|
|
215
222
|
|
|
216
223
|
```tsx
|
|
217
|
-
import { SplitText } from 'fetta/react';
|
|
218
|
-
import gsap from 'gsap';
|
|
219
|
-
|
|
220
224
|
<SplitText
|
|
221
|
-
revertOnComplete
|
|
222
225
|
onSplit={({ words }) => {
|
|
223
|
-
|
|
224
|
-
opacity: 0,
|
|
225
|
-
y: 20,
|
|
226
|
-
stagger: 0.05,
|
|
227
|
-
duration: 0.6,
|
|
228
|
-
});
|
|
226
|
+
animate(words, { opacity: [0, 1], y: [20, 0] }, { delay: stagger(0.05) });
|
|
229
227
|
}}
|
|
230
228
|
>
|
|
231
|
-
<h1>
|
|
229
|
+
<h1>Hello World</h1>
|
|
232
230
|
</SplitText>
|
|
233
231
|
```
|
|
234
232
|
|
|
235
|
-
|
|
233
|
+
#### Masked Line Reveal
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
<SplitText
|
|
237
|
+
options={{ type: 'lines', mask: 'lines' }}
|
|
238
|
+
onSplit={({ lines }) => {
|
|
239
|
+
animate(lines, { y: ['100%', '0%'] }, { delay: stagger(0.1) });
|
|
240
|
+
}}
|
|
241
|
+
>
|
|
242
|
+
<p>Each line reveals from below</p>
|
|
243
|
+
</SplitText>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### Scroll-Triggered with InView
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
<SplitText
|
|
250
|
+
options={{ type: 'words' }}
|
|
251
|
+
initialStyles={{
|
|
252
|
+
words: { opacity: '0', transform: 'translateY(20px)' }
|
|
253
|
+
}}
|
|
254
|
+
inView={{ amount: 0.5 }}
|
|
255
|
+
onInView={({ words }) => {
|
|
256
|
+
animate(words, { opacity: 1, y: 0 }, { delay: stagger(0.03) });
|
|
257
|
+
}}
|
|
258
|
+
resetOnLeave
|
|
259
|
+
>
|
|
260
|
+
<p>Animates when scrolled into view</p>
|
|
261
|
+
</SplitText>
|
|
262
|
+
```
|
|
236
263
|
|
|
237
|
-
|
|
264
|
+
#### Auto-Revert After Animation
|
|
238
265
|
|
|
239
266
|
```tsx
|
|
240
267
|
<SplitText
|
|
268
|
+
revertOnComplete
|
|
241
269
|
onSplit={({ chars }) => {
|
|
242
|
-
animate(chars, { opacity: [0, 1] }, { delay: stagger(0.02) });
|
|
270
|
+
return animate(chars, { opacity: [0, 1] }, { delay: stagger(0.02) });
|
|
243
271
|
}}
|
|
244
272
|
>
|
|
245
|
-
<
|
|
273
|
+
<h1>Reverts to original HTML after animation</h1>
|
|
246
274
|
</SplitText>
|
|
247
275
|
```
|
|
248
276
|
|
|
@@ -258,11 +286,52 @@ Default classes applied to split elements:
|
|
|
258
286
|
|
|
259
287
|
Each element also receives a `data-index` attribute with its position.
|
|
260
288
|
|
|
289
|
+
## Font Loading
|
|
290
|
+
|
|
291
|
+
For accurate kerning measurements, fonts must be fully loaded before splitting. When using custom fonts in vanilla JS, wait for `document.fonts.ready`:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
document.fonts.ready.then(() => {
|
|
295
|
+
const { words } = splitText(element);
|
|
296
|
+
animate(words, { opacity: [0, 1] });
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The React component handles this automatically — no additional setup required.
|
|
301
|
+
|
|
302
|
+
## Accessibility
|
|
303
|
+
|
|
304
|
+
Fetta automatically handles accessibility to ensure split text remains readable by screen readers.
|
|
305
|
+
|
|
306
|
+
**Headings and landmarks** — For elements that support `aria-label` natively (headings, `<section>`, `<nav>`, etc.), Fetta adds `aria-hidden="true"` to each split span and an `aria-label` on the parent:
|
|
307
|
+
|
|
308
|
+
```html
|
|
309
|
+
<!-- After splitting <h1>Hello World</h1> -->
|
|
310
|
+
<h1 aria-label="Hello World">
|
|
311
|
+
<span class="split-word" aria-hidden="true">Hello</span>
|
|
312
|
+
<span class="split-word" aria-hidden="true">World</span>
|
|
313
|
+
</h1>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Generic elements and nested content** — For `<span>`, `<div>`, `<p>`, or text containing inline elements like links, Fetta wraps the visual content with `aria-hidden="true"` and adds a screen-reader-only copy that preserves the semantic structure:
|
|
317
|
+
|
|
318
|
+
```html
|
|
319
|
+
<!-- After splitting <p>Click <a href="/signup">here</a> to start</p> -->
|
|
320
|
+
<p>
|
|
321
|
+
<span aria-hidden="true" data-fetta-visual="true">
|
|
322
|
+
<!-- Split visual content -->
|
|
323
|
+
</span>
|
|
324
|
+
<span class="fetta-sr-only" data-fetta-sr-copy="true">
|
|
325
|
+
Click <a href="/signup">here</a> to start
|
|
326
|
+
</span>
|
|
327
|
+
</p>
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Pre-existing `aria-label` attributes are always preserved.
|
|
331
|
+
|
|
261
332
|
## Notes
|
|
262
333
|
|
|
263
|
-
- **Fonts must be loaded** before splitting. The React component waits for `document.fonts.ready` automatically.
|
|
264
334
|
- **Ligatures are disabled** (`font-variant-ligatures: none`) because ligatures cannot span multiple elements.
|
|
265
|
-
- **Accessibility**: Automatic screen reader support for both simple text and text with nested elements like links.
|
|
266
335
|
|
|
267
336
|
## Browser Support
|
|
268
337
|
|
|
@@ -273,6 +342,8 @@ Requires:
|
|
|
273
342
|
- `IntersectionObserver`
|
|
274
343
|
- `Intl.Segmenter`
|
|
275
344
|
|
|
345
|
+
**Safari note** — Kerning compensation works but may be slightly less accurate due to Safari's unique font rendering. Differences are typically imperceptible and vary by font, but if you're using `revert()` and notice a subtle shift in some characters, you can bypass compensation with `disableKerning: true`.
|
|
346
|
+
|
|
276
347
|
## License
|
|
277
348
|
|
|
278
349
|
MIT
|