@stianlarsen/react-light-beam 1.2.0 → 2.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/dist/index.js CHANGED
@@ -1,10 +1,49 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
- var framerMotion = require('framer-motion');
4
+ var gsap2 = require('gsap');
5
+ var ScrollTrigger = require('gsap/ScrollTrigger');
5
6
  var react = require('react');
6
7
  var jsxRuntime = require('react/jsx-runtime');
7
8
 
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var gsap2__default = /*#__PURE__*/_interopDefault(gsap2);
12
+
13
+ var useIsomorphicLayoutEffect = typeof document !== "undefined" ? react.useLayoutEffect : react.useEffect;
14
+ var isConfig = (value) => value && !Array.isArray(value) && typeof value === "object";
15
+ var emptyArray = [];
16
+ var defaultConfig = {};
17
+ var _gsap = gsap2__default.default;
18
+ var useGSAP = (callback, dependencies = emptyArray) => {
19
+ let config = defaultConfig;
20
+ if (isConfig(callback)) {
21
+ config = callback;
22
+ callback = null;
23
+ dependencies = "dependencies" in config ? config.dependencies : emptyArray;
24
+ } else if (isConfig(dependencies)) {
25
+ config = dependencies;
26
+ dependencies = "dependencies" in config ? config.dependencies : emptyArray;
27
+ }
28
+ callback && typeof callback !== "function" && console.warn("First parameter must be a function or config object");
29
+ const { scope, revertOnUpdate } = config, mounted = react.useRef(false), context = react.useRef(_gsap.context(() => {
30
+ }, scope)), contextSafe = react.useRef((func) => context.current.add(null, func)), deferCleanup = dependencies && dependencies.length && !revertOnUpdate;
31
+ deferCleanup && useIsomorphicLayoutEffect(() => {
32
+ mounted.current = true;
33
+ return () => context.current.revert();
34
+ }, emptyArray);
35
+ useIsomorphicLayoutEffect(() => {
36
+ callback && context.current.add(callback, scope);
37
+ if (!deferCleanup || !mounted.current) {
38
+ return () => context.current.revert();
39
+ }
40
+ }, dependencies);
41
+ return { context: context.current, contextSafe: contextSafe.current };
42
+ };
43
+ useGSAP.register = (core) => {
44
+ _gsap = core;
45
+ };
46
+ useGSAP.headless = true;
8
47
  var useIsDarkmode = () => {
9
48
  const [isDarkmode, setIsDarkmodeActive] = react.useState(false);
10
49
  react.useEffect(() => {
@@ -20,14 +59,21 @@ var useIsDarkmode = () => {
20
59
  }, []);
21
60
  return { isDarkmode };
22
61
  };
62
+ gsap2__default.default.registerPlugin(ScrollTrigger.ScrollTrigger, useGSAP);
23
63
  var defaultStyles = {
24
64
  height: "var(--react-light-beam-height, 500px)",
25
65
  width: "var(--react-light-beam-width, 100vw)",
26
- transition: "var(--react-light-beam-transition, all 0.25s ease)",
27
- willChange: "all",
66
+ // CRITICAL: NO transition on GSAP-controlled properties (background, opacity, mask)
67
+ // Transitions would fight with GSAP's instant updates, causing visual glitches
68
+ // especially when scroll direction changes
69
+ transition: "none",
70
+ willChange: "background, opacity",
71
+ // Specific properties for better performance
28
72
  userSelect: "none",
29
73
  pointerEvents: "none",
30
- WebkitTransition: "var(--react-light-beam-transition, all 0.25s ease)",
74
+ contain: "layout style paint",
75
+ // CSS containment for better performance
76
+ WebkitTransition: "none",
31
77
  WebkitUserSelect: "none",
32
78
  MozUserSelect: "none"
33
79
  };
@@ -46,97 +92,142 @@ var LightBeam = ({
46
92
  disableDefaultStyles = false
47
93
  }) => {
48
94
  const elementRef = react.useRef(null);
49
- const inViewProgress = framerMotion.useMotionValue(0);
50
- const opacity = framerMotion.useMotionValue(0.839322);
51
95
  const { isDarkmode } = useIsDarkmode();
52
96
  const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;
97
+ const colorRef = react.useRef(chosenColor);
98
+ const invertRef = react.useRef(invert);
99
+ const maskByProgressRef = react.useRef(maskLightByProgress);
100
+ react.useEffect(() => {
101
+ colorRef.current = chosenColor;
102
+ invertRef.current = invert;
103
+ maskByProgressRef.current = maskLightByProgress;
104
+ }, [chosenColor, colorLightmode, colorDarkmode, invert, maskLightByProgress]);
53
105
  react.useEffect(() => {
54
106
  onLoaded && onLoaded();
55
107
  }, []);
56
- react.useEffect(() => {
57
- if (typeof window !== "undefined") {
58
- const handleScroll = () => {
59
- if (elementRef.current) {
60
- const rect = elementRef.current.getBoundingClientRect();
61
- const windowHeight = window.innerHeight;
62
- const adjustedFullWidth = 1 - fullWidth;
63
- const progress = invert ? 0 + Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight)) : 1 - Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight));
64
- inViewProgress.set(progress);
65
- opacity.set(0.839322 + (1 - 0.839322) * progress);
108
+ useGSAP(
109
+ () => {
110
+ const element = elementRef.current;
111
+ if (!element || typeof window === "undefined") return;
112
+ const opacityMin = 0.839322;
113
+ const opacityRange = 0.160678;
114
+ const interpolateBackground = (progress, color) => {
115
+ const leftPos = 90 - progress * 90;
116
+ const rightPos = 10 + progress * 90;
117
+ const leftSize = 150 - progress * 50;
118
+ return `conic-gradient(from 90deg at ${leftPos}% 0%, ${color}, transparent 180deg) 0% 0% / 50% ${leftSize}% no-repeat, conic-gradient(from 270deg at ${rightPos}% 0%, transparent 180deg, ${color}) 100% 0% / 50% 100% no-repeat`;
119
+ };
120
+ const interpolateMask = (progress, color) => {
121
+ if (!maskByProgressRef.current) {
122
+ return `linear-gradient(to bottom, ${color} 25%, transparent 95%)`;
66
123
  }
124
+ const stopPoint = 50 + progress * 45;
125
+ return `linear-gradient(to bottom, ${color} 0%, transparent ${stopPoint}%)`;
67
126
  };
68
- const handleScrollThrottled = throttle(handleScroll);
69
- const target = scrollElement || document.body || document.documentElement;
70
- target.addEventListener("scroll", handleScrollThrottled);
71
- window.addEventListener("resize", handleScrollThrottled);
72
- handleScroll();
127
+ const adjustedFullWidth = 1 - fullWidth;
128
+ const calculateProgress = (rawProgress) => {
129
+ const normalizedPosition = Math.max(
130
+ adjustedFullWidth,
131
+ // Minimum (floor)
132
+ Math.min(1, 1 - rawProgress)
133
+ // Convert GSAP progress to Framer's normalized position
134
+ );
135
+ return invertRef.current ? normalizedPosition : 1 - normalizedPosition;
136
+ };
137
+ const scroller = scrollElement ? scrollElement : void 0;
138
+ const st = ScrollTrigger.ScrollTrigger.create({
139
+ trigger: element,
140
+ start: "top bottom",
141
+ // Element top hits viewport bottom
142
+ end: "top top",
143
+ // Element top hits viewport top
144
+ scroller,
145
+ scrub: true,
146
+ // Instant scrubbing
147
+ onUpdate: (self) => {
148
+ const progress = calculateProgress(self.progress);
149
+ gsap2__default.default.set(element, {
150
+ background: interpolateBackground(progress, colorRef.current),
151
+ opacity: opacityMin + opacityRange * progress,
152
+ maskImage: interpolateMask(progress, colorRef.current),
153
+ webkitMaskImage: interpolateMask(progress, colorRef.current)
154
+ });
155
+ },
156
+ onRefresh: (self) => {
157
+ const progress = calculateProgress(self.progress);
158
+ gsap2__default.default.set(element, {
159
+ background: interpolateBackground(progress, colorRef.current),
160
+ opacity: opacityMin + opacityRange * progress,
161
+ maskImage: interpolateMask(progress, colorRef.current),
162
+ webkitMaskImage: interpolateMask(progress, colorRef.current)
163
+ });
164
+ }
165
+ });
166
+ const initialProgress = calculateProgress(st.progress);
167
+ gsap2__default.default.set(element, {
168
+ background: interpolateBackground(initialProgress, colorRef.current),
169
+ opacity: opacityMin + opacityRange * initialProgress,
170
+ maskImage: interpolateMask(initialProgress, colorRef.current),
171
+ webkitMaskImage: interpolateMask(initialProgress, colorRef.current)
172
+ });
173
+ const refreshTimeout = setTimeout(() => {
174
+ ScrollTrigger.ScrollTrigger.refresh();
175
+ }, 100);
73
176
  return () => {
74
- target.removeEventListener("scroll", handleScrollThrottled);
75
- window.removeEventListener("resize", handleScrollThrottled);
177
+ st.kill();
178
+ clearTimeout(refreshTimeout);
76
179
  };
180
+ },
181
+ {
182
+ // CRITICAL: Use refs for frequently changing values!
183
+ // colorRef, invertRef, maskByProgressRef allow updates without recreating ScrollTrigger
184
+ // This prevents visual glitches when these values change mid-scroll
185
+ // Only include values that affect ScrollTrigger's position/range calculations
186
+ dependencies: [
187
+ fullWidth,
188
+ // Affects trigger range
189
+ scrollElement
190
+ // Affects which element to watch
191
+ ],
192
+ scope: elementRef
77
193
  }
78
- }, [inViewProgress, opacity, scrollElement, fullWidth, invert]);
79
- const backgroundPosition = framerMotion.useTransform(
80
- inViewProgress,
81
- [0, 1],
82
- [
83
- `conic-gradient(from 90deg at 90% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 150% no-repeat, conic-gradient(from 270deg at 10% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`,
84
- `conic-gradient(from 90deg at 0% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 100% no-repeat, conic-gradient(from 270deg at 100% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`
85
- ]
86
- );
87
- const maskImageOpacity = framerMotion.useTransform(
88
- inViewProgress,
89
- [0, 1],
90
- [
91
- `linear-gradient(to bottom, ${chosenColor} 0%, transparent 50%)`,
92
- `linear-gradient(to bottom, ${chosenColor} 0%, transparent 95%)`
93
- ]
94
194
  );
95
- const maskImage = maskLightByProgress ? maskImageOpacity : `linear-gradient(to bottom, ${chosenColor} 25%, transparent 95%)`;
96
195
  const combinedClassName = `react-light-beam ${className || ""}`.trim();
97
196
  const finalStyles = disableDefaultStyles ? {
98
- // No default styles, only motion values and user styles
99
- background: backgroundPosition,
100
- opacity,
101
- maskImage,
102
- WebkitMaskImage: maskImage,
197
+ // No default styles, only user styles
103
198
  willChange: "background, opacity",
199
+ contain: "layout style paint",
104
200
  ...style
105
201
  // User styles override
106
202
  } : {
107
- // Merge default styles with motion values
203
+ // Merge default styles with user styles
108
204
  ...defaultStyles,
109
- background: backgroundPosition,
110
- // MotionValue (overrides default)
111
- opacity,
112
- // MotionValue (overrides default)
113
- maskImage,
114
- // MotionValue or string
115
- WebkitMaskImage: maskImage,
116
- willChange: "background, opacity",
117
205
  ...style
118
206
  // User styles override everything
119
207
  };
120
- const motionProps = {
121
- style: finalStyles,
122
- ref: elementRef,
123
- className: combinedClassName,
124
- ...id ? { id } : {}
125
- };
126
- return /* @__PURE__ */ jsxRuntime.jsx(framerMotion.motion.div, { ...motionProps });
127
- };
128
- var throttle = (func) => {
129
- let ticking = false;
130
- return function(...args) {
131
- if (!ticking) {
132
- requestAnimationFrame(() => {
133
- func.apply(this, args);
134
- ticking = false;
135
- });
136
- ticking = true;
208
+ return /* @__PURE__ */ jsxRuntime.jsx(
209
+ "div",
210
+ {
211
+ ref: elementRef,
212
+ className: combinedClassName,
213
+ style: finalStyles,
214
+ ...id ? { id } : {}
137
215
  }
138
- };
216
+ );
139
217
  };
218
+ /*! Bundled license information:
219
+
220
+ @gsap/react/src/index.js:
221
+ (*!
222
+ * @gsap/react 2.1.2
223
+ * https://gsap.com
224
+ *
225
+ * Copyright 2008-2025, GreenSock. All rights reserved.
226
+ * Subject to the terms at https://gsap.com/standard-license or for
227
+ * Club GSAP members, the agreement issued with that membership.
228
+ * @author: Jack Doyle, jack@greensock.com
229
+ *)
230
+ */
140
231
 
141
232
  exports.LightBeam = LightBeam;
142
233
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useDarkmode.tsx","../src/index.tsx"],"names":["useState","useEffect","useRef","useMotionValue","useTransform","jsx","motion"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\nimport { useEffect, useState } from \"react\";\n\nexport const useIsDarkmode = () => {\n const [isDarkmode, setIsDarkmodeActive] = useState(false);\n\n useEffect(() => {\n const matchMedia = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\n const handleChange = () => {\n setIsDarkmodeActive(matchMedia.matches);\n };\n\n // Set the initial value\n setIsDarkmodeActive(matchMedia.matches);\n\n // Listen for changes\n matchMedia.addEventListener(\"change\", handleChange);\n\n // Cleanup listener on unmount\n return () => {\n matchMedia.removeEventListener(\"change\", handleChange);\n };\n }, []);\n\n return { isDarkmode };\n};\n","\"use client\";\nimport {motion, useMotionValue, useTransform} from \"framer-motion\";\nimport React, {useEffect, useRef} from \"react\";\nimport {LightBeamProps} from \"../types/types\";\nimport {useIsDarkmode} from \"./hooks/useDarkmode\";\n\n// Default inline styles using CSS variables for easy customization\n// Users can override via className by setting CSS variables\nconst defaultStyles: React.CSSProperties = {\n height: \"var(--react-light-beam-height, 500px)\",\n width: \"var(--react-light-beam-width, 100vw)\",\n transition: \"var(--react-light-beam-transition, all 0.25s ease)\",\n willChange: \"all\",\n userSelect: \"none\",\n pointerEvents: \"none\",\n WebkitTransition: \"var(--react-light-beam-transition, all 0.25s ease)\",\n WebkitUserSelect: \"none\",\n MozUserSelect: \"none\",\n};\n\nexport const LightBeam = ({\n className,\n style,\n colorLightmode = \"rgba(0,0,0, 0.5)\",\n colorDarkmode = \"rgba(255, 255, 255, 0.5)\",\n maskLightByProgress = false,\n fullWidth = 1.0, // Default to full width\n invert = false,\n id = undefined,\n onLoaded = undefined,\n scrollElement,\n disableDefaultStyles = false,\n}: LightBeamProps) => {\n const elementRef = useRef<HTMLDivElement>(null);\n const inViewProgress = useMotionValue(0);\n const opacity = useMotionValue(0.839322);\n const { isDarkmode } = useIsDarkmode();\n const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;\n\n useEffect(() => {\n onLoaded && onLoaded();\n }, []);\n\n useEffect(() => {\n if (typeof window !== \"undefined\") {\n const handleScroll = () => {\n if (elementRef.current) {\n const rect = elementRef.current.getBoundingClientRect();\n const windowHeight = window.innerHeight;\n\n // Invert the fullWidth value: 1 becomes 0, and 0 becomes 1\n const adjustedFullWidth = 1 - fullWidth;\n\n // Calculate progress\n const progress = invert\n ? 0 +\n Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight))\n : 1 -\n Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight));\n\n // Update motion values\n inViewProgress.set(progress);\n opacity.set(0.839322 + (1 - 0.839322) * progress);\n }\n };\n\n const handleScrollThrottled = throttle(handleScroll); // Approx 60fps\n\n // Default to document.body (works in modern React/Next.js setups)\n // window doesn't fire scroll events in many modern setups!\n const target = scrollElement || document.body || document.documentElement;\n\n target.addEventListener(\"scroll\", handleScrollThrottled);\n window.addEventListener(\"resize\", handleScrollThrottled);\n\n // Initial call to handleScroll to set initial state\n handleScroll();\n\n return () => {\n target.removeEventListener(\"scroll\", handleScrollThrottled);\n window.removeEventListener(\"resize\", handleScrollThrottled);\n };\n }\n }, [inViewProgress, opacity, scrollElement, fullWidth, invert]);\n\n const backgroundPosition = useTransform(\n inViewProgress,\n [0, 1],\n [\n `conic-gradient(from 90deg at 90% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 150% no-repeat, conic-gradient(from 270deg at 10% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`,\n `conic-gradient(from 90deg at 0% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 100% no-repeat, conic-gradient(from 270deg at 100% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`,\n ]\n );\n const maskImageOpacity = useTransform(\n inViewProgress,\n [0, 1],\n [\n `linear-gradient(to bottom, ${chosenColor} 0%, transparent 50%)`,\n `linear-gradient(to bottom, ${chosenColor} 0%, transparent 95%)`,\n ]\n );\n\n const maskImage = maskLightByProgress\n ? maskImageOpacity\n : `linear-gradient(to bottom, ${chosenColor} 25%, transparent 95%)`;\n\n const combinedClassName = `react-light-beam ${className || \"\"}`.trim();\n\n // CRITICAL: MotionValues must be passed directly to motion.div style prop\n // Don't spread them into plain objects or reactivity breaks!\n const finalStyles = disableDefaultStyles\n ? {\n // No default styles, only motion values and user styles\n background: backgroundPosition,\n opacity: opacity,\n maskImage: maskImage,\n WebkitMaskImage: maskImage,\n willChange: \"background, opacity\",\n ...style, // User styles override\n }\n : {\n // Merge default styles with motion values\n ...defaultStyles,\n background: backgroundPosition, // MotionValue (overrides default)\n opacity: opacity, // MotionValue (overrides default)\n maskImage: maskImage, // MotionValue or string\n WebkitMaskImage: maskImage,\n willChange: \"background, opacity\",\n ...style, // User styles override everything\n };\n\n const motionProps: any = {\n style: finalStyles,\n ref: elementRef,\n className: combinedClassName,\n ...(id ? { id } : {}),\n };\n\n return <motion.div {...motionProps} />;\n};\n\nconst throttle = (func: Function) => {\n let ticking = false;\n return function (this: any, ...args: any[]) {\n if (!ticking) {\n requestAnimationFrame(() => {\n func.apply(this, args);\n ticking = false;\n });\n ticking = true;\n }\n };\n};\n"]}
1
+ {"version":3,"sources":["../node_modules/@gsap/react/src/index.js","../src/hooks/useDarkmode.tsx","../src/index.tsx"],"names":["useLayoutEffect","useEffect","gsap","useRef","useState","ScrollTrigger","jsx"],"mappings":"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","file":"index.js","sourcesContent":["/*!\n * @gsap/react 2.1.2\n * https://gsap.com\n *\n * Copyright 2008-2025, GreenSock. All rights reserved.\n * Subject to the terms at https://gsap.com/standard-license or for\n * Club GSAP members, the agreement issued with that membership.\n * @author: Jack Doyle, jack@greensock.com\n*/\n/* eslint-disable */\nimport { useEffect, useLayoutEffect, useRef } from \"react\";\nimport gsap from \"gsap\";\n\nlet useIsomorphicLayoutEffect = typeof document !== \"undefined\" ? useLayoutEffect : useEffect,\n isConfig = value => value && !Array.isArray(value) && typeof(value) === \"object\",\n emptyArray = [],\n defaultConfig = {},\n _gsap = gsap; // accommodates situations where different versions of GSAP may be loaded, so a user can gsap.registerPlugin(useGSAP);\n\nexport const useGSAP = (callback, dependencies = emptyArray) => {\n let config = defaultConfig;\n if (isConfig(callback)) {\n config = callback;\n callback = null;\n dependencies = \"dependencies\" in config ? config.dependencies : emptyArray;\n } else if (isConfig(dependencies)) {\n config = dependencies;\n dependencies = \"dependencies\" in config ? config.dependencies : emptyArray;\n }\n (callback && typeof callback !== \"function\") && console.warn(\"First parameter must be a function or config object\");\n const { scope, revertOnUpdate } = config,\n mounted = useRef(false),\n context = useRef(_gsap.context(() => { }, scope)),\n contextSafe = useRef((func) => context.current.add(null, func)),\n deferCleanup = dependencies && dependencies.length && !revertOnUpdate;\n deferCleanup && useIsomorphicLayoutEffect(() => {\n mounted.current = true;\n return () => context.current.revert();\n }, emptyArray);\n useIsomorphicLayoutEffect(() => {\n callback && context.current.add(callback, scope);\n if (!deferCleanup || !mounted.current) { // React renders bottom-up, thus there could be hooks with dependencies that run BEFORE the component mounts, thus cleanup wouldn't occur since a hook with an empty dependency Array would only run once the component mounts.\n return () => context.current.revert();\n }\n }, dependencies);\n return { context: context.current, contextSafe: contextSafe.current };\n};\nuseGSAP.register = core => { _gsap = core; };\nuseGSAP.headless = true; // doesn't require the window to be registered.\n","\"use client\";\nimport { useEffect, useState } from \"react\";\n\nexport const useIsDarkmode = () => {\n const [isDarkmode, setIsDarkmodeActive] = useState(false);\n\n useEffect(() => {\n const matchMedia = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\n const handleChange = () => {\n setIsDarkmodeActive(matchMedia.matches);\n };\n\n // Set the initial value\n setIsDarkmodeActive(matchMedia.matches);\n\n // Listen for changes\n matchMedia.addEventListener(\"change\", handleChange);\n\n // Cleanup listener on unmount\n return () => {\n matchMedia.removeEventListener(\"change\", handleChange);\n };\n }, []);\n\n return { isDarkmode };\n};\n","\"use client\";\nimport gsap from \"gsap\";\nimport {ScrollTrigger} from \"gsap/ScrollTrigger\";\nimport {useGSAP} from \"@gsap/react\";\nimport React, {useEffect, useRef} from \"react\";\nimport {LightBeamProps} from \"../types/types\";\nimport {useIsDarkmode} from \"./hooks/useDarkmode\";\n\n// Register GSAP plugins\ngsap.registerPlugin(ScrollTrigger, useGSAP);\n\n// Default inline styles using CSS variables for easy customization\n// Users can override via className by setting CSS variables\nconst defaultStyles: React.CSSProperties = {\n height: \"var(--react-light-beam-height, 500px)\",\n width: \"var(--react-light-beam-width, 100vw)\",\n // CRITICAL: NO transition on GSAP-controlled properties (background, opacity, mask)\n // Transitions would fight with GSAP's instant updates, causing visual glitches\n // especially when scroll direction changes\n transition: \"none\",\n willChange: \"background, opacity\", // Specific properties for better performance\n userSelect: \"none\",\n pointerEvents: \"none\",\n contain: \"layout style paint\", // CSS containment for better performance\n WebkitTransition: \"none\",\n WebkitUserSelect: \"none\",\n MozUserSelect: \"none\",\n};\n\nexport const LightBeam = ({\n className,\n style,\n colorLightmode = \"rgba(0,0,0, 0.5)\",\n colorDarkmode = \"rgba(255, 255, 255, 0.5)\",\n maskLightByProgress = false,\n fullWidth = 1.0, // Default to full width\n invert = false,\n id = undefined,\n onLoaded = undefined,\n scrollElement,\n disableDefaultStyles = false,\n }: LightBeamProps) => {\n const elementRef = useRef<HTMLDivElement>(null);\n const {isDarkmode} = useIsDarkmode();\n const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;\n\n // Use refs to track current values without triggering useGSAP re-runs\n const colorRef = useRef(chosenColor);\n const invertRef = useRef(invert);\n const maskByProgressRef = useRef(maskLightByProgress);\n\n // Update refs whenever values change\n useEffect(() => {\n colorRef.current = chosenColor;\n invertRef.current = invert;\n maskByProgressRef.current = maskLightByProgress;\n }, [chosenColor, colorLightmode, colorDarkmode, invert, maskLightByProgress]);\n\n // Call onLoaded callback when component mounts\n useEffect(() => {\n onLoaded && onLoaded();\n }, []);\n\n // GSAP ScrollTrigger implementation\n useGSAP(\n () => {\n const element = elementRef.current;\n if (!element || typeof window === \"undefined\") return;\n\n // Pre-calculate constants for performance\n const opacityMin = 0.839322;\n const opacityRange = 0.160678; // 1 - 0.839322\n\n // Helper function to interpolate background gradient\n // NOTE: Takes color as parameter to always use current value (not closure!)\n const interpolateBackground = (progress: number, color: string): string => {\n // At progress 0: gradients are wide (90% and 10% positions)\n // At progress 1: gradients converge (0% and 100% positions)\n const leftPos = 90 - progress * 90; // 90% → 0%\n const rightPos = 10 + progress * 90; // 10% → 100%\n const leftSize = 150 - progress * 50; // 150% → 100%\n\n return `conic-gradient(from 90deg at ${leftPos}% 0%, ${color}, transparent 180deg) 0% 0% / 50% ${leftSize}% no-repeat, conic-gradient(from 270deg at ${rightPos}% 0%, transparent 180deg, ${color}) 100% 0% / 50% 100% no-repeat`;\n };\n\n // Helper function to interpolate mask\n // NOTE: Takes color as parameter to always use current value (not closure!)\n const interpolateMask = (progress: number, color: string): string => {\n if (!maskByProgressRef.current) {\n return `linear-gradient(to bottom, ${color} 25%, transparent 95%)`;\n }\n const stopPoint = 50 + progress * 45; // 50% → 95%\n return `linear-gradient(to bottom, ${color} 0%, transparent ${stopPoint}%)`;\n };\n\n // EXACT MATCH TO FRAMER MOTION LOGIC:\n // fullWidth controls the MINIMUM beam width, not maximum!\n // fullWidth=1.0 → beam goes from 0% to 100% wide (full range)\n // fullWidth=0.5 → beam goes from 50% to 100% wide (narrower minimum)\n // fullWidth=0.2 → beam goes from 80% to 100% wide (very narrow minimum)\n const adjustedFullWidth = 1 - fullWidth;\n\n // Helper function to calculate progress (EXACTLY like Framer Motion)\n const calculateProgress = (rawProgress: number): number => {\n // ScrollTrigger rawProgress is 0-1 as element moves from start to end\n // We need to convert this to match Framer's rect.top / windowHeight logic\n // CRITICAL: GSAP progress (0→1) is INVERSE of Framer's normalizedPosition (1→0)\n\n // Apply fullWidth floor (minimum progress value)\n const normalizedPosition = Math.max(\n adjustedFullWidth, // Minimum (floor)\n Math.min(1, 1 - rawProgress) // Convert GSAP progress to Framer's normalized position\n );\n\n // Apply invert logic (EXACTLY like Framer Motion)\n // Use invertRef to get current value without closure issues\n return invertRef.current ? normalizedPosition : 1 - normalizedPosition;\n };\n\n // Determine scroll container\n const scroller = scrollElement\n ? (scrollElement as Element | Window)\n : undefined;\n\n // Create ScrollTrigger with FIXED range (like Framer Motion)\n const st = ScrollTrigger.create({\n trigger: element,\n start: \"top bottom\", // Element top hits viewport bottom\n end: \"top top\", // Element top hits viewport top\n scroller: scroller,\n scrub: true, // Instant scrubbing\n onUpdate: (self) => {\n // Calculate progress using Framer Motion logic\n const progress = calculateProgress(self.progress);\n\n // Update styles\n gsap.set(element, {\n background: interpolateBackground(progress, colorRef.current),\n opacity: opacityMin + opacityRange * progress,\n maskImage: interpolateMask(progress, colorRef.current),\n webkitMaskImage: interpolateMask(progress, colorRef.current),\n });\n },\n onRefresh: (self) => {\n // Set initial state when ScrollTrigger refreshes\n const progress = calculateProgress(self.progress);\n gsap.set(element, {\n background: interpolateBackground(progress, colorRef.current),\n opacity: opacityMin + opacityRange * progress,\n maskImage: interpolateMask(progress, colorRef.current),\n webkitMaskImage: interpolateMask(progress, colorRef.current),\n });\n },\n });\n\n // Set initial state immediately\n const initialProgress = calculateProgress(st.progress);\n gsap.set(element, {\n background: interpolateBackground(initialProgress, colorRef.current),\n opacity: opacityMin + opacityRange * initialProgress,\n maskImage: interpolateMask(initialProgress, colorRef.current),\n webkitMaskImage: interpolateMask(initialProgress, colorRef.current),\n });\n\n // Refresh ScrollTrigger after a brief delay to ensure layout is settled\n // This is especially important for Next.js SSR/hydration\n const refreshTimeout = setTimeout(() => {\n ScrollTrigger.refresh();\n }, 100);\n\n // Cleanup function to kill ScrollTrigger and clear timeout\n return () => {\n st.kill();\n clearTimeout(refreshTimeout);\n };\n },\n {\n // CRITICAL: Use refs for frequently changing values!\n // colorRef, invertRef, maskByProgressRef allow updates without recreating ScrollTrigger\n // This prevents visual glitches when these values change mid-scroll\n // Only include values that affect ScrollTrigger's position/range calculations\n dependencies: [\n fullWidth, // Affects trigger range\n scrollElement, // Affects which element to watch\n ],\n scope: elementRef,\n }\n );\n\n const combinedClassName = `react-light-beam ${className || \"\"}`.trim();\n\n // Prepare final styles (same logic as before, just without MotionValues)\n const finalStyles = disableDefaultStyles\n ? {\n // No default styles, only user styles\n willChange: \"background, opacity\",\n contain: \"layout style paint\",\n ...style, // User styles override\n }\n : {\n // Merge default styles with user styles\n ...defaultStyles,\n ...style, // User styles override everything\n };\n\n return (\n <div\n ref={elementRef}\n className={combinedClassName}\n style={finalStyles}\n {...(id ? {id} : {})}\n />\n );\n};\n\n\n\n// <div class=\"react-light-beam max-h:[500px] absolute top:[-100px] inset:[0]\" style=\"height: var(--react-light-beam-height, 500px); width: var(--react-light-beam-width, 100vw); will-change: background,\n// opacity; pointer-events: none; contain: layout style paint; transition: var(--react-light-beam-transition, all 0.25s ease); mask-image: linear-gradient(rgba(255, 255, 255, 0.8) 25%, transparent 95%);\n// opacity: 0.9434; background: conic-gradient(from 90deg at 31.723% 0%, rgba(255, 255, 255, 0.8), transparent 180deg) 0% 0% / 50% 117.624% no-repeat, conic-gradient(from 270deg at 68.277% 0%, transparent\n// 180deg, rgba(255, 255, 255, 0.8)) 100% 0% / 50% 100% no-repeat;\"></div>\n\n// <div class=\"react-light-beam max-h:[500px] absolute top:[-100px] inset:[0]\" style=\"height: var(--react-light-beam-height, 500px); width: var(--react-light-beam-width, 100vw); will-change: background,\n// opacity; pointer-events: none; contain: layout style paint; transition: var(--react-light-beam-transition, all 0.25s ease); mask-image: linear-gradient(rgba(0, 0, 0, 0.2) 25%, transparent 95%); opacity:\n// 0.941; background: conic-gradient(from 90deg at 33.0405% 0%, rgba(0, 0, 0, 0.2), transparent 180deg) 0% 0% / 50% 118.356% no-repeat, conic-gradient(from 270deg at 66.9595% 0%, transparent 180deg, rgba(0, 0,\n// 0, 0.2)) 100% 0% / 50% 100% no-repeat;\"></div>\n\n"]}
package/dist/index.mjs CHANGED
@@ -1,8 +1,43 @@
1
1
  "use client";
2
- import { useMotionValue, useTransform, motion } from 'framer-motion';
3
- import { useRef, useEffect, useState } from 'react';
2
+ import gsap2 from 'gsap';
3
+ import { ScrollTrigger } from 'gsap/ScrollTrigger';
4
+ import { useRef, useLayoutEffect, useEffect, useState } from 'react';
4
5
  import { jsx } from 'react/jsx-runtime';
5
6
 
7
+ var useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
8
+ var isConfig = (value) => value && !Array.isArray(value) && typeof value === "object";
9
+ var emptyArray = [];
10
+ var defaultConfig = {};
11
+ var _gsap = gsap2;
12
+ var useGSAP = (callback, dependencies = emptyArray) => {
13
+ let config = defaultConfig;
14
+ if (isConfig(callback)) {
15
+ config = callback;
16
+ callback = null;
17
+ dependencies = "dependencies" in config ? config.dependencies : emptyArray;
18
+ } else if (isConfig(dependencies)) {
19
+ config = dependencies;
20
+ dependencies = "dependencies" in config ? config.dependencies : emptyArray;
21
+ }
22
+ callback && typeof callback !== "function" && console.warn("First parameter must be a function or config object");
23
+ const { scope, revertOnUpdate } = config, mounted = useRef(false), context = useRef(_gsap.context(() => {
24
+ }, scope)), contextSafe = useRef((func) => context.current.add(null, func)), deferCleanup = dependencies && dependencies.length && !revertOnUpdate;
25
+ deferCleanup && useIsomorphicLayoutEffect(() => {
26
+ mounted.current = true;
27
+ return () => context.current.revert();
28
+ }, emptyArray);
29
+ useIsomorphicLayoutEffect(() => {
30
+ callback && context.current.add(callback, scope);
31
+ if (!deferCleanup || !mounted.current) {
32
+ return () => context.current.revert();
33
+ }
34
+ }, dependencies);
35
+ return { context: context.current, contextSafe: contextSafe.current };
36
+ };
37
+ useGSAP.register = (core) => {
38
+ _gsap = core;
39
+ };
40
+ useGSAP.headless = true;
6
41
  var useIsDarkmode = () => {
7
42
  const [isDarkmode, setIsDarkmodeActive] = useState(false);
8
43
  useEffect(() => {
@@ -18,14 +53,21 @@ var useIsDarkmode = () => {
18
53
  }, []);
19
54
  return { isDarkmode };
20
55
  };
56
+ gsap2.registerPlugin(ScrollTrigger, useGSAP);
21
57
  var defaultStyles = {
22
58
  height: "var(--react-light-beam-height, 500px)",
23
59
  width: "var(--react-light-beam-width, 100vw)",
24
- transition: "var(--react-light-beam-transition, all 0.25s ease)",
25
- willChange: "all",
60
+ // CRITICAL: NO transition on GSAP-controlled properties (background, opacity, mask)
61
+ // Transitions would fight with GSAP's instant updates, causing visual glitches
62
+ // especially when scroll direction changes
63
+ transition: "none",
64
+ willChange: "background, opacity",
65
+ // Specific properties for better performance
26
66
  userSelect: "none",
27
67
  pointerEvents: "none",
28
- WebkitTransition: "var(--react-light-beam-transition, all 0.25s ease)",
68
+ contain: "layout style paint",
69
+ // CSS containment for better performance
70
+ WebkitTransition: "none",
29
71
  WebkitUserSelect: "none",
30
72
  MozUserSelect: "none"
31
73
  };
@@ -44,97 +86,142 @@ var LightBeam = ({
44
86
  disableDefaultStyles = false
45
87
  }) => {
46
88
  const elementRef = useRef(null);
47
- const inViewProgress = useMotionValue(0);
48
- const opacity = useMotionValue(0.839322);
49
89
  const { isDarkmode } = useIsDarkmode();
50
90
  const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;
91
+ const colorRef = useRef(chosenColor);
92
+ const invertRef = useRef(invert);
93
+ const maskByProgressRef = useRef(maskLightByProgress);
94
+ useEffect(() => {
95
+ colorRef.current = chosenColor;
96
+ invertRef.current = invert;
97
+ maskByProgressRef.current = maskLightByProgress;
98
+ }, [chosenColor, colorLightmode, colorDarkmode, invert, maskLightByProgress]);
51
99
  useEffect(() => {
52
100
  onLoaded && onLoaded();
53
101
  }, []);
54
- useEffect(() => {
55
- if (typeof window !== "undefined") {
56
- const handleScroll = () => {
57
- if (elementRef.current) {
58
- const rect = elementRef.current.getBoundingClientRect();
59
- const windowHeight = window.innerHeight;
60
- const adjustedFullWidth = 1 - fullWidth;
61
- const progress = invert ? 0 + Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight)) : 1 - Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight));
62
- inViewProgress.set(progress);
63
- opacity.set(0.839322 + (1 - 0.839322) * progress);
102
+ useGSAP(
103
+ () => {
104
+ const element = elementRef.current;
105
+ if (!element || typeof window === "undefined") return;
106
+ const opacityMin = 0.839322;
107
+ const opacityRange = 0.160678;
108
+ const interpolateBackground = (progress, color) => {
109
+ const leftPos = 90 - progress * 90;
110
+ const rightPos = 10 + progress * 90;
111
+ const leftSize = 150 - progress * 50;
112
+ return `conic-gradient(from 90deg at ${leftPos}% 0%, ${color}, transparent 180deg) 0% 0% / 50% ${leftSize}% no-repeat, conic-gradient(from 270deg at ${rightPos}% 0%, transparent 180deg, ${color}) 100% 0% / 50% 100% no-repeat`;
113
+ };
114
+ const interpolateMask = (progress, color) => {
115
+ if (!maskByProgressRef.current) {
116
+ return `linear-gradient(to bottom, ${color} 25%, transparent 95%)`;
64
117
  }
118
+ const stopPoint = 50 + progress * 45;
119
+ return `linear-gradient(to bottom, ${color} 0%, transparent ${stopPoint}%)`;
120
+ };
121
+ const adjustedFullWidth = 1 - fullWidth;
122
+ const calculateProgress = (rawProgress) => {
123
+ const normalizedPosition = Math.max(
124
+ adjustedFullWidth,
125
+ // Minimum (floor)
126
+ Math.min(1, 1 - rawProgress)
127
+ // Convert GSAP progress to Framer's normalized position
128
+ );
129
+ return invertRef.current ? normalizedPosition : 1 - normalizedPosition;
65
130
  };
66
- const handleScrollThrottled = throttle(handleScroll);
67
- const target = scrollElement || document.body || document.documentElement;
68
- target.addEventListener("scroll", handleScrollThrottled);
69
- window.addEventListener("resize", handleScrollThrottled);
70
- handleScroll();
131
+ const scroller = scrollElement ? scrollElement : void 0;
132
+ const st = ScrollTrigger.create({
133
+ trigger: element,
134
+ start: "top bottom",
135
+ // Element top hits viewport bottom
136
+ end: "top top",
137
+ // Element top hits viewport top
138
+ scroller,
139
+ scrub: true,
140
+ // Instant scrubbing
141
+ onUpdate: (self) => {
142
+ const progress = calculateProgress(self.progress);
143
+ gsap2.set(element, {
144
+ background: interpolateBackground(progress, colorRef.current),
145
+ opacity: opacityMin + opacityRange * progress,
146
+ maskImage: interpolateMask(progress, colorRef.current),
147
+ webkitMaskImage: interpolateMask(progress, colorRef.current)
148
+ });
149
+ },
150
+ onRefresh: (self) => {
151
+ const progress = calculateProgress(self.progress);
152
+ gsap2.set(element, {
153
+ background: interpolateBackground(progress, colorRef.current),
154
+ opacity: opacityMin + opacityRange * progress,
155
+ maskImage: interpolateMask(progress, colorRef.current),
156
+ webkitMaskImage: interpolateMask(progress, colorRef.current)
157
+ });
158
+ }
159
+ });
160
+ const initialProgress = calculateProgress(st.progress);
161
+ gsap2.set(element, {
162
+ background: interpolateBackground(initialProgress, colorRef.current),
163
+ opacity: opacityMin + opacityRange * initialProgress,
164
+ maskImage: interpolateMask(initialProgress, colorRef.current),
165
+ webkitMaskImage: interpolateMask(initialProgress, colorRef.current)
166
+ });
167
+ const refreshTimeout = setTimeout(() => {
168
+ ScrollTrigger.refresh();
169
+ }, 100);
71
170
  return () => {
72
- target.removeEventListener("scroll", handleScrollThrottled);
73
- window.removeEventListener("resize", handleScrollThrottled);
171
+ st.kill();
172
+ clearTimeout(refreshTimeout);
74
173
  };
174
+ },
175
+ {
176
+ // CRITICAL: Use refs for frequently changing values!
177
+ // colorRef, invertRef, maskByProgressRef allow updates without recreating ScrollTrigger
178
+ // This prevents visual glitches when these values change mid-scroll
179
+ // Only include values that affect ScrollTrigger's position/range calculations
180
+ dependencies: [
181
+ fullWidth,
182
+ // Affects trigger range
183
+ scrollElement
184
+ // Affects which element to watch
185
+ ],
186
+ scope: elementRef
75
187
  }
76
- }, [inViewProgress, opacity, scrollElement, fullWidth, invert]);
77
- const backgroundPosition = useTransform(
78
- inViewProgress,
79
- [0, 1],
80
- [
81
- `conic-gradient(from 90deg at 90% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 150% no-repeat, conic-gradient(from 270deg at 10% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`,
82
- `conic-gradient(from 90deg at 0% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 100% no-repeat, conic-gradient(from 270deg at 100% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`
83
- ]
84
- );
85
- const maskImageOpacity = useTransform(
86
- inViewProgress,
87
- [0, 1],
88
- [
89
- `linear-gradient(to bottom, ${chosenColor} 0%, transparent 50%)`,
90
- `linear-gradient(to bottom, ${chosenColor} 0%, transparent 95%)`
91
- ]
92
188
  );
93
- const maskImage = maskLightByProgress ? maskImageOpacity : `linear-gradient(to bottom, ${chosenColor} 25%, transparent 95%)`;
94
189
  const combinedClassName = `react-light-beam ${className || ""}`.trim();
95
190
  const finalStyles = disableDefaultStyles ? {
96
- // No default styles, only motion values and user styles
97
- background: backgroundPosition,
98
- opacity,
99
- maskImage,
100
- WebkitMaskImage: maskImage,
191
+ // No default styles, only user styles
101
192
  willChange: "background, opacity",
193
+ contain: "layout style paint",
102
194
  ...style
103
195
  // User styles override
104
196
  } : {
105
- // Merge default styles with motion values
197
+ // Merge default styles with user styles
106
198
  ...defaultStyles,
107
- background: backgroundPosition,
108
- // MotionValue (overrides default)
109
- opacity,
110
- // MotionValue (overrides default)
111
- maskImage,
112
- // MotionValue or string
113
- WebkitMaskImage: maskImage,
114
- willChange: "background, opacity",
115
199
  ...style
116
200
  // User styles override everything
117
201
  };
118
- const motionProps = {
119
- style: finalStyles,
120
- ref: elementRef,
121
- className: combinedClassName,
122
- ...id ? { id } : {}
123
- };
124
- return /* @__PURE__ */ jsx(motion.div, { ...motionProps });
125
- };
126
- var throttle = (func) => {
127
- let ticking = false;
128
- return function(...args) {
129
- if (!ticking) {
130
- requestAnimationFrame(() => {
131
- func.apply(this, args);
132
- ticking = false;
133
- });
134
- ticking = true;
202
+ return /* @__PURE__ */ jsx(
203
+ "div",
204
+ {
205
+ ref: elementRef,
206
+ className: combinedClassName,
207
+ style: finalStyles,
208
+ ...id ? { id } : {}
135
209
  }
136
- };
210
+ );
137
211
  };
212
+ /*! Bundled license information:
213
+
214
+ @gsap/react/src/index.js:
215
+ (*!
216
+ * @gsap/react 2.1.2
217
+ * https://gsap.com
218
+ *
219
+ * Copyright 2008-2025, GreenSock. All rights reserved.
220
+ * Subject to the terms at https://gsap.com/standard-license or for
221
+ * Club GSAP members, the agreement issued with that membership.
222
+ * @author: Jack Doyle, jack@greensock.com
223
+ *)
224
+ */
138
225
 
139
226
  export { LightBeam };
140
227
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useDarkmode.tsx","../src/index.tsx"],"names":["useEffect"],"mappings":"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","file":"index.mjs","sourcesContent":["\"use client\";\nimport { useEffect, useState } from \"react\";\n\nexport const useIsDarkmode = () => {\n const [isDarkmode, setIsDarkmodeActive] = useState(false);\n\n useEffect(() => {\n const matchMedia = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\n const handleChange = () => {\n setIsDarkmodeActive(matchMedia.matches);\n };\n\n // Set the initial value\n setIsDarkmodeActive(matchMedia.matches);\n\n // Listen for changes\n matchMedia.addEventListener(\"change\", handleChange);\n\n // Cleanup listener on unmount\n return () => {\n matchMedia.removeEventListener(\"change\", handleChange);\n };\n }, []);\n\n return { isDarkmode };\n};\n","\"use client\";\nimport {motion, useMotionValue, useTransform} from \"framer-motion\";\nimport React, {useEffect, useRef} from \"react\";\nimport {LightBeamProps} from \"../types/types\";\nimport {useIsDarkmode} from \"./hooks/useDarkmode\";\n\n// Default inline styles using CSS variables for easy customization\n// Users can override via className by setting CSS variables\nconst defaultStyles: React.CSSProperties = {\n height: \"var(--react-light-beam-height, 500px)\",\n width: \"var(--react-light-beam-width, 100vw)\",\n transition: \"var(--react-light-beam-transition, all 0.25s ease)\",\n willChange: \"all\",\n userSelect: \"none\",\n pointerEvents: \"none\",\n WebkitTransition: \"var(--react-light-beam-transition, all 0.25s ease)\",\n WebkitUserSelect: \"none\",\n MozUserSelect: \"none\",\n};\n\nexport const LightBeam = ({\n className,\n style,\n colorLightmode = \"rgba(0,0,0, 0.5)\",\n colorDarkmode = \"rgba(255, 255, 255, 0.5)\",\n maskLightByProgress = false,\n fullWidth = 1.0, // Default to full width\n invert = false,\n id = undefined,\n onLoaded = undefined,\n scrollElement,\n disableDefaultStyles = false,\n}: LightBeamProps) => {\n const elementRef = useRef<HTMLDivElement>(null);\n const inViewProgress = useMotionValue(0);\n const opacity = useMotionValue(0.839322);\n const { isDarkmode } = useIsDarkmode();\n const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;\n\n useEffect(() => {\n onLoaded && onLoaded();\n }, []);\n\n useEffect(() => {\n if (typeof window !== \"undefined\") {\n const handleScroll = () => {\n if (elementRef.current) {\n const rect = elementRef.current.getBoundingClientRect();\n const windowHeight = window.innerHeight;\n\n // Invert the fullWidth value: 1 becomes 0, and 0 becomes 1\n const adjustedFullWidth = 1 - fullWidth;\n\n // Calculate progress\n const progress = invert\n ? 0 +\n Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight))\n : 1 -\n Math.max(adjustedFullWidth, Math.min(1, rect.top / windowHeight));\n\n // Update motion values\n inViewProgress.set(progress);\n opacity.set(0.839322 + (1 - 0.839322) * progress);\n }\n };\n\n const handleScrollThrottled = throttle(handleScroll); // Approx 60fps\n\n // Default to document.body (works in modern React/Next.js setups)\n // window doesn't fire scroll events in many modern setups!\n const target = scrollElement || document.body || document.documentElement;\n\n target.addEventListener(\"scroll\", handleScrollThrottled);\n window.addEventListener(\"resize\", handleScrollThrottled);\n\n // Initial call to handleScroll to set initial state\n handleScroll();\n\n return () => {\n target.removeEventListener(\"scroll\", handleScrollThrottled);\n window.removeEventListener(\"resize\", handleScrollThrottled);\n };\n }\n }, [inViewProgress, opacity, scrollElement, fullWidth, invert]);\n\n const backgroundPosition = useTransform(\n inViewProgress,\n [0, 1],\n [\n `conic-gradient(from 90deg at 90% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 150% no-repeat, conic-gradient(from 270deg at 10% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`,\n `conic-gradient(from 90deg at 0% 0%, ${chosenColor}, transparent 180deg) 0% 0% / 50% 100% no-repeat, conic-gradient(from 270deg at 100% 0%, transparent 180deg, ${chosenColor}) 100% 0% / 50% 100% no-repeat`,\n ]\n );\n const maskImageOpacity = useTransform(\n inViewProgress,\n [0, 1],\n [\n `linear-gradient(to bottom, ${chosenColor} 0%, transparent 50%)`,\n `linear-gradient(to bottom, ${chosenColor} 0%, transparent 95%)`,\n ]\n );\n\n const maskImage = maskLightByProgress\n ? maskImageOpacity\n : `linear-gradient(to bottom, ${chosenColor} 25%, transparent 95%)`;\n\n const combinedClassName = `react-light-beam ${className || \"\"}`.trim();\n\n // CRITICAL: MotionValues must be passed directly to motion.div style prop\n // Don't spread them into plain objects or reactivity breaks!\n const finalStyles = disableDefaultStyles\n ? {\n // No default styles, only motion values and user styles\n background: backgroundPosition,\n opacity: opacity,\n maskImage: maskImage,\n WebkitMaskImage: maskImage,\n willChange: \"background, opacity\",\n ...style, // User styles override\n }\n : {\n // Merge default styles with motion values\n ...defaultStyles,\n background: backgroundPosition, // MotionValue (overrides default)\n opacity: opacity, // MotionValue (overrides default)\n maskImage: maskImage, // MotionValue or string\n WebkitMaskImage: maskImage,\n willChange: \"background, opacity\",\n ...style, // User styles override everything\n };\n\n const motionProps: any = {\n style: finalStyles,\n ref: elementRef,\n className: combinedClassName,\n ...(id ? { id } : {}),\n };\n\n return <motion.div {...motionProps} />;\n};\n\nconst throttle = (func: Function) => {\n let ticking = false;\n return function (this: any, ...args: any[]) {\n if (!ticking) {\n requestAnimationFrame(() => {\n func.apply(this, args);\n ticking = false;\n });\n ticking = true;\n }\n };\n};\n"]}
1
+ {"version":3,"sources":["../node_modules/@gsap/react/src/index.js","../src/hooks/useDarkmode.tsx","../src/index.tsx"],"names":["gsap","useEffect","useRef"],"mappings":"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","file":"index.mjs","sourcesContent":["/*!\n * @gsap/react 2.1.2\n * https://gsap.com\n *\n * Copyright 2008-2025, GreenSock. All rights reserved.\n * Subject to the terms at https://gsap.com/standard-license or for\n * Club GSAP members, the agreement issued with that membership.\n * @author: Jack Doyle, jack@greensock.com\n*/\n/* eslint-disable */\nimport { useEffect, useLayoutEffect, useRef } from \"react\";\nimport gsap from \"gsap\";\n\nlet useIsomorphicLayoutEffect = typeof document !== \"undefined\" ? useLayoutEffect : useEffect,\n isConfig = value => value && !Array.isArray(value) && typeof(value) === \"object\",\n emptyArray = [],\n defaultConfig = {},\n _gsap = gsap; // accommodates situations where different versions of GSAP may be loaded, so a user can gsap.registerPlugin(useGSAP);\n\nexport const useGSAP = (callback, dependencies = emptyArray) => {\n let config = defaultConfig;\n if (isConfig(callback)) {\n config = callback;\n callback = null;\n dependencies = \"dependencies\" in config ? config.dependencies : emptyArray;\n } else if (isConfig(dependencies)) {\n config = dependencies;\n dependencies = \"dependencies\" in config ? config.dependencies : emptyArray;\n }\n (callback && typeof callback !== \"function\") && console.warn(\"First parameter must be a function or config object\");\n const { scope, revertOnUpdate } = config,\n mounted = useRef(false),\n context = useRef(_gsap.context(() => { }, scope)),\n contextSafe = useRef((func) => context.current.add(null, func)),\n deferCleanup = dependencies && dependencies.length && !revertOnUpdate;\n deferCleanup && useIsomorphicLayoutEffect(() => {\n mounted.current = true;\n return () => context.current.revert();\n }, emptyArray);\n useIsomorphicLayoutEffect(() => {\n callback && context.current.add(callback, scope);\n if (!deferCleanup || !mounted.current) { // React renders bottom-up, thus there could be hooks with dependencies that run BEFORE the component mounts, thus cleanup wouldn't occur since a hook with an empty dependency Array would only run once the component mounts.\n return () => context.current.revert();\n }\n }, dependencies);\n return { context: context.current, contextSafe: contextSafe.current };\n};\nuseGSAP.register = core => { _gsap = core; };\nuseGSAP.headless = true; // doesn't require the window to be registered.\n","\"use client\";\nimport { useEffect, useState } from \"react\";\n\nexport const useIsDarkmode = () => {\n const [isDarkmode, setIsDarkmodeActive] = useState(false);\n\n useEffect(() => {\n const matchMedia = window.matchMedia(\"(prefers-color-scheme: dark)\");\n\n const handleChange = () => {\n setIsDarkmodeActive(matchMedia.matches);\n };\n\n // Set the initial value\n setIsDarkmodeActive(matchMedia.matches);\n\n // Listen for changes\n matchMedia.addEventListener(\"change\", handleChange);\n\n // Cleanup listener on unmount\n return () => {\n matchMedia.removeEventListener(\"change\", handleChange);\n };\n }, []);\n\n return { isDarkmode };\n};\n","\"use client\";\nimport gsap from \"gsap\";\nimport {ScrollTrigger} from \"gsap/ScrollTrigger\";\nimport {useGSAP} from \"@gsap/react\";\nimport React, {useEffect, useRef} from \"react\";\nimport {LightBeamProps} from \"../types/types\";\nimport {useIsDarkmode} from \"./hooks/useDarkmode\";\n\n// Register GSAP plugins\ngsap.registerPlugin(ScrollTrigger, useGSAP);\n\n// Default inline styles using CSS variables for easy customization\n// Users can override via className by setting CSS variables\nconst defaultStyles: React.CSSProperties = {\n height: \"var(--react-light-beam-height, 500px)\",\n width: \"var(--react-light-beam-width, 100vw)\",\n // CRITICAL: NO transition on GSAP-controlled properties (background, opacity, mask)\n // Transitions would fight with GSAP's instant updates, causing visual glitches\n // especially when scroll direction changes\n transition: \"none\",\n willChange: \"background, opacity\", // Specific properties for better performance\n userSelect: \"none\",\n pointerEvents: \"none\",\n contain: \"layout style paint\", // CSS containment for better performance\n WebkitTransition: \"none\",\n WebkitUserSelect: \"none\",\n MozUserSelect: \"none\",\n};\n\nexport const LightBeam = ({\n className,\n style,\n colorLightmode = \"rgba(0,0,0, 0.5)\",\n colorDarkmode = \"rgba(255, 255, 255, 0.5)\",\n maskLightByProgress = false,\n fullWidth = 1.0, // Default to full width\n invert = false,\n id = undefined,\n onLoaded = undefined,\n scrollElement,\n disableDefaultStyles = false,\n }: LightBeamProps) => {\n const elementRef = useRef<HTMLDivElement>(null);\n const {isDarkmode} = useIsDarkmode();\n const chosenColor = isDarkmode ? colorDarkmode : colorLightmode;\n\n // Use refs to track current values without triggering useGSAP re-runs\n const colorRef = useRef(chosenColor);\n const invertRef = useRef(invert);\n const maskByProgressRef = useRef(maskLightByProgress);\n\n // Update refs whenever values change\n useEffect(() => {\n colorRef.current = chosenColor;\n invertRef.current = invert;\n maskByProgressRef.current = maskLightByProgress;\n }, [chosenColor, colorLightmode, colorDarkmode, invert, maskLightByProgress]);\n\n // Call onLoaded callback when component mounts\n useEffect(() => {\n onLoaded && onLoaded();\n }, []);\n\n // GSAP ScrollTrigger implementation\n useGSAP(\n () => {\n const element = elementRef.current;\n if (!element || typeof window === \"undefined\") return;\n\n // Pre-calculate constants for performance\n const opacityMin = 0.839322;\n const opacityRange = 0.160678; // 1 - 0.839322\n\n // Helper function to interpolate background gradient\n // NOTE: Takes color as parameter to always use current value (not closure!)\n const interpolateBackground = (progress: number, color: string): string => {\n // At progress 0: gradients are wide (90% and 10% positions)\n // At progress 1: gradients converge (0% and 100% positions)\n const leftPos = 90 - progress * 90; // 90% → 0%\n const rightPos = 10 + progress * 90; // 10% → 100%\n const leftSize = 150 - progress * 50; // 150% → 100%\n\n return `conic-gradient(from 90deg at ${leftPos}% 0%, ${color}, transparent 180deg) 0% 0% / 50% ${leftSize}% no-repeat, conic-gradient(from 270deg at ${rightPos}% 0%, transparent 180deg, ${color}) 100% 0% / 50% 100% no-repeat`;\n };\n\n // Helper function to interpolate mask\n // NOTE: Takes color as parameter to always use current value (not closure!)\n const interpolateMask = (progress: number, color: string): string => {\n if (!maskByProgressRef.current) {\n return `linear-gradient(to bottom, ${color} 25%, transparent 95%)`;\n }\n const stopPoint = 50 + progress * 45; // 50% → 95%\n return `linear-gradient(to bottom, ${color} 0%, transparent ${stopPoint}%)`;\n };\n\n // EXACT MATCH TO FRAMER MOTION LOGIC:\n // fullWidth controls the MINIMUM beam width, not maximum!\n // fullWidth=1.0 → beam goes from 0% to 100% wide (full range)\n // fullWidth=0.5 → beam goes from 50% to 100% wide (narrower minimum)\n // fullWidth=0.2 → beam goes from 80% to 100% wide (very narrow minimum)\n const adjustedFullWidth = 1 - fullWidth;\n\n // Helper function to calculate progress (EXACTLY like Framer Motion)\n const calculateProgress = (rawProgress: number): number => {\n // ScrollTrigger rawProgress is 0-1 as element moves from start to end\n // We need to convert this to match Framer's rect.top / windowHeight logic\n // CRITICAL: GSAP progress (0→1) is INVERSE of Framer's normalizedPosition (1→0)\n\n // Apply fullWidth floor (minimum progress value)\n const normalizedPosition = Math.max(\n adjustedFullWidth, // Minimum (floor)\n Math.min(1, 1 - rawProgress) // Convert GSAP progress to Framer's normalized position\n );\n\n // Apply invert logic (EXACTLY like Framer Motion)\n // Use invertRef to get current value without closure issues\n return invertRef.current ? normalizedPosition : 1 - normalizedPosition;\n };\n\n // Determine scroll container\n const scroller = scrollElement\n ? (scrollElement as Element | Window)\n : undefined;\n\n // Create ScrollTrigger with FIXED range (like Framer Motion)\n const st = ScrollTrigger.create({\n trigger: element,\n start: \"top bottom\", // Element top hits viewport bottom\n end: \"top top\", // Element top hits viewport top\n scroller: scroller,\n scrub: true, // Instant scrubbing\n onUpdate: (self) => {\n // Calculate progress using Framer Motion logic\n const progress = calculateProgress(self.progress);\n\n // Update styles\n gsap.set(element, {\n background: interpolateBackground(progress, colorRef.current),\n opacity: opacityMin + opacityRange * progress,\n maskImage: interpolateMask(progress, colorRef.current),\n webkitMaskImage: interpolateMask(progress, colorRef.current),\n });\n },\n onRefresh: (self) => {\n // Set initial state when ScrollTrigger refreshes\n const progress = calculateProgress(self.progress);\n gsap.set(element, {\n background: interpolateBackground(progress, colorRef.current),\n opacity: opacityMin + opacityRange * progress,\n maskImage: interpolateMask(progress, colorRef.current),\n webkitMaskImage: interpolateMask(progress, colorRef.current),\n });\n },\n });\n\n // Set initial state immediately\n const initialProgress = calculateProgress(st.progress);\n gsap.set(element, {\n background: interpolateBackground(initialProgress, colorRef.current),\n opacity: opacityMin + opacityRange * initialProgress,\n maskImage: interpolateMask(initialProgress, colorRef.current),\n webkitMaskImage: interpolateMask(initialProgress, colorRef.current),\n });\n\n // Refresh ScrollTrigger after a brief delay to ensure layout is settled\n // This is especially important for Next.js SSR/hydration\n const refreshTimeout = setTimeout(() => {\n ScrollTrigger.refresh();\n }, 100);\n\n // Cleanup function to kill ScrollTrigger and clear timeout\n return () => {\n st.kill();\n clearTimeout(refreshTimeout);\n };\n },\n {\n // CRITICAL: Use refs for frequently changing values!\n // colorRef, invertRef, maskByProgressRef allow updates without recreating ScrollTrigger\n // This prevents visual glitches when these values change mid-scroll\n // Only include values that affect ScrollTrigger's position/range calculations\n dependencies: [\n fullWidth, // Affects trigger range\n scrollElement, // Affects which element to watch\n ],\n scope: elementRef,\n }\n );\n\n const combinedClassName = `react-light-beam ${className || \"\"}`.trim();\n\n // Prepare final styles (same logic as before, just without MotionValues)\n const finalStyles = disableDefaultStyles\n ? {\n // No default styles, only user styles\n willChange: \"background, opacity\",\n contain: \"layout style paint\",\n ...style, // User styles override\n }\n : {\n // Merge default styles with user styles\n ...defaultStyles,\n ...style, // User styles override everything\n };\n\n return (\n <div\n ref={elementRef}\n className={combinedClassName}\n style={finalStyles}\n {...(id ? {id} : {})}\n />\n );\n};\n\n\n\n// <div class=\"react-light-beam max-h:[500px] absolute top:[-100px] inset:[0]\" style=\"height: var(--react-light-beam-height, 500px); width: var(--react-light-beam-width, 100vw); will-change: background,\n// opacity; pointer-events: none; contain: layout style paint; transition: var(--react-light-beam-transition, all 0.25s ease); mask-image: linear-gradient(rgba(255, 255, 255, 0.8) 25%, transparent 95%);\n// opacity: 0.9434; background: conic-gradient(from 90deg at 31.723% 0%, rgba(255, 255, 255, 0.8), transparent 180deg) 0% 0% / 50% 117.624% no-repeat, conic-gradient(from 270deg at 68.277% 0%, transparent\n// 180deg, rgba(255, 255, 255, 0.8)) 100% 0% / 50% 100% no-repeat;\"></div>\n\n// <div class=\"react-light-beam max-h:[500px] absolute top:[-100px] inset:[0]\" style=\"height: var(--react-light-beam-height, 500px); width: var(--react-light-beam-width, 100vw); will-change: background,\n// opacity; pointer-events: none; contain: layout style paint; transition: var(--react-light-beam-transition, all 0.25s ease); mask-image: linear-gradient(rgba(0, 0, 0, 0.2) 25%, transparent 95%); opacity:\n// 0.941; background: conic-gradient(from 90deg at 33.0405% 0%, rgba(0, 0, 0, 0.2), transparent 180deg) 0% 0% / 50% 118.356% no-repeat, conic-gradient(from 270deg at 66.9595% 0%, transparent 180deg, rgba(0, 0,\n// 0, 0.2)) 100% 0% / 50% 100% no-repeat;\"></div>\n\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stianlarsen/react-light-beam",
3
- "version": "1.2.0",
4
- "description": "A customizable React component that creates a light beam effect using conic gradients. Supports dark mode and various customization options.",
3
+ "version": "2.1.0",
4
+ "description": "A customizable React component that creates a light beam effect using conic gradients. Powered by GSAP for maximum performance. Supports dark mode and various customization options.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
@@ -38,7 +38,9 @@
38
38
  "light beam",
39
39
  "conic gradient",
40
40
  "dark mode",
41
- "framer-motion",
41
+ "gsap",
42
+ "scrolltrigger",
43
+ "scroll animation",
42
44
  "animation"
43
45
  ],
44
46
  "author": "Stian Larsen <stian.larsen@mac.com>",
@@ -48,14 +50,15 @@
48
50
  },
49
51
  "homepage": "https://github.com/stianalars1/react-light-beam#readme",
50
52
  "peerDependencies": {
51
- "framer-motion": "^11.11.1",
53
+ "gsap": "^3.12.0",
52
54
  "react": "^18.0.0 || ^19.0.0",
53
55
  "react-dom": "^18.0.0 || ^19.0.0"
54
56
  },
55
57
  "devDependencies": {
58
+ "@gsap/react": "^2.1.2",
56
59
  "@types/react": "^19.2.7",
57
60
  "@types/react-dom": "^19.2.3",
58
- "framer-motion": "^11.11.1",
61
+ "gsap": "^3.12.5",
59
62
  "react": "^19.2.3",
60
63
  "react-dom": "^19.2.3",
61
64
  "tsup": "^8.5.1",