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'
|
import type { CodeObject } from '../../../types/code'
|
||||||
|
|
||||||
export const scrollFloatCode: CodeObject = {
|
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>
|
usage: `<template>
|
||||||
<ScrollFloat
|
<ScrollFloat
|
||||||
:children="scrollText"
|
:children="scrollText"
|
||||||
|
|||||||
@@ -1,141 +1,123 @@
|
|||||||
<template>
|
<template>
|
||||||
<h2 ref="containerRef" :class="`scroll-float ${containerClassName}`">
|
<h2 ref="containerRef" :class="`overflow-hidden ${containerClassName}`">
|
||||||
<span :class="`scroll-float-text ${textClassName}`">
|
<span :class="`inline-block text-center leading-relaxed font-black ${textClassName}`" style="font-size: clamp(1.6rem, 8vw, 10rem);">
|
||||||
<span
|
<span
|
||||||
v-for="(char, index) in splitText"
|
v-for="(char, index) in splitText"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="char"
|
class="inline-block char"
|
||||||
>
|
>
|
||||||
{{ char === " " ? "\u00A0" : char }}
|
{{ char === " " ? "\u00A0" : char }}
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</span>
|
||||||
</template>
|
</h2>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||||
import { gsap } from 'gsap';
|
import { gsap } from 'gsap';
|
||||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
|
|
||||||
gsap.registerPlugin(ScrollTrigger);
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: string;
|
children: string;
|
||||||
scrollContainerRef?: { current: HTMLElement | null };
|
scrollContainerRef?: { current: HTMLElement | null };
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
textClassName?: string;
|
textClassName?: string;
|
||||||
animationDuration?: number;
|
animationDuration?: number;
|
||||||
ease?: string;
|
ease?: string;
|
||||||
scrollStart?: string;
|
scrollStart?: string;
|
||||||
scrollEnd?: string;
|
scrollEnd?: string;
|
||||||
stagger?: number;
|
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>(), {
|
const tl = gsap.fromTo(
|
||||||
containerClassName: '',
|
charElements,
|
||||||
textClassName: '',
|
{
|
||||||
animationDuration: 1,
|
willChange: 'opacity, transform',
|
||||||
ease: 'back.inOut(2)',
|
opacity: 0,
|
||||||
scrollStart: 'center bottom+=50%',
|
yPercent: 120,
|
||||||
scrollEnd: 'bottom bottom-=40%',
|
scaleY: 2.3,
|
||||||
stagger: 0.03
|
scaleX: 0.7,
|
||||||
});
|
transformOrigin: '50% 0%'
|
||||||
|
|
||||||
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();
|
|
||||||
},
|
},
|
||||||
{ 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>
|
scrollTriggerInstance = tl.scrollTrigger || null;
|
||||||
.scroll-float {
|
};
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-float-text {
|
onMounted(() => {
|
||||||
display: inline-block;
|
initializeAnimation();
|
||||||
font-size: clamp(1.6rem, 8vw, 10rem);
|
});
|
||||||
font-weight: 900;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.char {
|
onUnmounted(() => {
|
||||||
display: inline-block;
|
if (scrollTriggerInstance) {
|
||||||
|
scrollTriggerInstance.kill();
|
||||||
}
|
}
|
||||||
</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>
|
<template>
|
||||||
<div class="profile-card-demo">
|
|
||||||
<TabbedLayout>
|
<TabbedLayout>
|
||||||
<template #preview>
|
<template #preview>
|
||||||
<div
|
<div
|
||||||
@@ -47,7 +46,6 @@
|
|||||||
<CliInstallation :command="scrollFloatCode.cli" />
|
<CliInstallation :command="scrollFloatCode.cli" />
|
||||||
</template>
|
</template>
|
||||||
</TabbedLayout>
|
</TabbedLayout>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
Reference in New Issue
Block a user