mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
@@ -2,7 +2,7 @@ import code from '@content/TextAnimations/ScrollFloat/ScrollFloat.vue?raw'
|
||||
import type { CodeObject } from '../../../types/code'
|
||||
|
||||
export const scrollFloatCode: CodeObject = {
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/Components/ProfileCard`,
|
||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/ScrollFloat`,
|
||||
usage: `<template>
|
||||
<ScrollFloat
|
||||
:children="scrollText"
|
||||
|
||||
@@ -1,141 +1,123 @@
|
||||
<template>
|
||||
<h2 ref="containerRef" :class="`scroll-float ${containerClassName}`">
|
||||
<span :class="`scroll-float-text ${textClassName}`">
|
||||
<span
|
||||
v-for="(char, index) in splitText"
|
||||
:key="index"
|
||||
class="char"
|
||||
>
|
||||
{{ char === " " ? "\u00A0" : char }}
|
||||
</span>
|
||||
<h2 ref="containerRef" :class="`overflow-hidden ${containerClassName}`">
|
||||
<span :class="`inline-block text-center leading-relaxed font-black ${textClassName}`" style="font-size: clamp(1.6rem, 8vw, 10rem);">
|
||||
<span
|
||||
v-for="(char, index) in splitText"
|
||||
:key="index"
|
||||
class="inline-block char"
|
||||
>
|
||||
{{ char === " " ? "\u00A0" : char }}
|
||||
</span>
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
interface Props {
|
||||
children: string;
|
||||
scrollContainerRef?: { current: HTMLElement | null };
|
||||
containerClassName?: string;
|
||||
textClassName?: string;
|
||||
animationDuration?: number;
|
||||
ease?: string;
|
||||
scrollStart?: string;
|
||||
scrollEnd?: string;
|
||||
stagger?: number;
|
||||
</span>
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
interface Props {
|
||||
children: string;
|
||||
scrollContainerRef?: { current: HTMLElement | null };
|
||||
containerClassName?: string;
|
||||
textClassName?: string;
|
||||
animationDuration?: number;
|
||||
ease?: string;
|
||||
scrollStart?: string;
|
||||
scrollEnd?: string;
|
||||
stagger?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
containerClassName: '',
|
||||
textClassName: '',
|
||||
animationDuration: 1,
|
||||
ease: 'back.inOut(2)',
|
||||
scrollStart: 'center bottom+=50%',
|
||||
scrollEnd: 'bottom bottom-=40%',
|
||||
stagger: 0.03
|
||||
});
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null);
|
||||
let scrollTriggerInstance: ScrollTrigger | null = null;
|
||||
|
||||
const splitText = computed(() => {
|
||||
const text = typeof props.children === 'string' ? props.children : '';
|
||||
return text.split("");
|
||||
});
|
||||
|
||||
const initializeAnimation = () => {
|
||||
const el = containerRef.value;
|
||||
if (!el) return;
|
||||
|
||||
const scroller =
|
||||
props.scrollContainerRef && props.scrollContainerRef.current
|
||||
? props.scrollContainerRef.current
|
||||
: window;
|
||||
|
||||
const charElements = el.querySelectorAll('.char');
|
||||
|
||||
if (scrollTriggerInstance) {
|
||||
scrollTriggerInstance.kill();
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
containerClassName: '',
|
||||
textClassName: '',
|
||||
animationDuration: 1,
|
||||
ease: 'back.inOut(2)',
|
||||
scrollStart: 'center bottom+=50%',
|
||||
scrollEnd: 'bottom bottom-=40%',
|
||||
stagger: 0.03
|
||||
});
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null);
|
||||
let scrollTriggerInstance: ScrollTrigger | null = null;
|
||||
|
||||
const splitText = computed(() => {
|
||||
const text = typeof props.children === 'string' ? props.children : '';
|
||||
return text.split("");
|
||||
});
|
||||
|
||||
const initializeAnimation = () => {
|
||||
const el = containerRef.value;
|
||||
if (!el) return;
|
||||
|
||||
const scroller =
|
||||
props.scrollContainerRef && props.scrollContainerRef.current
|
||||
? props.scrollContainerRef.current
|
||||
: window;
|
||||
|
||||
const charElements = el.querySelectorAll('.char');
|
||||
|
||||
if (scrollTriggerInstance) {
|
||||
scrollTriggerInstance.kill();
|
||||
}
|
||||
|
||||
const tl = gsap.fromTo(
|
||||
charElements,
|
||||
{
|
||||
willChange: 'opacity, transform',
|
||||
opacity: 0,
|
||||
yPercent: 120,
|
||||
scaleY: 2.3,
|
||||
scaleX: 0.7,
|
||||
transformOrigin: '50% 0%'
|
||||
},
|
||||
{
|
||||
duration: props.animationDuration,
|
||||
ease: props.ease,
|
||||
opacity: 1,
|
||||
yPercent: 0,
|
||||
scaleY: 1,
|
||||
scaleX: 1,
|
||||
stagger: props.stagger,
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
scroller,
|
||||
start: props.scrollStart,
|
||||
end: props.scrollEnd,
|
||||
scrub: true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
scrollTriggerInstance = tl.scrollTrigger || null;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initializeAnimation();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (scrollTriggerInstance) {
|
||||
scrollTriggerInstance.kill();
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
[
|
||||
() => props.children,
|
||||
() => props.scrollContainerRef,
|
||||
() => props.animationDuration,
|
||||
() => props.ease,
|
||||
() => props.scrollStart,
|
||||
() => props.scrollEnd,
|
||||
() => props.stagger
|
||||
],
|
||||
() => {
|
||||
initializeAnimation();
|
||||
|
||||
const tl = gsap.fromTo(
|
||||
charElements,
|
||||
{
|
||||
willChange: 'opacity, transform',
|
||||
opacity: 0,
|
||||
yPercent: 120,
|
||||
scaleY: 2.3,
|
||||
scaleX: 0.7,
|
||||
transformOrigin: '50% 0%'
|
||||
},
|
||||
{ deep: true }
|
||||
{
|
||||
duration: props.animationDuration,
|
||||
ease: props.ease,
|
||||
opacity: 1,
|
||||
yPercent: 0,
|
||||
scaleY: 1,
|
||||
scaleX: 1,
|
||||
stagger: props.stagger,
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
scroller,
|
||||
start: props.scrollStart,
|
||||
end: props.scrollEnd,
|
||||
scrub: true
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scroll-float {
|
||||
overflow: hidden;
|
||||
|
||||
scrollTriggerInstance = tl.scrollTrigger || null;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initializeAnimation();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (scrollTriggerInstance) {
|
||||
scrollTriggerInstance.kill();
|
||||
}
|
||||
|
||||
.scroll-float-text {
|
||||
display: inline-block;
|
||||
font-size: clamp(1.6rem, 8vw, 10rem);
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.char {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
});
|
||||
|
||||
watch(
|
||||
[
|
||||
() => props.children,
|
||||
() => props.scrollContainerRef,
|
||||
() => props.animationDuration,
|
||||
() => props.ease,
|
||||
() => props.scrollStart,
|
||||
() => props.scrollEnd,
|
||||
() => props.stagger
|
||||
],
|
||||
() => {
|
||||
initializeAnimation();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
@@ -1,5 +1,4 @@
|
||||
<template>
|
||||
<div class="profile-card-demo">
|
||||
<TabbedLayout>
|
||||
<template #preview>
|
||||
<div
|
||||
@@ -47,7 +46,6 @@
|
||||
<CliInstallation :command="scrollFloatCode.cli" />
|
||||
</template>
|
||||
</TabbedLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
Reference in New Issue
Block a user