{"_id":"@code_with_sachin/ngx-gsap","name":"@code_with_sachin/ngx-gsap","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@code_with_sachin/ngx-gsap","version":"0.1.0","description":"Angular directives for GSAP + Lenis — scrub timelines, split-text reveals, marquees, counters and canvas frame sequences.","keywords":["angular","gsap","scrolltrigger","splittext","lenis","smooth-scroll","marquee","count-up","frame-sequence","ssr","standalone","signals"],"homepage":"https://sachinsingh.me/packages/gsap","license":"MIT","author":{"name":"Sachin Singh","url":"https://sachinsingh.me"},"repository":{"type":"git","url":"git+https://github.com/CodeWithSachin/kshatriya-portfolio.git","directory":"projects/ngx-gsap"},"bugs":{"url":"https://github.com/CodeWithSachin/kshatriya-portfolio/issues"},"publishConfig":{"access":"public"},"peerDependencies":{"@angular/common":"^22.0.0","@angular/core":"^22.0.0","gsap":"^3.13.0","lenis":"^1.3.0"},"dependencies":{"tslib":"^2.3.0"},"sideEffects":false,"module":"fesm2022/code_with_sachin-ngx-gsap.mjs","typings":"types/code_with_sachin-ngx-gsap.d.ts","exports":{"./package.json":{"default":"./package.json"},".":{"types":"./types/code_with_sachin-ngx-gsap.d.ts","default":"./fesm2022/code_with_sachin-ngx-gsap.mjs"}},"type":"module","gitHead":"060ebca6612769b29f91d6995dd150e137a6186e","_id":"@code_with_sachin/ngx-gsap@0.1.0","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-bTI8lYO9zu3J6ZWVX7E0FTUbVC6FETq5mmzRthXqXoHZZ75D0cZoHDbyKjVauNbeU/E5TqiiB04glTputSWOTg==","shasum":"87c8724460917e0ce845de4ee268ae89ec5e289b","tarball":"https://registry.npmjs.org/@code_with_sachin/ngx-gsap/-/ngx-gsap-0.1.0.tgz","fileCount":5,"unpackedSize":93554,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIE+mx24YyR8gfLpGDaUVDh0TJqG5VHxmpC1JPsoElVijAiEAxSN5e2zXLByV/eadsde22LFePMDfjDqDdYHL6ebNk7w="}]},"_npmUser":{"name":"code_with_sachin","email":"sachinnnsingh0510@gmail.com"},"directories":{},"maintainers":[{"name":"code_with_sachin","email":"sachinnnsingh0510@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ngx-gsap_0.1.0_1786290341724_0.42268595856050384"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-09T15:45:41.589Z","0.1.0":"2026-08-09T15:45:41.865Z","modified":"2026-08-09T15:45:42.051Z"},"maintainers":[{"name":"code_with_sachin","email":"sachinnnsingh0510@gmail.com"}],"description":"Angular directives for GSAP + Lenis — scrub timelines, split-text reveals, marquees, counters and canvas frame sequences.","homepage":"https://sachinsingh.me/packages/gsap","keywords":["angular","gsap","scrolltrigger","splittext","lenis","smooth-scroll","marquee","count-up","frame-sequence","ssr","standalone","signals"],"repository":{"type":"git","url":"git+https://github.com/CodeWithSachin/kshatriya-portfolio.git","directory":"projects/ngx-gsap"},"author":{"name":"Sachin Singh","url":"https://sachinsingh.me"},"bugs":{"url":"https://github.com/CodeWithSachin/kshatriya-portfolio/issues"},"license":"MIT","readme":"# @code_with_sachin/ngx-gsap\n\nAngular directives for [GSAP](https://gsap.com) + [Lenis](https://lenis.darkroom.engineering) —\nscrub timelines, split-text reveals, marquees, counters and canvas frame sequences.\n\n**[Live demo & docs → https://sachinsingh.me/packages/gsap](https://sachinsingh.me/packages/gsap)**\n\nGSAP and ScrollTrigger are the heavy machinery behind scroll-driven sites, and wiring\nthem into Angular means the same boilerplate every time: register plugins once,\nbrowser-only; do DOM work after render; scope everything to a `gsap.context()` so it\nreverts on destroy. This package is that boilerplate, written once, as directives.\n\n- Every directive early-returns on the server; nothing touches the DOM there.\n- Reduced motion is a first-class path — timelines jump to their final state, marquees\n  freeze, counters print their final value, sequences draw their last frame.\n- Lenis smooth scroll synced to ScrollTrigger and driven off `gsap.ticker` — the rAF\n  loop never touches change detection.\n\n## Install\n\n```bash\nnpm i @code_with_sachin/ngx-gsap gsap lenis\n```\n\nPeer dependencies: Angular 22+, `gsap` 3.13+, `lenis` 1.3+. SplitText is a GSAP plugin,\nfree from GSAP 3.13 onward — which is why the peer range starts there.\n\n## Setup\n\n```ts\nimport { ApplicationConfig } from '@angular/core';\nimport { provideGsap } from '@code_with_sachin/ngx-gsap';\n\nexport const appConfig: ApplicationConfig = {\n  // Registers ScrollTrigger + SplitText once, browser-only.\n  // Required before any directive in this package works.\n  providers: [provideGsap()],\n};\n```\n\n## Requirements\n\nPeer ranges are wider than this; the **Tested** column is the exact combination the\n[live demo](https://sachinsingh.me/packages/gsap) runs, so you have a known-good set to fall back on.\n\n| Package         | Peer range | Tested  | Notes                                                                    |\n| --------------- | ---------- | ------- | ------------------------------------------------------------------------ |\n| `@angular/core` | `^22.0.0`  | 22.0.5  | Standalone APIs, signal inputs and `afterNextRender`.                    |\n| `typescript`    | `~6.0.0`   | 6.0.3   | Whatever your Angular version supports.                                  |\n| `node`          | `>=20`     | 24.15.0 | Build and SSR only.                                                      |\n| `gsap`          | `^3.13.0`  | 3.15.0  | SplitText became free in 3.13, which is why the range starts there.      |\n| `lenis`         | `^1.3.0`   | 1.3.25  | Only used by `ScrollService`; the directives work without smooth scroll. |\n\n## Providers\n\n**Required.** Without it ScrollTrigger and SplitText are never registered and every directive silently does nothing.\n\n```ts\nimport { ApplicationConfig } from '@angular/core';\nimport { provideGsap } from '@code_with_sachin/ngx-gsap';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [provideGsap()],\n};\n\n// main.ts\n// bootstrapApplication(App, appConfig);\n```\n\n## Using NgModules\n\nEverything here is standalone, but standalone components and directives are importable from\nan `@NgModule` — put them in the module's `imports`, **not** `declarations`. No\n`importProvidersFrom` is needed: `@NgModule.providers` is typed\n`Array<Provider | EnvironmentProviders>`, so the `provide*()` functions drop straight in.\n\n```ts\nimport { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport {\n  MarqueeDirective,\n  CountUpDirective,\n  SplitRevealDirective,\n  ScrubTimelineDirective,\n  FrameSequenceComponent,\n  provideGsap,\n} from '@code_with_sachin/ngx-gsap';\n\nimport { AppComponent } from './app.component';\n\n@NgModule({\n  declarations: [AppComponent],\n  // Standalone components and directives go in `imports`.\n  imports: [\n    BrowserModule,\n    MarqueeDirective,\n    CountUpDirective,\n    SplitRevealDirective,\n    ScrubTimelineDirective,\n    FrameSequenceComponent,\n  ],\n  providers: [provideGsap()],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n```\n\n## `ngxMarquee`\n\nWraps the host's content in a track and clones it until the row is at least twice the\nhost width, then loops it linearly. Clones are `aria-hidden`.\n\n```html\n<div ngxMarquee [speed]=\"80\">CRAFT OVER HYPE ·&nbsp;</div>\n<div ngxMarquee [speed]=\"140\" [direction]=\"-1\">…</div>\n```\n\n| Input       | Type      | Default | Notes                           |\n| ----------- | --------- | ------- | ------------------------------- |\n| `speed`     | `number`  | `90`    | Pixels per second.              |\n| `direction` | `1 \\| -1` | `1`     | `1` leftwards, `-1` rightwards. |\n\n## `ngxCountUp`\n\n```html\n<span [ngxCountUp]=\"70000\" suffix=\"+\" [duration]=\"2\"></span>\n```\n\n| Input        | Type     | Default | Notes                                                          |\n| ------------ | -------- | ------- | -------------------------------------------------------------- |\n| `ngxCountUp` | `number` | —       | Required. Starts when the host hits 85% of the viewport, once. |\n| `duration`   | `number` | `1.6`   | Seconds, eased with `power2.out`.                              |\n| `suffix`     | `string` | `''`    | Appended to every frame — `+`, `%`, `k`.                       |\n\n## `ngxSplitReveal`\n\n```html\n<h2 ngxSplitReveal mode=\"words\" [stagger]=\"0.05\">Every pixel is a promise</h2>\n```\n\n| Input      | Type                            | Default   | Notes                                             |\n| ---------- | ------------------------------- | --------- | ------------------------------------------------- |\n| `mode`     | `'lines' \\| 'words' \\| 'chars'` | `'words'` | See the warning below.                            |\n| `scrubbed` | `boolean`                       | `true`    | `false` plays once on enter and reverses on exit. |\n| `stagger`  | `number`                        | `0.03`    | Seconds between pieces.                           |\n| `y`        | `number`                        | `28`      | Travel in pixels.                                 |\n\n> ⚠️ **Never use `mode=\"chars\"` on Devanagari or other Indic scripts** — conjunct\n> ligatures break when text is split per character. `words` and `lines` are safe.\n\n## `ngxScrub`\n\nA scroll-scrubbed timeline bound to the host. Your callback runs inside a\n`gsap.context()` scoped to the host, so plain selector strings only match inside that\nsection — and everything reverts automatically on destroy.\n\n```ts\n@Component({\n  imports: [ScrubTimelineDirective],\n  template: `\n    <section ngxScrub [pin]=\"true\" end=\"+=150%\" (timelineReady)=\"build($event)\">\n      <div class=\"bar\"></div>\n    </section>\n  `,\n})\nexport class Chapter {\n  protected build(tl: gsap.core.Timeline): void {\n    tl.to('.bar', { width: '100%', ease: 'none' });\n  }\n}\n```\n\n| Input / Output  | Type                         | Default        | Notes                                     |\n| --------------- | ---------------------------- | -------------- | ----------------------------------------- |\n| `start`         | `string`                     | `'top bottom'` | ScrollTrigger start position.             |\n| `end`           | `string`                     | `'bottom top'` | e.g. `'+=150%'`.                          |\n| `scrub`         | `boolean \\| number`          | `true`         | A number adds catch-up smoothing seconds. |\n| `pin`           | `boolean`                    | `false`        | Pin the host for the timeline's duration. |\n| `markers`       | `boolean`                    | `false`        | ScrollTrigger debug markers.              |\n| `timelineReady` | `output<gsap.core.Timeline>` | —              | Populate the timeline synchronously.      |\n\nUnder reduced motion the timeline is created paused and jumped to `progress(1)` — the\nfinal state, no scrubbing.\n\n## `<ngx-frame-sequence>`\n\nA scroll-scrubbed canvas image sequence. Frames are fetched and decoded to\n`ImageBitmap`s about two viewports before arrival, then drawn at `devicePixelRatio`\ncapped at 2.\n\n```html\n<ngx-frame-sequence class=\"relative block h-screen\" [frames]=\"frames\" [pinLength]=\"150\">\n  <h3 class=\"absolute inset-0 grid place-items-center\">{{ caption() }}</h3>\n</ngx-frame-sequence>\n```\n\n| Input / Member   | Type                   | Default   | Notes                                                      |\n| ---------------- | ---------------------- | --------- | ---------------------------------------------------------- |\n| `frames`         | `string[]`             | —         | Required. Image URLs in order.                             |\n| `pinLength`      | `number`               | `200`     | Viewport-heights of scroll the pin consumes. `0` = no pin. |\n| `fit`            | `'cover' \\| 'contain'` | `'cover'` | How each frame fills the canvas.                           |\n| `external`       | `boolean`              | `false`   | No scroll trigger of its own — drive with `setProgress()`. |\n| `progress`       | `Signal<number>`       | —         | 0–1 sequence progress; drive overlay swaps off it.         |\n| `setProgress(p)` | `(number) => void`     | —         | External-drive entry point; clamps to 0–1.                 |\n\nThe host is **not** positioned for you — component styles are un-layered and would\noutrank Tailwind utilities. Position it yourself (`relative h-screen`, `absolute inset-0`).\n\n## `ScrollService`\n\n```ts\nexport class App {\n  private readonly scroll = inject(ScrollService);\n\n  constructor() {\n    afterNextRender(() => this.scroll.init()); // once, from the root component\n  }\n\n  protected readonly progress = this.scroll.progress; // a signal\n}\n```\n\n| Member                      | Type                                        | Notes                                                       |\n| --------------------------- | ------------------------------------------- | ----------------------------------------------------------- |\n| `init()`                    | `() => void`                                | Idempotent, browser-only, skipped under reduced motion.     |\n| `progress`                  | `Signal<number>`                            | 0–1 page scroll progress, updated outside change detection. |\n| `scrollTo(target, offset?)` | `(string \\| number \\| HTMLElement, number)` | Falls back to `scrollIntoView` when Lenis is off.           |\n| `stop()` / `start()`        | `() => void`                                | Pause and resume — useful behind a modal.                   |\n| `destroy()`                 | `() => void`                                | Called automatically when the root injector is destroyed.   |\n\n## Re-measuring\n\nScrollTrigger turns `start`/`end` into pixel positions the moment a trigger is created.\nPlenty of things move the page _after_ that — `ngxMarquee` clones its track, `ngxSplitReveal`\nlets SplitText rewrap every line, web fonts swap in, images finish decoding. A trigger\nmeasured too early is then quietly wrong for the rest of the session: scrubbed timelines run\nearly, counters fire off-screen, and nothing points at the cause.\n\nEvery directive here handles that for you — after setup it schedules a debounced\n`ScrollTrigger.refresh()`, arms one more for `document.fonts.ready`, and watches the document\nheight so late arrivals are caught too. If you change page height in a way nothing observes,\ncall `ScrollService.refresh()` (or the exported `scheduleScrollRefresh()`) yourself.\n\n## `prefersReducedMotion()`\n\nA small SSR-safe helper, exported for consumers building their own reduced-motion paths.\n\n```ts\nimport { prefersReducedMotion } from '@code_with_sachin/ngx-gsap';\nif (!prefersReducedMotion()) {\n  /* … */\n}\n```\n\n## Licence\n\nMIT © Sachin Singh\n","readmeFilename":"README.md","_rev":"1-747ed20702b13cbc95f05c0ed1d042f4"}