-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: add documentation, add example * feat: wip * fix: add missing gsap imports * types: fix build and rename some types * Update scrollytelling/src/types/index.ts Co-authored-by: Joaquín R. Montes <joaquin@basement.studio> * Revert "Update scrollytelling/src/types/index.ts" This reverts commit 47ef057. * semver: add changeset file --------- Co-authored-by: Nazareno Oviedo <nazareno@basement.studio> Co-authored-by: Nazareno Oviedo <nazarenooviedo@gmail.com>
- Loading branch information
1 parent
7570743
commit 4ed8eff
Showing
14 changed files
with
608 additions
and
551 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@bsmnt/scrollytelling": minor | ||
--- | ||
|
||
File reordering |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import * as React from "react"; | ||
import { Slot } from "@radix-ui/react-slot"; | ||
|
||
|
||
|
||
// ---- Types ---- | ||
import { | ||
AnimationProps, | ||
DataOrDataArray, | ||
TweenWithChildrenDef, | ||
TweenWithTargetDef, | ||
} from "../../types"; | ||
|
||
// ---- Utils ---- | ||
import { buildDeclarativeTween, getTweenTarget } from "../../util"; | ||
import { useDispatcher, useScrollytelling } from "../../context"; | ||
|
||
/* ------------------------------------------------------------------------------------------------- | ||
* Animation | ||
* -----------------------------------------------------------------------------------------------*/ | ||
|
||
/** | ||
* Animation component enables defining animations using GSAP within the Scrollytelling context. | ||
* | ||
* @param {AnimationProps} props - Animation component props | ||
* @returns {null | React.ReactElement} Animation component | ||
* @link https://github.com/basementstudio/scrollytelling/blob/main/docs/api.md#animation | ||
*/ | ||
|
||
export function Animation(props: { | ||
tween: DataOrDataArray<TweenWithTargetDef>; | ||
}): null; | ||
|
||
export function Animation(props: { | ||
children: React.ReactNode; | ||
tween: DataOrDataArray<TweenWithChildrenDef>; | ||
}): React.ReactElement; | ||
|
||
export function Animation(props: AnimationProps): React.ReactElement | null { | ||
const ref = React.useRef<HTMLElement>(null); | ||
const id = React.useId(); | ||
|
||
const { timeline } = useScrollytelling(); | ||
const { getTimelineSpace } = useDispatcher(); | ||
|
||
React.useEffect(() => { | ||
if (!timeline || !props.tween) return; | ||
|
||
const addTweenToTimeline = ( | ||
tween: TweenWithChildrenDef | TweenWithTargetDef | ||
) => { | ||
const tweenTarget = getTweenTarget({ | ||
targetContainer: "target" in tween ? tween : {}, | ||
ref, | ||
}); | ||
if (tweenTarget) { | ||
const timelineSpace = getTimelineSpace({ | ||
start: tween.start, | ||
end: tween.end, | ||
}); | ||
const cleanup = buildDeclarativeTween({ | ||
id, | ||
timeline, | ||
op: tween, | ||
target: tweenTarget, | ||
duration: timelineSpace.duration, | ||
position: timelineSpace.position, | ||
}); | ||
|
||
return () => { | ||
cleanup(); | ||
timelineSpace.cleanup(); | ||
}; | ||
} else return () => undefined; | ||
}; | ||
|
||
if (Array.isArray(props.tween)) { | ||
const cleanupTweens = props.tween.map((tween) => { | ||
const cleanup = addTweenToTimeline(tween); | ||
return cleanup; | ||
}); | ||
return () => { | ||
cleanupTweens.forEach((cleanup) => cleanup()); | ||
}; | ||
} else { | ||
const cleanup = addTweenToTimeline(props.tween); | ||
return () => { | ||
cleanup(); | ||
}; | ||
} | ||
}, [getTimelineSpace, id, props.tween, timeline]); | ||
|
||
if (props.children) { | ||
return <Slot ref={ref}>{props.children}</Slot>; | ||
} | ||
return null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import * as React from "react"; | ||
import { useScrollytelling } from "../../context"; | ||
|
||
// ---- Debugger | ||
|
||
export const Debugger = () => { | ||
const { timeline, rootRef } = useScrollytelling(); | ||
const [progress, setProgress] = React.useState<{ | ||
percentage: string; | ||
px: string; | ||
}>(); | ||
|
||
React.useEffect(() => { | ||
if (!timeline || !rootRef.current) return; | ||
const handleUpdate = () => { | ||
const progress = timeline.progress(); | ||
if (!timeline.scrollTrigger) return; | ||
|
||
const start = timeline.scrollTrigger.start; | ||
const end = timeline.scrollTrigger.end; | ||
const scroll = (end - start) * progress; | ||
setProgress({ | ||
percentage: `${(progress * 100).toFixed(2)}%`, | ||
px: `${scroll.toFixed(0)}px`, | ||
}); | ||
}; | ||
|
||
timeline.eventCallback("onUpdate", handleUpdate); | ||
|
||
return () => { | ||
timeline.eventCallback("onUpdate", null); | ||
}; | ||
}, [timeline, rootRef]); | ||
|
||
return ( | ||
<div | ||
style={{ | ||
width: "370px", | ||
padding: "24px", | ||
border: "1px solid black", | ||
fontSize: "12px", | ||
background: "#360202", | ||
}} | ||
> | ||
<pre>{JSON.stringify({ progress }, null, 2)}</pre> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import * as React from "react"; | ||
|
||
import { Animation } from "../animation"; | ||
|
||
/* ------------------------------------------------------------------------------------------------- | ||
* Parallax | ||
* -----------------------------------------------------------------------------------------------*/ | ||
|
||
import { RequireAtLeastOne, StartEndOptions, TweenTarget, UnitValue } from "../../types"; | ||
|
||
interface ParallaxProps { | ||
tween: StartEndOptions & { | ||
target?: TweenTarget; // Optional: The target element or elements to apply the animation to. | ||
} & RequireAtLeastOne<{ | ||
movementX?: UnitValue; // The amount of movement on the X-axis. | ||
movementY?: UnitValue; // The amount of movement on the Y-axis. | ||
}>; | ||
children?: React.ReactNode; // Optional: Content to be rendered inside the Parallax component. | ||
} | ||
|
||
/** | ||
* Applies a parallax effect to its children using GSAP animations. | ||
* | ||
* @returns {JSX.Element} `Animation` component with parallax effect applied. _(Expects `children`)_ | ||
* @link [[@bsmnt/scrollytelling] API Docs: Parallax](https://github.com/basementstudio/scrollytelling/blob/main/docs/api.md#parallax) | ||
*/ | ||
|
||
export const Parallax: React.FC<ParallaxProps> = ({ children, tween }): JSX.Element => { | ||
if (!tween.movementY && !tween.movementX) { | ||
throw new Error( | ||
"At least one of movementY and movementX is required in Parallax component." | ||
); | ||
} | ||
|
||
return ( | ||
<Animation | ||
tween={{ | ||
...tween, | ||
fromTo: [ | ||
{ | ||
y: tween.movementY | ||
? -1 * tween.movementY.value + tween.movementY.unit | ||
: undefined, | ||
x: tween.movementX | ||
? -1 * tween.movementX.value + tween.movementX.unit | ||
: undefined, | ||
}, | ||
{ | ||
y: tween.movementY | ||
? tween.movementY.value + tween.movementY.unit | ||
: undefined, | ||
x: tween.movementX | ||
? tween.movementX.value + tween.movementX.unit | ||
: undefined, | ||
ease: "linear", | ||
}, | ||
], | ||
}} | ||
> | ||
{children} | ||
</Animation> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import * as React from "react"; | ||
|
||
/* ------------------------------------------------------------------------------------------------- | ||
* Pin | ||
* -----------------------------------------------------------------------------------------------*/ | ||
|
||
interface PinProps { | ||
childHeight: string | number; // The height of the pinned element in the pin. | ||
pinSpacerHeight: string | number; // The height of the spacer reserved for the pinned element in the pin. | ||
childClassName?: string; // Optional: Custom CSS class name for the child element | ||
children?: React.ReactNode; // Optional: Content to be rendered inside the pinned element | ||
pinSpacerClassName?: string; // Optional: Custom CSS class name for the pin spacer element | ||
top?: string | number; // Optional: Custom top position for the pinned element | ||
} | ||
|
||
/** | ||
* Pin component enables pinning an element in its initial position while the remaining content scrolls. | ||
* It ensures that the pinned element stays fixed at its starting position within the active duration of Scrollytelling. | ||
* | ||
* @param {PinProps} props - Pin component props | ||
* @returns {JSX.Element} Pin component | ||
* @link https://github.com/basementstudio/scrollytelling/blob/main/docs/api.md#pin | ||
*/ | ||
|
||
export const Pin = React.forwardRef<HTMLDivElement, PinProps>( | ||
( | ||
{ | ||
childClassName, | ||
childHeight, | ||
children, | ||
pinSpacerClassName, | ||
pinSpacerHeight, | ||
top = 0, | ||
}: PinProps, | ||
ref | ||
) => { | ||
if (!childHeight || !pinSpacerHeight) { | ||
throw new Error( | ||
"childHeight and pinSpacerHeight are required in Pin component." | ||
); | ||
} | ||
|
||
return ( | ||
<div | ||
className={pinSpacerClassName} | ||
ref={ref} | ||
style={{ height: pinSpacerHeight }} | ||
> | ||
<div | ||
className={childClassName} | ||
style={{ height: childHeight, position: "sticky", top }} | ||
> | ||
{children} | ||
</div> | ||
</div> | ||
); | ||
} | ||
); | ||
|
||
Pin.displayName = "Pin"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { gsap } from "gsap"; | ||
// ---- Hooks ---- | ||
import { useIsoLayoutEffect } from "../../hooks/use-iso-layout-effect"; | ||
|
||
// ---- Types ---- | ||
import { Plugin } from "../../types"; | ||
|
||
/* ------------------------------------------------------------------------------------------------- | ||
* RegisterGsapPlugins | ||
* -----------------------------------------------------------------------------------------------*/ | ||
|
||
interface RegisterGsapPluginsProps { | ||
plugins: Plugin[]; | ||
} | ||
|
||
/** | ||
* RegisterGsapPlugins registers GSAP plugins. | ||
* | ||
* @param {RegisterGsapPluginsProps} props - RegisterGsapPlugins component props | ||
* @returns {null} | ||
* @link https://github.com/basementstudio/scrollytelling/blob/main/docs/api.md#register-plugin | ||
*/ | ||
|
||
export const RegisterGsapPlugins = ({ plugins }: RegisterGsapPluginsProps) => { | ||
// This needs to run before ScrollTrigger does any animations | ||
useIsoLayoutEffect(() => { | ||
gsap.registerPlugin(...plugins); | ||
}, []); | ||
|
||
return null; | ||
}; |
Oops, something went wrong.
4ed8eff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
scrollytelling-website – ./
scrollytelling-website-git-main-basement.vercel.app
scrollytelling-website.vercel.app
scrollytelling.basement.studio
scrollytelling-website-basement.vercel.app