@thinkpixellab-public/px-vue 4.1.26 → 4.1.27
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/composables/loadGsap.js +60 -0
- package/composables/loadLenis.js +82 -0
- package/composables/useGsap.js +81 -0
- package/composables/useLenis.js +86 -0
- package/composables/useScrollAnimations.js +142 -0
- package/package.json +3 -2
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// composables/loadGsap.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lazy loading utilities for GSAP and ScrollTrigger
|
|
5
|
+
*
|
|
6
|
+
* These functions provide race-condition-safe loading of GSAP libraries.
|
|
7
|
+
* They ensure single initialization even with concurrent calls.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
let gsapPromise = null;
|
|
11
|
+
let scrollTriggerPromise = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Loads the GSAP library lazily
|
|
15
|
+
*
|
|
16
|
+
* @returns {Promise} Promise that resolves to the GSAP instance
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Direct usage (not recommended - use useGsap composable instead)
|
|
20
|
+
* import { loadGsap } from '@thinkpixellab-public/px-vue/composables/loadGsap';
|
|
21
|
+
*
|
|
22
|
+
* const gsap = await loadGsap();
|
|
23
|
+
* gsap.to('.element', { x: 100 });
|
|
24
|
+
*/
|
|
25
|
+
export const loadGsap = async () => {
|
|
26
|
+
if (!gsapPromise) {
|
|
27
|
+
gsapPromise = import('gsap').then(module => module.gsap);
|
|
28
|
+
}
|
|
29
|
+
return gsapPromise;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Loads GSAP with ScrollTrigger plugin registered
|
|
34
|
+
*
|
|
35
|
+
* @returns {Promise} Promise that resolves to { gsap, ScrollTrigger }
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // Direct usage (not recommended - use useScrollAnimation composable instead)
|
|
39
|
+
* import { loadScrollTrigger } from '@thinkpixellab-public/px-vue/composables/loadGsap';
|
|
40
|
+
*
|
|
41
|
+
* const { gsap, ScrollTrigger } = await loadScrollTrigger();
|
|
42
|
+
*
|
|
43
|
+
* gsap.to('.element', {
|
|
44
|
+
* x: 100,
|
|
45
|
+
* scrollTrigger: {
|
|
46
|
+
* trigger: '.element',
|
|
47
|
+
* start: 'top center'
|
|
48
|
+
* }
|
|
49
|
+
* });
|
|
50
|
+
*/
|
|
51
|
+
export const loadScrollTrigger = async () => {
|
|
52
|
+
if (!scrollTriggerPromise) {
|
|
53
|
+
scrollTriggerPromise = (async () => {
|
|
54
|
+
const [gsap, { ScrollTrigger }] = await Promise.all([loadGsap(), import('gsap/ScrollTrigger')]);
|
|
55
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
56
|
+
return { gsap, ScrollTrigger };
|
|
57
|
+
})();
|
|
58
|
+
}
|
|
59
|
+
return scrollTriggerPromise;
|
|
60
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// composables/loadLenis.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lazy loading utilities for Lenis smooth scrolling
|
|
5
|
+
*
|
|
6
|
+
* These functions provide race-condition-safe loading and initialization of Lenis.
|
|
7
|
+
* The instance is shared across all components for consistent scroll behavior.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
let lenisPromise = null;
|
|
11
|
+
let lenisInstancePromise = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Loads the Lenis class lazily
|
|
15
|
+
*
|
|
16
|
+
* @returns {Promise} Promise that resolves to the Lenis constructor
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Direct usage (not recommended - use useLenis composable instead)
|
|
20
|
+
* import { loadLenis } from '@thinkpixellab-public/px-vue/composables/loadLenis';
|
|
21
|
+
*
|
|
22
|
+
* const Lenis = await loadLenis();
|
|
23
|
+
* const customLenis = new Lenis({
|
|
24
|
+
* duration: 2,
|
|
25
|
+
* smoothWheel: true
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
export const loadLenis = async () => {
|
|
29
|
+
if (!lenisPromise) {
|
|
30
|
+
lenisPromise = import('lenis').then(module => module.default);
|
|
31
|
+
}
|
|
32
|
+
return lenisPromise;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Loads a shared Lenis instance with default configuration
|
|
37
|
+
*
|
|
38
|
+
* Returns the same instance across all calls for consistent scroll behavior.
|
|
39
|
+
* Starts with autoRaf enabled - useScrollAnimation will take over RAF control when needed.
|
|
40
|
+
*
|
|
41
|
+
* @returns {Promise} Promise that resolves to the shared Lenis instance
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // Direct usage (not recommended - use useLenis composable instead)
|
|
45
|
+
* import { loadLenisInstance } from '@thinkpixellab-public/px-vue/composables/loadLenis';
|
|
46
|
+
*
|
|
47
|
+
* const lenis = await loadLenisInstance();
|
|
48
|
+
*
|
|
49
|
+
* // smooth scroll to element
|
|
50
|
+
* lenis.scrollTo('#section-2', { duration: 1.5 });
|
|
51
|
+
*
|
|
52
|
+
* // listen to scroll events
|
|
53
|
+
* lenis.on('scroll', (e) => {
|
|
54
|
+
* console.log('Scroll position:', e.scroll);
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Configuration details
|
|
59
|
+
* // The shared instance is created with:
|
|
60
|
+
* // {
|
|
61
|
+
* // smoothWheel: false, // disabled for better performance
|
|
62
|
+
* // smoothTouch: false, // disabled for mobile compatibility
|
|
63
|
+
* // autoRaf: true // auto RAF until ScrollTrigger takes over
|
|
64
|
+
* // }
|
|
65
|
+
*/
|
|
66
|
+
export const loadLenisInstance = async () => {
|
|
67
|
+
if (!lenisInstancePromise) {
|
|
68
|
+
lenisInstancePromise = (async () => {
|
|
69
|
+
const Lenis = await loadLenis();
|
|
70
|
+
|
|
71
|
+
const lenisInstance = new Lenis({
|
|
72
|
+
smoothWheel: false,
|
|
73
|
+
smoothTouch: false,
|
|
74
|
+
autoRaf: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return lenisInstance;
|
|
78
|
+
})();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return lenisInstancePromise;
|
|
82
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// composables/useGsap.js
|
|
2
|
+
import { loadGsap } from './loadGsap';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Ensures GSAP is loaded and available globally.
|
|
6
|
+
* After calling this composable, you can use gsap directly as a global.
|
|
7
|
+
*
|
|
8
|
+
* @returns {Object} Object with ready promise that resolves when GSAP is loaded
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // Basic usage in a Vue component
|
|
12
|
+
* import { useGsap } from '@thinkpixellab-public/px-vue/composables/useGsap';
|
|
13
|
+
*
|
|
14
|
+
* export default {
|
|
15
|
+
* setup() {
|
|
16
|
+
* const { ready } = useGsap();
|
|
17
|
+
*
|
|
18
|
+
* onMounted(async () => {
|
|
19
|
+
* await ready;
|
|
20
|
+
*
|
|
21
|
+
* // gsap is now available globally
|
|
22
|
+
* gsap.to('.my-element', {
|
|
23
|
+
* duration: 1,
|
|
24
|
+
* x: 100,
|
|
25
|
+
* opacity: 0.5
|
|
26
|
+
* });
|
|
27
|
+
* });
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // With refs and cleanup
|
|
33
|
+
* import { ref, onMounted, onBeforeUnmount } from 'vue';
|
|
34
|
+
* import { useGsap } from '@thinkpixellab-public/px-vue/composables/useGsap';
|
|
35
|
+
*
|
|
36
|
+
* export default {
|
|
37
|
+
* setup() {
|
|
38
|
+
* useGsap(); // ensures gsap loads
|
|
39
|
+
* const myElement = ref(null);
|
|
40
|
+
* let animation = null;
|
|
41
|
+
*
|
|
42
|
+
* onMounted(() => {
|
|
43
|
+
* // gsap is available globally
|
|
44
|
+
* animation = gsap.to(myElement.value, {
|
|
45
|
+
* duration: 2,
|
|
46
|
+
* rotation: 360,
|
|
47
|
+
* repeat: -1
|
|
48
|
+
* });
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* onBeforeUnmount(() => {
|
|
52
|
+
* if (animation) {
|
|
53
|
+
* animation.kill();
|
|
54
|
+
* }
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* return { myElement };
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Immediate usage if you know GSAP is already loaded
|
|
63
|
+
* import { useGsap } from '@thinkpixellab-public/px-vue/composables/useGsap';
|
|
64
|
+
*
|
|
65
|
+
* export default {
|
|
66
|
+
* setup() {
|
|
67
|
+
* useGsap(); // triggers load if needed
|
|
68
|
+
*
|
|
69
|
+
* // can use gsap immediately in most cases
|
|
70
|
+
* const tl = gsap.timeline();
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
*/
|
|
74
|
+
export const useGsap = () => {
|
|
75
|
+
if (process.server) {
|
|
76
|
+
return { ready: Promise.resolve() };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const ready = loadGsap();
|
|
80
|
+
return { ready };
|
|
81
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// composables/useLenis.js
|
|
2
|
+
import { loadLenisInstance } from './loadLenis';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vue composable for using Lenis smooth scrolling
|
|
6
|
+
*
|
|
7
|
+
* Provides access to a shared Lenis instance with autoRaf enabled.
|
|
8
|
+
* Perfect for components that need smooth scrolling without ScrollTrigger integration.
|
|
9
|
+
*
|
|
10
|
+
* @returns {Object} Object with getLenis loader function
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // Basic usage for smooth scrolling
|
|
14
|
+
* import { useLenis } from '@thinkpixellab-public/px-vue/composables/useLenis';
|
|
15
|
+
*
|
|
16
|
+
* export default {
|
|
17
|
+
* setup() {
|
|
18
|
+
* const { getLenis } = useLenis();
|
|
19
|
+
*
|
|
20
|
+
* onMounted(async () => {
|
|
21
|
+
* const lenis = await getLenis();
|
|
22
|
+
*
|
|
23
|
+
* // scroll to top smoothly
|
|
24
|
+
* lenis.scrollTo(0);
|
|
25
|
+
*
|
|
26
|
+
* // scroll to element
|
|
27
|
+
* lenis.scrollTo('#my-section');
|
|
28
|
+
*
|
|
29
|
+
* // scroll to specific position
|
|
30
|
+
* lenis.scrollTo(500, { duration: 2 });
|
|
31
|
+
* });
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // Listen to scroll events for parallax
|
|
37
|
+
* import { ref, onMounted } from 'vue';
|
|
38
|
+
* import { useLenis } from '@thinkpixellab-public/px-vue/composables/useLenis';
|
|
39
|
+
*
|
|
40
|
+
* export default {
|
|
41
|
+
* setup() {
|
|
42
|
+
* const { getLenis } = useLenis();
|
|
43
|
+
* const parallaxOffset = ref(0);
|
|
44
|
+
*
|
|
45
|
+
* onMounted(async () => {
|
|
46
|
+
* const lenis = await getLenis();
|
|
47
|
+
*
|
|
48
|
+
* lenis.on('scroll', (e) => {
|
|
49
|
+
* parallaxOffset.value = e.scroll * 0.5;
|
|
50
|
+
* });
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* return { parallaxOffset };
|
|
54
|
+
* }
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Custom scroll behavior
|
|
59
|
+
* import { useLenis } from '@thinkpixellab-public/px-vue/composables/useLenis';
|
|
60
|
+
*
|
|
61
|
+
* export default {
|
|
62
|
+
* setup() {
|
|
63
|
+
* const { getLenis } = useLenis();
|
|
64
|
+
*
|
|
65
|
+
* const scrollToSection = async (selector) => {
|
|
66
|
+
* const lenis = await getLenis();
|
|
67
|
+
*
|
|
68
|
+
* lenis.scrollTo(selector, {
|
|
69
|
+
* duration: 1.5,
|
|
70
|
+
* easing: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
|
|
71
|
+
* });
|
|
72
|
+
* };
|
|
73
|
+
*
|
|
74
|
+
* return { scrollToSection };
|
|
75
|
+
* }
|
|
76
|
+
* }
|
|
77
|
+
*/
|
|
78
|
+
export const useLenis = () => {
|
|
79
|
+
if (process.server) {
|
|
80
|
+
return { getLenis: () => null };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
getLenis: loadLenisInstance
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// composables/useScrollAnimation.js
|
|
2
|
+
import { loadScrollTrigger } from './loadGsap';
|
|
3
|
+
import { loadLenisInstance } from './loadLenis';
|
|
4
|
+
|
|
5
|
+
let scrollTriggerPromise = null;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Vue composable for scroll-triggered animations with GSAP ScrollTrigger and Lenis
|
|
9
|
+
*
|
|
10
|
+
* Ensures GSAP, ScrollTrigger, and Lenis are loaded and configured for optimal scroll animations.
|
|
11
|
+
* Automatically manages RAF control transfer from Lenis to GSAP for optimal performance.
|
|
12
|
+
* After ready resolves, gsap and ScrollTrigger are available globally.
|
|
13
|
+
*
|
|
14
|
+
* @returns {Object} Object with ready promise and getLenis function
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Basic scroll-triggered animation
|
|
18
|
+
* import { useScrollAnimation } from '@thinkpixellab-public/px-vue/composables/useScrollAnimations';
|
|
19
|
+
*
|
|
20
|
+
* export default {
|
|
21
|
+
* setup() {
|
|
22
|
+
* const { ready } = useScrollAnimation();
|
|
23
|
+
*
|
|
24
|
+
* onMounted(async () => {
|
|
25
|
+
* await ready;
|
|
26
|
+
*
|
|
27
|
+
* // gsap and ScrollTrigger are now available globally
|
|
28
|
+
* gsap.to('.fade-in', {
|
|
29
|
+
* opacity: 1,
|
|
30
|
+
* y: 0,
|
|
31
|
+
* scrollTrigger: {
|
|
32
|
+
* trigger: '.fade-in',
|
|
33
|
+
* start: 'top 80%',
|
|
34
|
+
* end: 'bottom 20%',
|
|
35
|
+
* scrub: true
|
|
36
|
+
* }
|
|
37
|
+
* });
|
|
38
|
+
* });
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Parallax scrolling with Lenis integration
|
|
44
|
+
* import { useScrollAnimation } from '@thinkpixellab-public/px-vue/composables/useScrollAnimations';
|
|
45
|
+
*
|
|
46
|
+
* export default {
|
|
47
|
+
* setup() {
|
|
48
|
+
* const { ready, getLenis } = useScrollAnimation();
|
|
49
|
+
*
|
|
50
|
+
* onMounted(async () => {
|
|
51
|
+
* await ready;
|
|
52
|
+
* const lenis = await getLenis();
|
|
53
|
+
*
|
|
54
|
+
* // smooth scroll to section
|
|
55
|
+
* lenis.scrollTo('#section-2', { duration: 2 });
|
|
56
|
+
*
|
|
57
|
+
* // parallax background with global gsap
|
|
58
|
+
* gsap.to('.bg-parallax', {
|
|
59
|
+
* yPercent: -50,
|
|
60
|
+
* ease: 'none',
|
|
61
|
+
* scrollTrigger: {
|
|
62
|
+
* trigger: '.parallax-section',
|
|
63
|
+
* start: 'top bottom',
|
|
64
|
+
* end: 'bottom top',
|
|
65
|
+
* scrub: true
|
|
66
|
+
* }
|
|
67
|
+
* });
|
|
68
|
+
* });
|
|
69
|
+
* }
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* // Complex timeline with ScrollTrigger
|
|
74
|
+
* import { ref, onMounted } from 'vue';
|
|
75
|
+
* import { useScrollAnimation } from '@thinkpixellab-public/px-vue/composables/useScrollAnimations';
|
|
76
|
+
*
|
|
77
|
+
* export default {
|
|
78
|
+
* setup() {
|
|
79
|
+
* const { ready } = useScrollAnimation();
|
|
80
|
+
* const container = ref(null);
|
|
81
|
+
*
|
|
82
|
+
* onMounted(async () => {
|
|
83
|
+
* await ready;
|
|
84
|
+
*
|
|
85
|
+
* // gsap is available globally
|
|
86
|
+
* const tl = gsap.timeline({
|
|
87
|
+
* scrollTrigger: {
|
|
88
|
+
* trigger: container.value,
|
|
89
|
+
* start: 'top center',
|
|
90
|
+
* end: 'bottom center',
|
|
91
|
+
* scrub: 1,
|
|
92
|
+
* pin: true
|
|
93
|
+
* }
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* tl.from('.item-1', { x: -100, opacity: 0 })
|
|
97
|
+
* .from('.item-2', { x: 100, opacity: 0 }, '-=0.5')
|
|
98
|
+
* .from('.item-3', { y: 100, opacity: 0 }, '-=0.3');
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* return { container };
|
|
102
|
+
* }
|
|
103
|
+
* }
|
|
104
|
+
*/
|
|
105
|
+
export const useScrollAnimation = () => {
|
|
106
|
+
if (process.server) {
|
|
107
|
+
return {
|
|
108
|
+
ready: Promise.resolve(),
|
|
109
|
+
getLenis: () => null,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const init = async () => {
|
|
114
|
+
if (!scrollTriggerPromise) {
|
|
115
|
+
scrollTriggerPromise = (async () => {
|
|
116
|
+
const [{ gsap, ScrollTrigger }, lenis] = await Promise.all([
|
|
117
|
+
loadScrollTrigger(),
|
|
118
|
+
loadLenisInstance(),
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
// Take over RAF control from Lenis
|
|
122
|
+
lenis.stop();
|
|
123
|
+
|
|
124
|
+
lenis.on('scroll', ScrollTrigger.update);
|
|
125
|
+
gsap.ticker.add(time => {
|
|
126
|
+
lenis.raf(time * 1000);
|
|
127
|
+
});
|
|
128
|
+
gsap.ticker.lagSmoothing(0);
|
|
129
|
+
|
|
130
|
+
// ScrollTrigger is now available globally
|
|
131
|
+
return { lenis };
|
|
132
|
+
})();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return scrollTriggerPromise;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
ready: init().then(() => {}), // resolves when ScrollTrigger is ready
|
|
140
|
+
getLenis: async () => (await init()).lenis,
|
|
141
|
+
};
|
|
142
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thinkpixellab-public/px-vue",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.27",
|
|
4
4
|
"description": "General purpose Vue components and helpers that can be used across projects.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Pixel Lab"
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
"chrono-node": "^2.7.8",
|
|
30
30
|
"dateformat": "^5.0.3",
|
|
31
31
|
"figma-squircle": "^1.1.0",
|
|
32
|
-
"gsap": "^3.
|
|
32
|
+
"gsap": "^3.13.0",
|
|
33
33
|
"highlight.js": "^11.11.1",
|
|
34
|
+
"lenis": "^1.1.17",
|
|
34
35
|
"mitt": "^3.0.1",
|
|
35
36
|
"qr-code-styling": "^1.9.2",
|
|
36
37
|
"sortablejs": "^1.15.6",
|