diff --git a/README.md b/README.md
index 9432743..8aa972c 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,19 @@
-# data-scroll
+# DataScroll
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![bundle][bundle-src]][bundle-href]
-[![Codecov][codecov-src]][codecov-href]
+
-Scroll parallax, animation from/to using data attributes
+Easily create responsive parallax effect and scroll animations on any element using data attributes.
+
+- 👀 The element only animates while inside the viewport.
+- 🎯 The element will have its normal position when in the middle of the viewport.
+- 🎨 Animate any property from/to any value.
+- 🌈 Apply breakpoint-specific speeds.
+- ⚙️ Customize the data-attribute names (hell, even use classes if you want!).
+- 🩻 Add helpful markers during development/troubleshooting
+- 🚀 Leverages [GSAP](https://gsap.com/docs/v3/GSAP/) and [ScrollTrigger](https://gsap.com/docs/v3/Plugins/ScrollTrigger/) under the hood.
## Usage
@@ -25,16 +33,231 @@ pnpm install data-scroll
bun install data-scroll
```
-Import:
+Import and instantiate:
+
+```ts
+import { useDataScroll } from "data-scroll";
+
+useDataScroll();
+```
+
+Play around!
+```html
+
Oowee!
+```
+
+## Data attributes
+
+By default, the following data attributes are used:
+
+### `data-scroll-speed`
+
+The speed factor at which the element will move.
+
+> - 1 is the default
+> - 0 is ignored
+> - 0.5 will make that element go at half-speed
+> - 2 will make it go twice as fast.
+
+```html
+Oowee!
+```
+
+#### Responsive speed
+
+You can specify the speed depending on a breakpoint.
+
+```html
+Oowee!
+```
+> In this example, the speed will be 0.5 starting at medium screens and 0.2 starting at large screens.
+
+#### Clamp
+
+Your element is above the fold and you want it to start from its normal position? Use `clamp`!
+
+```html
+Oowee!
+```
+
+> If the element is below the fold, clamp will be ignored.
+
+### `data-scroll-from`
+
+The style from which the element will animate from. See it as a `gsap.from()`
+
+> Beware that the value has to be valid JSON. For functions, use the `getFrom` option.
+
+```html
+Oowee!
+```
+
+> In this example, the element will animate from a black background color to its original background color.
+
+### `data-scroll-to`
+
+The style to which the element will animate to. See it as a `gsap.to()`
+
+> Beware that the value has to be valid JSON. For functions, use the `getTo` option.
+
+```html
+Oowee!
+```
+
+> In this example, the element will rotate to 360 degrees.
+
+### `data-scroll-markers`
+
+Add helpful markers for development/troubleshooting. It's ScrollTrigger's markers option.
+
+```html
+Oowee!
+```
+
+## Options
+
+### autoStart
+
+If `true`, will automatically find and instantiate elements.
+If `false`, you will have to instantiate manually.
+
+- type: `boolean`
+- default: `true`
+
+```ts
+const dataScroll = useDataScroll({
+ autoStart: false,
+});
+
+const targets = document.querySelectorAll('[data-scroll-speed]')
+for (const target of targets) {
+ dataScroll.apply(target)
+}
+```
+
+### screens
+
+The breakpoints used for the `data-scroll-speed` attribute. The defaults are the same as [TailwindCSS](https://tailwindcss.com/docs/breakpoints) but you can customize them.
+
+> Only min-width breakpoints are supported.
+
+- type: `Record`
+- default:
+```ts
+{
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ "2xl": '1536px'
+}
+```
+
+Feel free to have as few or as many screens as you want, naming them in whatever way you’d prefer for your project.
+
+```ts
+useDataScroll({
+ screens: {
+ 'tablet': '640px',
+ 'laptop': '1024px',
+ 'desktop': '1280px',
+ },
+});
+```
+
+```html
+Oowee!
+```
+
+### selector
+
+The selector used to find elements to instantiate.
+- type: `string`
+- default: `[data-scroll-speed],[data-scroll-from],[data-scroll-to]`
+
+```ts
+useDataScroll({
+ selector: '.foo',
+});
+```
+
+### getSpeed
+
+A function that returns the speed of an element.
+- type: `(target: HTMLElement) => string | void`
+- default: `(target) => target.dataset.scrollSpeed`
+
+> If you don't want or just can't use the default data-attributes, you can plug your custom logic.
-```js
-// ESM
-import {} from "data-scroll";
+Let's say you want to use classes instead of data-attributes:
-// CommonJS
-const {} = require("data-scroll");
+```ts
+// Extract from a string the contents inside brackets
+function extractValue(value: string) {
+ const matches = value.match(/\[(.*?)\]/);
+ if (matches) {
+ return matches[1].replaceAll("_", " ");
+ }
+ return "";
+}
+
+useDataScroll({
+ selector: '[class^="scroll-speed"]',
+ getSpeed: (target) => {
+ // Find the class that starts with "scroll-speed"
+ const value = Array.from(target.classList).find((className) => {
+ return className.startsWith("scroll-speed"),
+ });
+ // Extract the value inside the brackets
+ if (value) return extractValue(value);
+ },
+})
+```
+
+```html
+Oowee!
+Oooooowwweeeee!
```
+### getFrom
+
+A function that returns the style to animate the element from.
+- type: `(target: HTMLElement) => gsap.TweenVars | void`
+- default:
+```ts
+(target: HTMLElement) => {
+ const data = target.dataset.scrollFrom;
+ if (data) return JSON.parse(data);
+}
+```
+
+> You could use predefined animations
+```ts
+useDataScroll({
+ getFrom: (target) => {
+ if (target.classList.contains("rotate-360")) {
+ return { rotate: 360 };
+ }
+ }
+})
+```
+
+```html
+
+```
+
+### getTo
+
+A function that returns the style to animate the element to. See [getFrom](#getfrom).
+
+## Roadmap
+
+- [x] documentation
+- [ ] tests
+- [ ] playground
+- [ ] Add data-scroll-progress
+- [ ] Add hooks for ScrollTrigger like toggleClass, onEnter, onLeave, etc.
+
## Development
- Clone this repository
diff --git a/src/index.ts b/src/index.ts
index b14b8e8..2d5b646 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,14 +1,16 @@
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
+gsap.registerPlugin(ScrollTrigger)
+
type Screens = Record
export interface UseDataScrollOptions {
autoStart?: boolean
screens?: Screens
selector?: string
- getSpeed?: (target: HTMLElement) => string | undefined
- getFrom?: (target: HTMLElement) => gsap.TweenVars | undefined
- getTo?: (target: HTMLElement) => gsap.TweenVars | undefined
+ getSpeed?: (target: HTMLElement) => string | void
+ getFrom?: (target: HTMLElement) => gsap.TweenVars | void
+ getTo?: (target: HTMLElement) => gsap.TweenVars | void
getUseMarkers?: (target: HTMLElement) => boolean
}
@@ -31,8 +33,7 @@ let matchMedia: gsap.MatchMedia
function getVariables(element: HTMLElement, dataKey: string) {
const data = element.dataset[dataKey]
- if (!data) return
- return JSON.parse(data) as gsap.TweenVars
+ if (data) return JSON.parse(data) as gsap.TweenVars
}
function getMediaQuery(
@@ -43,7 +44,8 @@ function getMediaQuery(
if (!screens) screens = defaultScreen
if (!screen) {
if (type === 'min') return '(min-width: 0px)'
- if (type === 'max') throw new Error('Only min is supported without screen.')
+ if (type === 'max')
+ throw new Error('Only the type "min" is allowed if screen is undefined.')
return undefined as never
}
@@ -205,7 +207,11 @@ function apply(target: HTMLElement, options?: ApplyOptions) {
)
// Prevent the element from being visible before the animation starts
- if (speed > 1 && startOffset !== 0) {
+ if (
+ speed > 1 &&
+ startOffset !== 0 &&
+ ScrollTrigger.positionInViewport(target, 'bottom') > 1
+ ) {
gsap.set(target, {
visibility: 'hidden',
})