[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-3870":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":18,"stars30d":19,"stars90d":16,"forks30d":16,"starsTrendScore":18,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":24,"hasPages":24,"topics":25,"createdAt":10,"pushedAt":10,"updatedAt":26,"readmeContent":27,"aiSummary":28,"trendingCount":16,"starSnapshotCount":16,"syncStatus":29,"lastSyncTime":30,"discoverSource":31},3870,"ScrollMagic","janpaepke\u002FScrollMagic","janpaepke","The javascript library for magical scroll interactions.","http:\u002F\u002FScrollMagic.io",null,"TypeScript",14951,2125,303,4,0,1,3,8,74.28,"MIT License",false,"main",true,[],"2026-06-12 04:00:19","# ScrollMagic 3\n\n\u003C!--\nTODO: Replace static shields (license, bundle, dependencies) once published\n![license](https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fl\u002Fscrollmagic)\n-->\n\n[![npm version](https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fv\u002Fscrollmagic\u002Fnext)](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fscrollmagic\u002Fv\u002Fnext)\n[![license](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-MIT-lightgrey)](LICENSE.md)\n[![bundle size](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fgzip-~6kb-brightgreen)](https:\u002F\u002Fbundlephobia.com\u002Fpackage\u002Fscrollmagic)\n[![dependencies](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fdependencies-0-brightgreen)](https:\u002F\u002Fnpmgraph.js.org\u002F?q=scrollmagic)\n[![TypeScript](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FTypeScript-native-blue)](https:\u002F\u002Fwww.typescriptlang.org\u002F)\n\n### The lightweight library for magical scroll interactions\n\n> **Looking for ScrollMagic v2?** The legacy version is on the [`v2-stable`](https:\u002F\u002Fgithub.com\u002Fjanpaepke\u002FScrollMagic\u002Ftree\u002Fv2-stable) branch.\n\nScrollMagic tells you where an element is relative to the viewport as the user scrolls — and fires events when that changes.\n\nIt's a convenience wrapper around [IntersectionObserver](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FIntersectionObserver) and [ResizeObserver](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FResizeObserver) that handles the performance pitfalls and counter-intuitive edge cases for you.\n\n[![Donate](https:\u002F\u002Fscrollmagic.io\u002Fassets\u002Fimg\u002Fbtn_donate.svg 'Shut up and take my money!')](https:\u002F\u002Fwww.paypal.com\u002Fcgi-bin\u002Fwebscr?cmd=_s-xclick&hosted_button_id=8BJC8B58XHKLL 'Shut up and take my money!')\n\n### Not an animation library – unless you want it to be\n\nBy itself, ScrollMagic doesn't animate anything. It provides precise scroll-position data and events — what you do with them is up to you. If you're looking for a ready-made scroll animation solution, check out [GSAP ScrollTrigger](https:\u002F\u002Fgsap.com\u002Fdocs\u002Fv3\u002FPlugins\u002FScrollTrigger\u002F), [Motion](https:\u002F\u002Fmotion.dev\u002Fdocs\u002Fscroll), or [anime.js](https:\u002F\u002Fanimejs.com\u002F).\n\nFor pure CSS-driven scroll animations, see native [scroll-driven animations](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FCSS\u002FCSS_scroll-driven_animations) (not yet supported in all browsers). ScrollMagic complements them by providing cross-browser support, event callbacks, progress values, and state management that the native API doesn't cover.\n\nScrollMagic is a general-purpose, framework-agnostic, zero-dependency foundation for scroll-driven UX — what you do with it is entirely up to you: class toggles, animations, lazy loading, parallax, scroll-linked video, behavioural tracking, or anything else.\n\n### Why ScrollMagic?\n\n- Tiny footprint, zero dependencies\n- Free to use ([open source](LICENSE.md))\n- Optimized for performance (shared observers, batched rAF, single-frame updates)\n- Built for modern browsers, mobile compatible\n- Native TypeScript support\n- SSR safe\n- Works with any scroll container (window or custom element)\n- Horizontal and vertical scrolling\n- Plugin system for extensibility\n- Framework agnostic — works with React, Vue, vanilla JS, anything\n\n## Installation\n\n```sh\nnpm install scrollmagic@next\n```\n\n## Quick Start\n\n```js\nimport ScrollMagic from 'scrollmagic';\n\nnew ScrollMagic({ element: '#my-element' })\n\t.on('enter', () => console.log('visible!'))\n\t.on('leave', () => console.log('gone!'))\n\t.on('progress', e => console.log(`${(e.target.progress * 100).toFixed(0)}%`));\n```\n\n## How It Works\n\nScrollMagic uses two sets of bounds to define the active range:\n\n- **Container bounds** — a zone on the scroll container, defined by `containerStart` and `containerEnd`\n- **Element bounds** — a zone on the tracked element, defined by `elementStart` and `elementEnd`\n\nProgress goes from `0` to `1` as the element bounds pass through the container bounds. Events fire on enter, leave, and progress change.\n\n### Contain and Intersect\n\nThe two most common configurations are **contain** and **intersect**. They differ in where the container bounds are positioned:\n\n#### Contain (default when `element` is `null`)\n\n\u003Cimg align=\"right\" src=\"docs\u002Fdist\u002Fgfx\u002Fcontain.gif\" alt=\"Contain mode animation: tall element scrolls through viewport, progress tracks from 0% to 100%\" width=\"260\" \u002F>\n\nThe container bounds match the viewport edges — `containerStart` and `containerEnd` are both at `'here'` (`0%`). Progress goes from 0 to 1 while one fully **contains** the other: either the element is fully visible inside the viewport, or the element fully covers the viewport.\n\nTypical uses: scroll progress bars, parallax, scroll-linked video, scroll-driven storytelling.\n\n\u003Cbr clear=\"both\" \u002F>\n\n#### Intersect (default when `element` is set)\n\n\u003Cimg align=\"right\" src=\"docs\u002Fdist\u002Fgfx\u002Fintersect.gif\" alt=\"Intersect mode animation: element scrolls through the viewport, progress tracks from 0% to 100%\" width=\"260\" \u002F>\n\nThe container bounds span the full viewport — `containerStart` and `containerEnd` are at `'opposite'` edges (`100%`). Progress goes from 0 to 1 while the element **intersects** with the viewport: starting when its leading edge enters and ending when its trailing edge leaves.\n\nTypical uses: enter\u002Fleave animations, lazy loading, class toggles, visibility tracking.\n\n\u003Cbr clear=\"both\" \u002F>\n\n#### Not just defaults\n\nWhile _contain_ and _intersect_ are the inferred defaults, you can also configure them explicitly — for example setting `containerStart: 0, containerEnd: 0` on an instance that has an element to get contain behaviour, or mixing container and element insets for custom tracking zones. The two configurations are **useful mental models, not rigid modes**.\n\n#### Native scroll-driven animation ranges\n\nIf you're familiar with [CSS scroll-driven animations](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FCSS\u002FCSS_scroll-driven_animations), here's how the native `view()` timeline ranges map to ScrollMagic configurations:\n\n| Native range | ScrollMagic equivalent |\n| ------------ | ---------------------- |\n| `cover`      | _intersect_ default — `containerStart: 'opposite', containerEnd: 'opposite'` |\n| `contain`    | _contain_ default — `containerStart: 0, containerEnd: 0` |\n| `entry`      | `containerStart: 'opposite', containerEnd: 0` — container zone collapses to the trailing edge |\n| `exit`       | `containerStart: 0, containerEnd: 'opposite'` — container zone collapses to the leading edge |\n\nThe native `entry-crossing` and `exit-crossing` ranges are equivalent to `entry` and `exit` above — the distinction only applies when subdividing a single native timeline, not when defining standalone tracking ranges.\n\n## Options\n\nAll options are optional. They can be passed to the constructor and updated at any time via setters or `.modify()`.\n\n| Option           | Type                                   | Default                    | Description                                           |\n| ---------------- | -------------------------------------- | -------------------------- | ----------------------------------------------------- |\n| `element`        | `Element \\| string \\| null`            | first child of `container` | The tracked element (or CSS selector). Selectors match only the first element — create one instance per element to track multiple. |\n| `elementStart`   | `number \\| string \\| function`         | `0`                        | Start **inset** on the element.                       |\n| `elementEnd`     | `number \\| string \\| function`         | `0`                        | End **inset** on the element.                         |\n| `container`      | `Window \\| Element \\| string \\| null`  | `window`                   | The scroll container (or CSS selector). Selectors use the first match. |\n| `containerStart` | `number \\| string \\| function \\| null` | inferred (see below)       | Start **inset** on the scroll container.              |\n| `containerEnd`   | `number \\| string \\| function \\| null` | inferred (see below)       | End **inset** on the scroll container.                |\n| `vertical`       | `boolean`                              | `true`                     | Scroll axis. `true` = vertical, `false` = horizontal. |\n\n**Inset values** work like CSS `top`\u002F`bottom`: positive values offset inward from the respective edge in the tracked direction. Accepted value types:\n\n- **Numbers** — pixel values (e.g. `50`)\n- **Strings** — percentage or pixel strings (e.g. `'50%'`, `'20px'`), relative to the parent size (scroll container for container options, element for element options)\n- **Named positions** — `'here'` (0%), `'center'` (50%), `'opposite'` (100%)\n- **Functions** — `(size) => number` for dynamic computation\n\n**`null` means infer:** For `element`, `container`, `containerStart`, or `containerEnd`, setting it to `null` resets them to their inferred default.\n\nFor `containerStart`\u002F`containerEnd` the inferred values depend on `element`:\n\n- **`element` is `null`** → defaults to [**contain**](#contain-default-when-element-is-null): the element is inferred as the first child of the container (for `window` this is `document.body`), container offsets are `'here'` (0%), mapping progress to overall scroll position.\n- **`element` is not `null`** → defaults to [**intersect**](#intersect-default-when-element-is-set): container offsets are `'opposite'` (100%), tracking the element as it scrolls through the full viewport.\n\n## Events\n\nSubscribe with `.on()`, `.off()`, or `.subscribe()` (returns an unsubscribe function). Pass `{ once: true }` to auto-remove the listener after its first invocation. Calling `.off()` or the unsubscribe function after the listener has already been removed (e.g. after a `once` listener fires) is a safe no-op.\n\n| Event      | When                                                     |\n| ---------- | -------------------------------------------------------- |\n| `enter`    | Element enters the active zone (progress leaves 0 or 1)  |\n| `leave`    | Element leaves the active zone (progress reaches 0 or 1) |\n| `progress` | Progress value changes while in the active zone          |\n\nEvery event provides:\n\n```ts\nevent.target; \u002F\u002F the ScrollMagic instance (access all properties, e.g. event.target.progress, event.target.element)\nevent.type; \u002F\u002F 'enter' | 'leave' | 'progress'\nevent.direction; \u002F\u002F 'forward' | 'reverse'\nevent.location; \u002F\u002F 'start' | 'inside' | 'end'\n```\n\n## Examples\n\n```js\n\u002F\u002F Intersect (default): active while any part of the element\n\u002F\u002F is visible in the viewport\nnew ScrollMagic({\n\telement: '#a',\n});\n\n\u002F\u002F Intersect with narrowed container zone:\n\u002F\u002F active while the element passes through the center line\nnew ScrollMagic({\n\telement: '#b',\n\tcontainerStart: 'center',\n\tcontainerEnd: 'center',\n});\n\n\u002F\u002F Same as above, but with element offsets:\n\u002F\u002F starts 50px before the element, ends 100px after it\nnew ScrollMagic({\n\telement: '#c',\n\tcontainerStart: 'center',\n\tcontainerEnd: 'center',\n\telementStart: -50,\n\telementEnd: -100,\n});\n\n\u002F\u002F Fixed scroll distance of 150px, regardless of element height.\n\u002F\u002F elementEnd receives the element's size and offsets from\n\u002F\u002F the bottom — (size - 150) leaves only 150px of track.\nnew ScrollMagic({\n\telement: '#d',\n\tcontainerStart: 'center',\n\tcontainerEnd: 'center',\n\telementEnd: size => size - 150,\n});\n\n\u002F\u002F Contain: active only while the element is fully visible\n\u002F\u002F (element insets pushed to opposite edges = full element height)\nnew ScrollMagic({\n\telement: '#e',\n\telementStart: 'opposite', \u002F\u002F same as '100%'\n\telementEnd: 'opposite', \u002F\u002F same as '100%'\n});\n\n\u002F\u002F Contain (default when no element): track overall scroll progress\nnew ScrollMagic();\n```\n\n## API\n\n```ts\nconst sm = new ScrollMagic(options);\n\n\u002F\u002F Event listeners\nsm.on(type, callback); \u002F\u002F add listener, returns instance (chainable)\nsm.on(type, callback, { once: true }); \u002F\u002F listener auto-removes after first invocation\nsm.off(type, callback); \u002F\u002F remove listener, returns instance (chainable)\nsm.subscribe(type, callback); \u002F\u002F add listener, returns unsubscribe function\nsm.subscribe(type, callback, { once: true }); \u002F\u002F both auto-removes and returns unsubscribe\n\n\u002F\u002F Modify options after creation\nsm.modify({ containerStart: 'center' });\n\n\u002F\u002F All options can also be directly read and written\nconst elem = sm.element; \u002F\u002F get the tracked element\nsm.containerStart = 'center'; \u002F\u002F set individual options\n\n\u002F\u002F Read-only getters\nsm.progress; \u002F\u002F 0–1, how far through the active zone\nsm.activeRange; \u002F\u002F { start, end } container scroll positions where tracking is active\nsm.scrollVelocity; \u002F\u002F px\u002Fs along tracked axis, 0 when idle\nsm.resolvedBounds; \u002F\u002F { element, container } cached layout bounds\n\n\u002F\u002F Refresh — recalculate bounds after external layout changes\nsm.refresh();\n\n\u002F\u002F Pause \u002F resume tracking without destroying\nsm.disable(); \u002F\u002F disconnects all observers, freezes progress\nsm.enable(); \u002F\u002F reconnects observers, recalculates from current state\nsm.disabled; \u002F\u002F read-only, true when disabled or destroyed\n\n\u002F\u002F Lifecycle\nsm.destroy();\n\n\u002F\u002F Static\nScrollMagic.defaultOptions({ vertical: false }); \u002F\u002F get\u002Fset defaults for new instances\nScrollMagic.refreshAll(); \u002F\u002F refresh every active instance\nScrollMagic.destroyAll(); \u002F\u002F destroy every active instance\n```\n\n## When to use `refresh()`\n\nScrollMagic automatically tracks element size changes (via `ResizeObserver`) and scroll position changes. But some layout changes are invisible to these observers — they change an element's **position** without changing its **size** or triggering a scroll event.\n\nCall `refresh()` (or `ScrollMagic.refreshAll()`) after:\n\n- **CSS position\u002Fmargin\u002Fpadding changes** — `element.style.marginTop = '20px'`\n- **CSS class toggles that affect layout** — `element.classList.add('expanded')`\n- **DOM structure changes** — siblings added\u002Fremoved above the element, shifting its position\n- **Images loading without explicit dimensions** — an `\u003Cimg>` above the tracked element loads and expands, pushing it down\n- **Font loading** — `document.fonts.ready.then(() => ScrollMagic.refreshAll())`\n- **Route changes in SPAs** — content swap changes scroll height\n- **Dynamic content loading** — CMS-injected content, third-party widgets\n\n```js\n\u002F\u002F After changing a style that affects position\nelement.style.marginTop = '100px';\nsm.refresh();\n\n\u002F\u002F After fonts finish loading (affects text reflow)\ndocument.fonts.ready.then(() => ScrollMagic.refreshAll());\n\n\u002F\u002F After a framework re-render that changes layout\nonRouteChange(() => ScrollMagic.refreshAll());\n```\n\nNote that `refresh()` is only needed if you want bounds to update **before the next scroll event**. If the user keeps scrolling, element positions are re-read on every scroll frame anyway. `refresh()` matters when layout changes while tracking is active and the scroll position stays the same — e.g. toggling a class or injecting content without any scrolling.\n\n`refresh()` is asynchronous — it schedules recalculation for the next animation frame and returns immediately. Multiple `refresh()` calls within the same frame are batched automatically.\n\n## Plugins\n\nScrollMagic has a plugin system for extending instance behaviour.\n\n```ts\nsm.addPlugin(myPlugin);\nsm.removePlugin(myPlugin);\n```\n\nSee [PLUGINS.md](PLUGINS.md) for the full plugin authoring guide.\n\n## Browser Support\n\nChrome 73+, Firefox 69+, Safari 13.1+, Edge 79+ (aligned to `ResizeObserver` support).\n\n## License\n\nMIT — [Jan Paepke](https:\u002F\u002Fjanpaepke.de)\n\n\u003C!-- TODO: link to extended documentation, demos, migration guide -->\n","ScrollMagic 是一个用于实现滚动交互效果的轻量级 JavaScript 库。其核心功能包括提供元素相对于视口位置的精确数据和事件触发，基于 IntersectionObserver 和 ResizeObserver 技术封装，处理了性能瓶颈和反直觉的边缘情况。ScrollMagic 本身并不执行动画，而是为开发者提供了丰富的滚动位置信息及事件，便于自定义实现如类切换、动画、懒加载、视差效果等。适用于需要创建流畅且响应式的滚动体验的各种 Web 开发场景，尤其适合那些希望在保持高性能的同时增加页面互动性的项目。",2,"2026-06-11 02:56:48","top_language"]