kmcom-nuxt-layers 1.6.39 → 1.6.40
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/layers/motion/app/components/Motion/Marquee.vue +130 -130
- package/layers/motion/app/components/Motion/MarqueeText.vue +147 -0
- package/layers/motion/app/composables/useMarqueeCopies.ts +50 -0
- package/layers/motion/app/composables/useMarqueeVelocity.ts +44 -0
- package/layers/motion/tsconfig.json +1 -0
- package/package.json +1 -1
|
@@ -1,142 +1,142 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
const props = withDefaults(
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
const { gsap } = useGsap()
|
|
56
|
-
const { velocity: scrollVelocity, direction: scrollDirection } = useSmoothScroll()
|
|
57
|
-
|
|
58
|
-
const containerRef = ref<HTMLElement | null>(null)
|
|
59
|
-
const contentRef = ref<HTMLElement | null>(null)
|
|
60
|
-
const tweenRef = ref<gsap.core.Tween | null>(null)
|
|
61
|
-
|
|
62
|
-
const isPaused = ref(false)
|
|
63
|
-
const currentTimeScale = ref(1)
|
|
64
|
-
|
|
65
|
-
onMounted(() => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
2
|
+
const props = withDefaults(
|
|
3
|
+
defineProps<{
|
|
4
|
+
/**
|
|
5
|
+
* Base animation speed in pixels per second
|
|
6
|
+
*/
|
|
7
|
+
speed?: number
|
|
8
|
+
/**
|
|
9
|
+
* Direction of scroll
|
|
10
|
+
*/
|
|
11
|
+
direction?: 'left' | 'right'
|
|
12
|
+
/**
|
|
13
|
+
* Pause animation on hover
|
|
14
|
+
*/
|
|
15
|
+
pauseOnHover?: boolean
|
|
16
|
+
/**
|
|
17
|
+
* Gap between repeated items
|
|
18
|
+
*/
|
|
19
|
+
gap?: string
|
|
20
|
+
/**
|
|
21
|
+
* Enable velocity-based speed (scroll velocity affects marquee speed)
|
|
22
|
+
*/
|
|
23
|
+
velocityBased?: boolean
|
|
24
|
+
/**
|
|
25
|
+
* How much scroll velocity affects speed (0-1)
|
|
26
|
+
* 0 = no effect, 1 = velocity fully controls speed
|
|
27
|
+
*/
|
|
28
|
+
velocitySensitivity?: number
|
|
29
|
+
/**
|
|
30
|
+
* Reverse direction based on scroll direction
|
|
31
|
+
*/
|
|
32
|
+
velocityDirection?: boolean
|
|
33
|
+
/**
|
|
34
|
+
* Minimum speed multiplier when using velocity
|
|
35
|
+
*/
|
|
36
|
+
minSpeed?: number
|
|
37
|
+
/**
|
|
38
|
+
* Maximum speed multiplier when using velocity
|
|
39
|
+
*/
|
|
40
|
+
maxSpeed?: number
|
|
41
|
+
}>(),
|
|
42
|
+
{
|
|
43
|
+
speed: 50,
|
|
44
|
+
direction: 'left',
|
|
45
|
+
pauseOnHover: true,
|
|
46
|
+
gap: '2rem',
|
|
47
|
+
velocityBased: false,
|
|
48
|
+
velocitySensitivity: 0.5,
|
|
49
|
+
velocityDirection: false,
|
|
50
|
+
minSpeed: 0.2,
|
|
51
|
+
maxSpeed: 5,
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const { gsap } = useGsap()
|
|
56
|
+
const { velocity: scrollVelocity, direction: scrollDirection } = useSmoothScroll()
|
|
57
|
+
|
|
58
|
+
const containerRef = ref<HTMLElement | null>(null)
|
|
59
|
+
const contentRef = ref<HTMLElement | null>(null)
|
|
60
|
+
const tweenRef = ref<gsap.core.Tween | null>(null)
|
|
61
|
+
|
|
62
|
+
const isPaused = ref(false)
|
|
63
|
+
const currentTimeScale = ref(1)
|
|
64
|
+
|
|
65
|
+
onMounted(() => {
|
|
66
|
+
if (!containerRef.value || !contentRef.value) return
|
|
67
|
+
|
|
68
|
+
const content = contentRef.value
|
|
69
|
+
const contentWidth = content.offsetWidth
|
|
70
|
+
|
|
71
|
+
// Set up the infinite scroll animation
|
|
72
|
+
tweenRef.value = gsap.to(content, {
|
|
73
|
+
x: props.direction === 'left' ? -contentWidth / 2 : contentWidth / 2,
|
|
74
|
+
duration: contentWidth / props.speed,
|
|
75
|
+
ease: 'none',
|
|
76
|
+
repeat: -1,
|
|
77
|
+
modifiers: {
|
|
78
|
+
x: gsap.utils.unitize((x) => {
|
|
79
|
+
const mod = contentWidth / 2
|
|
80
|
+
return props.direction === 'left' ? parseFloat(x) % mod : Math.abs(parseFloat(x) % mod)
|
|
81
|
+
}),
|
|
82
|
+
},
|
|
83
|
+
})
|
|
83
84
|
})
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
// Watch velocity changes for velocity-based animation
|
|
87
|
-
watch(
|
|
88
|
-
[scrollVelocity, scrollDirection],
|
|
89
|
-
([vel, dir]) => {
|
|
90
|
-
if (!props.velocityBased || !tweenRef.value || isPaused.value) return
|
|
91
|
-
|
|
92
|
-
// Calculate velocity multiplier
|
|
93
|
-
const absVelocity = Math.abs(vel)
|
|
94
|
-
const velocityEffect = absVelocity * props.velocitySensitivity * 0.02
|
|
95
85
|
|
|
96
|
-
|
|
97
|
-
|
|
86
|
+
// Watch velocity changes for velocity-based animation
|
|
87
|
+
watch(
|
|
88
|
+
[scrollVelocity, scrollDirection],
|
|
89
|
+
([vel, dir]) => {
|
|
90
|
+
if (!props.velocityBased || !tweenRef.value || isPaused.value) return
|
|
91
|
+
|
|
92
|
+
// Calculate velocity multiplier
|
|
93
|
+
const absVelocity = Math.abs(vel)
|
|
94
|
+
const velocityEffect = absVelocity * props.velocitySensitivity * 0.02
|
|
95
|
+
|
|
96
|
+
// Base multiplier from velocity magnitude
|
|
97
|
+
let multiplier = 1 + velocityEffect
|
|
98
|
+
|
|
99
|
+
// Clamp to min/max
|
|
100
|
+
multiplier = Math.max(props.minSpeed, Math.min(props.maxSpeed, multiplier))
|
|
101
|
+
|
|
102
|
+
// Optionally reverse direction based on scroll direction
|
|
103
|
+
if (props.velocityDirection && dir !== 0) {
|
|
104
|
+
const baseDirection = props.direction === 'left' ? 1 : -1
|
|
105
|
+
multiplier *= dir * baseDirection
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Smoothly interpolate to target
|
|
109
|
+
gsap.to(currentTimeScale, {
|
|
110
|
+
value: multiplier,
|
|
111
|
+
duration: 0.3,
|
|
112
|
+
ease: 'power2.out',
|
|
113
|
+
onUpdate: () => {
|
|
114
|
+
if (tweenRef.value) {
|
|
115
|
+
tweenRef.value.timeScale(currentTimeScale.value)
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
{ immediate: false }
|
|
121
|
+
)
|
|
98
122
|
|
|
99
|
-
|
|
100
|
-
|
|
123
|
+
onUnmounted(() => {
|
|
124
|
+
tweenRef.value?.kill()
|
|
125
|
+
})
|
|
101
126
|
|
|
102
|
-
|
|
103
|
-
if (props.
|
|
104
|
-
|
|
105
|
-
|
|
127
|
+
function handleMouseEnter() {
|
|
128
|
+
if (props.pauseOnHover && tweenRef.value) {
|
|
129
|
+
gsap.to(tweenRef.value, { timeScale: 0, duration: 0.5 })
|
|
130
|
+
isPaused.value = true
|
|
106
131
|
}
|
|
107
|
-
|
|
108
|
-
// Smoothly interpolate to target
|
|
109
|
-
gsap.to(currentTimeScale, {
|
|
110
|
-
value: multiplier,
|
|
111
|
-
duration: 0.3,
|
|
112
|
-
ease: 'power2.out',
|
|
113
|
-
onUpdate: () => {
|
|
114
|
-
if (tweenRef.value) {
|
|
115
|
-
tweenRef.value.timeScale(currentTimeScale.value)
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
})
|
|
119
|
-
},
|
|
120
|
-
{ immediate: false }
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
onUnmounted(() => {
|
|
124
|
-
tweenRef.value?.kill()
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
function handleMouseEnter() {
|
|
128
|
-
if (props.pauseOnHover && tweenRef.value) {
|
|
129
|
-
gsap.to(tweenRef.value, { timeScale: 0, duration: 0.5 })
|
|
130
|
-
isPaused.value = true
|
|
131
132
|
}
|
|
132
|
-
}
|
|
133
133
|
|
|
134
|
-
function handleMouseLeave() {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
function handleMouseLeave() {
|
|
135
|
+
if (props.pauseOnHover && tweenRef.value) {
|
|
136
|
+
gsap.to(tweenRef.value, { timeScale: currentTimeScale.value, duration: 0.5 })
|
|
137
|
+
isPaused.value = false
|
|
138
|
+
}
|
|
138
139
|
}
|
|
139
|
-
}
|
|
140
140
|
</script>
|
|
141
141
|
|
|
142
142
|
<template>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ScrollTrigger as GSAPScrollTrigger } from 'gsap/ScrollTrigger'
|
|
3
|
+
|
|
4
|
+
interface VelocityMapping {
|
|
5
|
+
input: [number, number]
|
|
6
|
+
output: [number, number]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
texts = [],
|
|
11
|
+
velocity = 100,
|
|
12
|
+
className = '',
|
|
13
|
+
damping = 50,
|
|
14
|
+
stiffness = 400,
|
|
15
|
+
velocityMapping = { input: [0, 1000] as [number, number], output: [0, 5] as [number, number] },
|
|
16
|
+
pauseOnHover = false,
|
|
17
|
+
parallaxClassName = '',
|
|
18
|
+
scrollerClassName = '',
|
|
19
|
+
parallaxStyle = {},
|
|
20
|
+
scrollerStyle = {},
|
|
21
|
+
} = defineProps<{
|
|
22
|
+
texts?: string[]
|
|
23
|
+
velocity?: number
|
|
24
|
+
className?: string
|
|
25
|
+
damping?: number
|
|
26
|
+
stiffness?: number
|
|
27
|
+
velocityMapping?: VelocityMapping
|
|
28
|
+
pauseOnHover?: boolean
|
|
29
|
+
parallaxClassName?: string
|
|
30
|
+
scrollerClassName?: string
|
|
31
|
+
parallaxStyle?: Record<string, string | number>
|
|
32
|
+
scrollerStyle?: Record<string, string | number>
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const { gsap, ScrollTrigger } = useGsap()
|
|
36
|
+
const { velocityFactor } = useMarqueeVelocity({ damping, stiffness, velocityMapping })
|
|
37
|
+
|
|
38
|
+
const containerRef = ref<HTMLElement[]>([])
|
|
39
|
+
const copyRefs = ref<HTMLSpanElement[]>([])
|
|
40
|
+
|
|
41
|
+
const { copyWidths, calculatedCopies } = useMarqueeCopies(
|
|
42
|
+
containerRef,
|
|
43
|
+
copyRefs,
|
|
44
|
+
computed(() => texts.length)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const baseX = ref<number[]>([])
|
|
48
|
+
const directionFactors = ref<number[]>([])
|
|
49
|
+
const isPaused = ref(false)
|
|
50
|
+
|
|
51
|
+
let scrollTriggerInstance: GSAPScrollTrigger | null = null
|
|
52
|
+
let animLastTime = 0
|
|
53
|
+
|
|
54
|
+
const wrap = (min: number, max: number, v: number): number => {
|
|
55
|
+
const range = max - min
|
|
56
|
+
if (range === 0) return min
|
|
57
|
+
return ((((v - min) % range) + range) % range) + min
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const scrollTransforms = computed(() =>
|
|
61
|
+
texts.map((_, index) => {
|
|
62
|
+
const singleWidth = copyWidths.value[index]
|
|
63
|
+
if (!singleWidth) return '0px'
|
|
64
|
+
return `${wrap(-singleWidth, 0, baseX.value[index] ?? 0)}px`
|
|
65
|
+
})
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
const setCopyRef = (el: Element | ComponentPublicInstance | null, index: number) => {
|
|
69
|
+
if (el instanceof HTMLSpanElement) copyRefs.value[index] = el
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function animate() {
|
|
73
|
+
if (isPaused.value) return
|
|
74
|
+
|
|
75
|
+
const now = performance.now()
|
|
76
|
+
const delta = animLastTime ? now - animLastTime : 16
|
|
77
|
+
animLastTime = now
|
|
78
|
+
|
|
79
|
+
texts.forEach((_, index) => {
|
|
80
|
+
const baseVelocity = index % 2 !== 0 ? -velocity : velocity
|
|
81
|
+
let moveBy = (directionFactors.value[index] ?? 1) * baseVelocity * (delta / 1000)
|
|
82
|
+
|
|
83
|
+
if (velocityFactor.value < 0) {
|
|
84
|
+
directionFactors.value[index] = -1
|
|
85
|
+
} else if (velocityFactor.value > 0) {
|
|
86
|
+
directionFactors.value[index] = 1
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
moveBy += (directionFactors.value[index] ?? 1) * moveBy * velocityFactor.value
|
|
90
|
+
baseX.value[index] = (baseX.value[index] ?? 0) + moveBy
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onMounted(async () => {
|
|
95
|
+
await nextTick()
|
|
96
|
+
|
|
97
|
+
baseX.value = new Array(texts.length).fill(0)
|
|
98
|
+
directionFactors.value = new Array(texts.length).fill(1)
|
|
99
|
+
animLastTime = performance.now()
|
|
100
|
+
|
|
101
|
+
const firstContainer = containerRef.value[0]
|
|
102
|
+
if (firstContainer) {
|
|
103
|
+
scrollTriggerInstance = ScrollTrigger.create({
|
|
104
|
+
trigger: firstContainer,
|
|
105
|
+
start: 'top bottom',
|
|
106
|
+
end: 'bottom top',
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
gsap.ticker.add(animate)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
onUnmounted(() => {
|
|
114
|
+
gsap.ticker.remove(animate)
|
|
115
|
+
scrollTriggerInstance?.kill()
|
|
116
|
+
})
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<template>
|
|
120
|
+
<section
|
|
121
|
+
class="w-full overflow-x-hidden"
|
|
122
|
+
@mouseenter="isPaused = pauseOnHover"
|
|
123
|
+
@mouseleave="isPaused = false"
|
|
124
|
+
>
|
|
125
|
+
<div
|
|
126
|
+
v-for="(text, index) in texts"
|
|
127
|
+
:key="index"
|
|
128
|
+
ref="containerRef"
|
|
129
|
+
:class="`${parallaxClassName} relative w-full overflow-x-hidden overflow-y-visible`"
|
|
130
|
+
:style="parallaxStyle"
|
|
131
|
+
>
|
|
132
|
+
<div
|
|
133
|
+
:class="`${scrollerClassName} font-styles-logo flex py-2 text-center font-sans text-4xl font-medium tracking-[-0.02em] whitespace-nowrap text-neutral drop-shadow md:text-[5rem] md:leading-[5rem]`"
|
|
134
|
+
:style="{ transform: `translateX(${scrollTransforms[index] ?? '0px'})`, ...scrollerStyle }"
|
|
135
|
+
>
|
|
136
|
+
<span
|
|
137
|
+
v-for="spanIndex in calculatedCopies[index] ?? 15"
|
|
138
|
+
:key="spanIndex"
|
|
139
|
+
:ref="spanIndex === 1 ? (el) => setCopyRef(el, index) : undefined"
|
|
140
|
+
:class="`shrink-0 ${className} text-primary-500`"
|
|
141
|
+
>
|
|
142
|
+
{{ text }}
|
|
143
|
+
</span>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</section>
|
|
147
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { MaybeRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function useMarqueeCopies(
|
|
4
|
+
containerRefs: Ref<HTMLElement[]>,
|
|
5
|
+
copyRefs: Ref<HTMLSpanElement[]>,
|
|
6
|
+
rowCount: MaybeRef<number>,
|
|
7
|
+
) {
|
|
8
|
+
const copyWidths = ref<number[]>([])
|
|
9
|
+
const calculatedCopies = ref<number[]>([])
|
|
10
|
+
|
|
11
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
12
|
+
|
|
13
|
+
function calculate() {
|
|
14
|
+
const n = unref(rowCount)
|
|
15
|
+
for (let i = 0; i < n; i++) {
|
|
16
|
+
const copy = copyRefs.value[i]
|
|
17
|
+
const container = containerRefs.value[i]
|
|
18
|
+
if (!copy || !container) continue
|
|
19
|
+
const singleWidth = copy.offsetWidth
|
|
20
|
+
if (singleWidth === 0) continue
|
|
21
|
+
const effectiveWidth = Math.max(container.offsetWidth, window.innerWidth)
|
|
22
|
+
const minCopies = Math.ceil((effectiveWidth * 2.5) / singleWidth)
|
|
23
|
+
copyWidths.value[i] = singleWidth
|
|
24
|
+
calculatedCopies.value[i] = Math.max(minCopies, 8)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function debouncedCalculate() {
|
|
29
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
30
|
+
debounceTimer = setTimeout(() => {
|
|
31
|
+
calculate()
|
|
32
|
+
debounceTimer = null
|
|
33
|
+
}, 150)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
onMounted(() => {
|
|
37
|
+
nextTick(() => {
|
|
38
|
+
calculate()
|
|
39
|
+
setTimeout(calculate, 100)
|
|
40
|
+
})
|
|
41
|
+
window.addEventListener('resize', debouncedCalculate, { passive: true })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
onUnmounted(() => {
|
|
45
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
46
|
+
window.removeEventListener('resize', debouncedCalculate)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return { copyWidths, calculatedCopies }
|
|
50
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { MaybeRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
interface VelocityMapping {
|
|
4
|
+
input: [number, number]
|
|
5
|
+
output: [number, number]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface UseMarqueeVelocityOptions {
|
|
9
|
+
damping?: MaybeRef<number>
|
|
10
|
+
stiffness?: MaybeRef<number>
|
|
11
|
+
velocityMapping?: MaybeRef<VelocityMapping>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useMarqueeVelocity(opts: UseMarqueeVelocityOptions = {}) {
|
|
15
|
+
const { velocity: rawVelocity } = useSmoothScroll()
|
|
16
|
+
const { gsap } = useGsap()
|
|
17
|
+
|
|
18
|
+
const smoothVelocity = ref(0)
|
|
19
|
+
const velocityFactor = ref(0)
|
|
20
|
+
|
|
21
|
+
function tick() {
|
|
22
|
+
const damping = (unref(opts.damping) ?? 50) / 1000
|
|
23
|
+
const stiffness = (unref(opts.stiffness) ?? 400) / 1000
|
|
24
|
+
const mapping: VelocityMapping = unref(opts.velocityMapping) ?? {
|
|
25
|
+
input: [0, 1000],
|
|
26
|
+
output: [0, 5],
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
smoothVelocity.value += (rawVelocity.value - smoothVelocity.value) * stiffness
|
|
30
|
+
smoothVelocity.value *= 1 - damping
|
|
31
|
+
|
|
32
|
+
const inputRange = mapping.input[1] - mapping.input[0]
|
|
33
|
+
const outputRange = mapping.output[1] - mapping.output[0]
|
|
34
|
+
let t = (Math.abs(smoothVelocity.value) - mapping.input[0]) / inputRange
|
|
35
|
+
t = Math.max(0, Math.min(1, t))
|
|
36
|
+
velocityFactor.value = mapping.output[0] + t * outputRange
|
|
37
|
+
if (smoothVelocity.value < 0) velocityFactor.value *= -1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
onMounted(() => gsap.ticker.add(tick))
|
|
41
|
+
onUnmounted(() => gsap.ticker.remove(tick))
|
|
42
|
+
|
|
43
|
+
return { velocityFactor }
|
|
44
|
+
}
|
package/package.json
CHANGED