scroll-text-reveal 0.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 +149 -0
- package/dist/ScrollTextReveal.d.ts +47 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# scroll-text-reveal
|
|
2
|
+
|
|
3
|
+
A tiny TypeScript library that reveals text line by line when it enters the
|
|
4
|
+
viewport. Built for static websites with GSAP, ScrollTrigger, and SplitText.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- Line-by-line masked text reveal
|
|
9
|
+
- One small function and seven practical options
|
|
10
|
+
- Configurable duration, trigger delay, line stagger, distance, start position,
|
|
11
|
+
and easing
|
|
12
|
+
- Optional animation for elements visible during initialization
|
|
13
|
+
- Responsive line re-splitting after font loading and width changes
|
|
14
|
+
- Automatic screen-reader attributes through SplitText
|
|
15
|
+
- Automatic `prefers-reduced-motion` safeguard
|
|
16
|
+
- Duplicate initialization protection
|
|
17
|
+
- ESM, CommonJS, and TypeScript declarations
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add scroll-text-reveal gsap
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install scroll-text-reveal gsap
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
yarn add scroll-text-reveal gsap
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
GSAP 3.13 or newer is required.
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
Add a selector to the text you want to reveal:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<h2 data-scroll-text-reveal>
|
|
41
|
+
A heading that reveals one line at a time.
|
|
42
|
+
</h2>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Initialize it once after the page markup is available:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { scrollTextReveal } from "scroll-text-reveal";
|
|
49
|
+
|
|
50
|
+
scrollTextReveal("[data-scroll-text-reveal]");
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The function accepts a CSS selector, one `HTMLElement`, or an iterable
|
|
54
|
+
collection of `HTMLElement`s.
|
|
55
|
+
|
|
56
|
+
## Configuration
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
scrollTextReveal(".reveal-heading", {
|
|
60
|
+
duration: 0.85,
|
|
61
|
+
delay: 0,
|
|
62
|
+
stagger: 0.1,
|
|
63
|
+
distance: 115,
|
|
64
|
+
start: 86,
|
|
65
|
+
ease: "power3.out",
|
|
66
|
+
animateInitiallyVisible: false,
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
| Option | Type | Default | Description |
|
|
71
|
+
| --- | --- | --- | --- |
|
|
72
|
+
| `duration` | `number` | `0.85` | Animation duration in seconds |
|
|
73
|
+
| `delay` | `number` | `0` | Pause after the element reaches its viewport start position |
|
|
74
|
+
| `stagger` | `number` | `0.1` | Delay between consecutive lines in seconds |
|
|
75
|
+
| `distance` | `number` | `115` | Initial vertical offset as a percentage of each line |
|
|
76
|
+
| `start` | `number` | `86` | Viewport percentage at which the reveal starts |
|
|
77
|
+
| `ease` | `string` | `power3.out` | GSAP easing name |
|
|
78
|
+
| `animateInitiallyVisible` | `boolean` | `false` | Animate text already visible during initialization |
|
|
79
|
+
|
|
80
|
+
`start: 86` means that the animation is triggered when the top of the element
|
|
81
|
+
reaches a point 86% down the viewport.
|
|
82
|
+
|
|
83
|
+
### Delayed reveal
|
|
84
|
+
|
|
85
|
+
`delay` starts after ScrollTrigger activates the animation:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
scrollTextReveal(".delayed-heading", {
|
|
89
|
+
delay: 0.5,
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Initially visible text
|
|
94
|
+
|
|
95
|
+
By default, text already visible when `scrollTextReveal()` runs is left
|
|
96
|
+
untouched. Enable its animation explicitly:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
scrollTextReveal(".hero-heading", {
|
|
100
|
+
animateInitiallyVisible: true,
|
|
101
|
+
delay: 0.2,
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Direct elements
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const heading = document.querySelector<HTMLElement>(".heading");
|
|
109
|
+
|
|
110
|
+
if (heading) {
|
|
111
|
+
scrollTextReveal(heading);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
scrollTextReveal(document.querySelectorAll<HTMLElement>(".reveal"));
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Calling `scrollTextReveal()` more than once for an element is safe. An element
|
|
120
|
+
that has already been initialized is ignored.
|
|
121
|
+
|
|
122
|
+
## Accessibility
|
|
123
|
+
|
|
124
|
+
When a visitor requests reduced motion, the function returns before splitting
|
|
125
|
+
or animating any text.
|
|
126
|
+
|
|
127
|
+
SplitText uses `aria: "auto"` so the original text is exposed as a single
|
|
128
|
+
accessible label while generated lines are hidden from screen readers. For text
|
|
129
|
+
containing interactive nested elements such as links, review the alternative
|
|
130
|
+
accessibility strategy in the
|
|
131
|
+
[SplitText documentation](https://gsap.com/docs/v3/Plugins/SplitText/).
|
|
132
|
+
|
|
133
|
+
## Browser support
|
|
134
|
+
|
|
135
|
+
The library targets modern browsers supported by GSAP and requires a browser
|
|
136
|
+
DOM. Call it after the relevant HTML has been parsed, either from a module at
|
|
137
|
+
the end of `body` or after `DOMContentLoaded`.
|
|
138
|
+
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
pnpm install
|
|
143
|
+
pnpm typecheck
|
|
144
|
+
pnpm test
|
|
145
|
+
pnpm build
|
|
146
|
+
pnpm build:docs
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Use `pnpm dev` to open the demo.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A CSS selector, HTML element, or iterable collection of HTML elements.
|
|
3
|
+
*/
|
|
4
|
+
export type ScrollTextRevealTarget = string | HTMLElement | Iterable<HTMLElement>;
|
|
5
|
+
/**
|
|
6
|
+
* Controls the line reveal animation.
|
|
7
|
+
*/
|
|
8
|
+
export interface ScrollTextRevealOptions {
|
|
9
|
+
/** Animation duration in seconds. */
|
|
10
|
+
duration?: number;
|
|
11
|
+
/** Delay in seconds after the element reaches its viewport start position. */
|
|
12
|
+
delay?: number;
|
|
13
|
+
/** Delay in seconds between consecutive lines. */
|
|
14
|
+
stagger?: number;
|
|
15
|
+
/** Initial vertical offset as a percentage of each line. */
|
|
16
|
+
distance?: number;
|
|
17
|
+
/** Viewport percentage at which the animation starts. */
|
|
18
|
+
start?: number;
|
|
19
|
+
/** Any GSAP ease name accepted by gsap.from(). */
|
|
20
|
+
ease?: string;
|
|
21
|
+
/** Animate elements already visible when scrollTextReveal() is called. */
|
|
22
|
+
animateInitiallyVisible?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface ScrollTextRevealDefaults {
|
|
25
|
+
readonly duration: number;
|
|
26
|
+
readonly delay: number;
|
|
27
|
+
readonly stagger: number;
|
|
28
|
+
readonly distance: number;
|
|
29
|
+
readonly start: number;
|
|
30
|
+
readonly ease: string;
|
|
31
|
+
readonly animateInitiallyVisible: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare const scrollTextRevealDefaults: ScrollTextRevealDefaults;
|
|
34
|
+
/**
|
|
35
|
+
* Reveals text line by line when it enters the viewport.
|
|
36
|
+
*
|
|
37
|
+
* Calling the function more than once for the same element is safe: an element
|
|
38
|
+
* is initialized only once.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* scrollTextReveal("[data-scroll-text-reveal]", {
|
|
42
|
+
* duration: 0.9,
|
|
43
|
+
* delay: 0.15,
|
|
44
|
+
* stagger: 0.08,
|
|
45
|
+
* });
|
|
46
|
+
*/
|
|
47
|
+
export declare const scrollTextReveal: (target: ScrollTextRevealTarget, options?: ScrollTextRevealOptions) => void;
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var g=Object.create;var i=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var R=Object.getPrototypeOf,w=Object.prototype.hasOwnProperty;var S=(t,e)=>{for(var r in e)i(t,r,{get:e[r],enumerable:!0})},d=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let l of x(e))!w.call(t,l)&&l!==r&&i(t,l,{get:()=>e[l],enumerable:!(n=v(e,l))||n.enumerable});return t};var E=(t,e,r)=>(r=t!=null?g(R(t)):{},d(e||!t||!t.__esModule?i(r,"default",{value:t,enumerable:!0}):r,t)),M=t=>d(i({},"__esModule",{value:!0}),t);var L={};S(L,{scrollTextReveal:()=>y,scrollTextRevealDefaults:()=>c});module.exports=M(L);var a=E(require("gsap"),1),p=require("gsap/ScrollTrigger"),s=require("gsap/SplitText"),c=Object.freeze({duration:.85,delay:0,stagger:.1,distance:115,start:86,ease:"power3.out",animateInitiallyVisible:!1}),m=new WeakSet,T=!1,h=()=>{T||(a.default.registerPlugin(p.ScrollTrigger,s.SplitText),T=!0)},f=t=>typeof HTMLElement<"u"&&t instanceof HTMLElement,H=t=>{if(typeof document>"u")throw new Error("scrollTextReveal requires a browser DOM.");if(typeof t=="string")try{return Array.from(document.querySelectorAll(t))}catch{throw new TypeError(`scrollTextReveal received an invalid selector: "${t}".`)}if(f(t))return[t];if(t&&typeof t[Symbol.iterator]=="function"){let e=Array.from(t);if(e.every(f))return e}throw new TypeError("scrollTextReveal target must be a CSS selector, HTMLElement, or iterable of HTMLElements.")},o=(t,e,r,n=Number.POSITIVE_INFINITY)=>{if(!Number.isFinite(e)||e<r||e>n){let l=Number.isFinite(n)?`between ${r} and ${n}`:`greater than or equal to ${r}`;throw new RangeError(`scrollTextReveal option "${t}" must be ${l}.`)}return e},I=t=>{let e={...c,...t};if(o("duration",e.duration,0),o("delay",e.delay,0),o("stagger",e.stagger,0),o("distance",e.distance,0),o("start",e.start,0,100),typeof e.ease!="string"||!e.ease.trim())throw new TypeError('scrollTextReveal option "ease" must be a non-empty string.');if(typeof e.animateInitiallyVisible!="boolean")throw new TypeError('scrollTextReveal option "animateInitiallyVisible" must be a boolean.');return e},O=t=>{let e=t.getBoundingClientRect();return e.top<window.innerHeight&&e.bottom>0},y=(t,e={})=>{if(typeof window>"u")throw new Error("scrollTextReveal requires a browser DOM.");let r=I(e),n=H(t);if(!(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches||n.length===0)){h();for(let l of n){if(m.has(l))continue;let u=O(l);u&&!r.animateInitiallyVisible||(s.SplitText.create(l,{type:"lines",mask:"lines",linesClass:"scroll-text-reveal-line",autoSplit:!0,aria:"auto",onSplit:b=>a.default.from(b.lines,{yPercent:r.distance,opacity:0,duration:r.duration,delay:r.delay,stagger:r.stagger,ease:r.ease,...u?{}:{scrollTrigger:{trigger:l,start:`top ${r.start}%`,once:!0}}})}),m.add(l))}}};0&&(module.exports={scrollTextReveal,scrollTextRevealDefaults});
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import u from"gsap";import{ScrollTrigger as f}from"gsap/ScrollTrigger";import{SplitText as d}from"gsap/SplitText";var m=Object.freeze({duration:.85,delay:0,stagger:.1,distance:115,start:86,ease:"power3.out",animateInitiallyVisible:!1}),a=new WeakSet,s=!1,p=()=>{s||(u.registerPlugin(f,d),s=!0)},c=t=>typeof HTMLElement<"u"&&t instanceof HTMLElement,y=t=>{if(typeof document>"u")throw new Error("scrollTextReveal requires a browser DOM.");if(typeof t=="string")try{return Array.from(document.querySelectorAll(t))}catch{throw new TypeError(`scrollTextReveal received an invalid selector: "${t}".`)}if(c(t))return[t];if(t&&typeof t[Symbol.iterator]=="function"){let e=Array.from(t);if(e.every(c))return e}throw new TypeError("scrollTextReveal target must be a CSS selector, HTMLElement, or iterable of HTMLElements.")},o=(t,e,r,n=Number.POSITIVE_INFINITY)=>{if(!Number.isFinite(e)||e<r||e>n){let l=Number.isFinite(n)?`between ${r} and ${n}`:`greater than or equal to ${r}`;throw new RangeError(`scrollTextReveal option "${t}" must be ${l}.`)}return e},b=t=>{let e={...m,...t};if(o("duration",e.duration,0),o("delay",e.delay,0),o("stagger",e.stagger,0),o("distance",e.distance,0),o("start",e.start,0,100),typeof e.ease!="string"||!e.ease.trim())throw new TypeError('scrollTextReveal option "ease" must be a non-empty string.');if(typeof e.animateInitiallyVisible!="boolean")throw new TypeError('scrollTextReveal option "animateInitiallyVisible" must be a boolean.');return e},g=t=>{let e=t.getBoundingClientRect();return e.top<window.innerHeight&&e.bottom>0},v=(t,e={})=>{if(typeof window>"u")throw new Error("scrollTextReveal requires a browser DOM.");let r=b(e),n=y(t);if(!(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches||n.length===0)){p();for(let l of n){if(a.has(l))continue;let i=g(l);i&&!r.animateInitiallyVisible||(d.create(l,{type:"lines",mask:"lines",linesClass:"scroll-text-reveal-line",autoSplit:!0,aria:"auto",onSplit:T=>u.from(T.lines,{yPercent:r.distance,opacity:0,duration:r.duration,delay:r.delay,stagger:r.stagger,ease:r.ease,...i?{}:{scrollTrigger:{trigger:l,start:`top ${r.start}%`,once:!0}}})}),a.add(l))}}};export{v as scrollTextReveal,m as scrollTextRevealDefaults};
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "scroll-text-reveal",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A tiny GSAP-powered line reveal for text entering the viewport.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"text-reveal",
|
|
7
|
+
"scroll-animation",
|
|
8
|
+
"split-text",
|
|
9
|
+
"gsap",
|
|
10
|
+
"typescript"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/dmitry-conquer/scroll-text-reveal.git"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/dmitry-conquer/scroll-text-reveal#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/dmitry-conquer/scroll-text-reveal/issues"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"packageManager": "pnpm@11.10.0",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public",
|
|
25
|
+
"registry": "https://registry.npmjs.org/"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"require": "./dist/index.cjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"dev": "vite --config vite.docs.config.ts --open",
|
|
42
|
+
"build": "tsup src/index.ts --format esm,cjs --clean --minify && tsc -p tsconfig.build.json",
|
|
43
|
+
"build:docs": "vite build --config vite.docs.config.ts",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"prepack": "pnpm run build",
|
|
48
|
+
"prepublishOnly": "pnpm run typecheck && pnpm test"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"gsap": ">=3.13.0 <4"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"gsap": "^3.15.0",
|
|
55
|
+
"happy-dom": "^20.11.0",
|
|
56
|
+
"tsup": "^8.5.1",
|
|
57
|
+
"typescript": "^7.0.2",
|
|
58
|
+
"vite": "^8.1.5",
|
|
59
|
+
"vitest": "^4.1.10"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=18"
|
|
63
|
+
}
|
|
64
|
+
}
|