mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
Format ScrollReveal component and demo for improved readability and consistency
This commit is contained in:
@@ -1,165 +1,155 @@
|
||||
<template>
|
||||
<h2 ref="containerRef" :class="`my-5 ${containerClassName}`">
|
||||
<p
|
||||
:class="`leading-relaxed font-semibold ${textClassName}`"
|
||||
style="font-size: clamp(1.6rem, 4vw, 3rem);"
|
||||
>
|
||||
<span
|
||||
v-for="(word, index) in splitText"
|
||||
:key="index"
|
||||
:class="word.isWhitespace ? '' : 'inline-block word'"
|
||||
>
|
||||
{{ word.text }}
|
||||
</span>
|
||||
</p>
|
||||
</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 };
|
||||
enableBlur?: boolean;
|
||||
baseOpacity?: number;
|
||||
baseRotation?: number;
|
||||
blurStrength?: number;
|
||||
containerClassName?: string;
|
||||
textClassName?: string;
|
||||
rotationEnd?: string;
|
||||
wordAnimationEnd?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
enableBlur: true,
|
||||
baseOpacity: 0.1,
|
||||
baseRotation: 3,
|
||||
blurStrength: 4,
|
||||
containerClassName: '',
|
||||
textClassName: '',
|
||||
rotationEnd: 'bottom bottom',
|
||||
wordAnimationEnd: 'bottom bottom'
|
||||
});
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null);
|
||||
let scrollTriggerInstances: ScrollTrigger[] = [];
|
||||
|
||||
const splitText = computed(() => {
|
||||
const text = typeof props.children === 'string' ? props.children : '';
|
||||
return text.split(/(\s+)/).map((word, index) => ({
|
||||
text: word,
|
||||
isWhitespace: word.match(/^\s+$/) !== null,
|
||||
key: index
|
||||
}));
|
||||
});
|
||||
|
||||
const initializeAnimation = () => {
|
||||
const el = containerRef.value;
|
||||
if (!el) return;
|
||||
|
||||
scrollTriggerInstances.forEach(trigger => trigger.kill());
|
||||
scrollTriggerInstances = [];
|
||||
|
||||
const scroller =
|
||||
props.scrollContainerRef && props.scrollContainerRef.current
|
||||
? props.scrollContainerRef.current
|
||||
: window;
|
||||
|
||||
const rotationTl = gsap.fromTo(
|
||||
el,
|
||||
{ transformOrigin: '0% 50%', rotate: props.baseRotation },
|
||||
{
|
||||
ease: 'none',
|
||||
rotate: 0,
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
scroller,
|
||||
start: 'top bottom',
|
||||
end: props.rotationEnd,
|
||||
scrub: true,
|
||||
},
|
||||
<h2 ref="containerRef" :class="`my-5 ${containerClassName}`">
|
||||
<p :class="`leading-relaxed font-semibold ${textClassName}`" style="font-size: clamp(1.6rem, 4vw, 3rem)">
|
||||
<span v-for="(word, index) in splitText" :key="index" :class="word.isWhitespace ? '' : 'inline-block word'">
|
||||
{{ word.text }}
|
||||
</span>
|
||||
</p>
|
||||
</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 };
|
||||
enableBlur?: boolean;
|
||||
baseOpacity?: number;
|
||||
baseRotation?: number;
|
||||
blurStrength?: number;
|
||||
containerClassName?: string;
|
||||
textClassName?: string;
|
||||
rotationEnd?: string;
|
||||
wordAnimationEnd?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
enableBlur: true,
|
||||
baseOpacity: 0.1,
|
||||
baseRotation: 3,
|
||||
blurStrength: 4,
|
||||
containerClassName: '',
|
||||
textClassName: '',
|
||||
rotationEnd: 'bottom bottom',
|
||||
wordAnimationEnd: 'bottom bottom'
|
||||
});
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null);
|
||||
let scrollTriggerInstances: ScrollTrigger[] = [];
|
||||
|
||||
const splitText = computed(() => {
|
||||
const text = typeof props.children === 'string' ? props.children : '';
|
||||
return text.split(/(\s+)/).map((word, index) => ({
|
||||
text: word,
|
||||
isWhitespace: word.match(/^\s+$/) !== null,
|
||||
key: index
|
||||
}));
|
||||
});
|
||||
|
||||
const initializeAnimation = () => {
|
||||
const el = containerRef.value;
|
||||
if (!el) return;
|
||||
|
||||
scrollTriggerInstances.forEach(trigger => trigger.kill());
|
||||
scrollTriggerInstances = [];
|
||||
|
||||
const scroller =
|
||||
props.scrollContainerRef && props.scrollContainerRef.current ? props.scrollContainerRef.current : window;
|
||||
|
||||
const rotationTl = gsap.fromTo(
|
||||
el,
|
||||
{ transformOrigin: '0% 50%', rotate: props.baseRotation },
|
||||
{
|
||||
ease: 'none',
|
||||
rotate: 0,
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
scroller,
|
||||
start: 'top bottom',
|
||||
end: props.rotationEnd,
|
||||
scrub: true
|
||||
}
|
||||
);
|
||||
|
||||
if (rotationTl.scrollTrigger) {
|
||||
scrollTriggerInstances.push(rotationTl.scrollTrigger);
|
||||
}
|
||||
|
||||
const wordElements = el.querySelectorAll('.word');
|
||||
|
||||
const opacityTl = gsap.fromTo(
|
||||
);
|
||||
|
||||
if (rotationTl.scrollTrigger) {
|
||||
scrollTriggerInstances.push(rotationTl.scrollTrigger);
|
||||
}
|
||||
|
||||
const wordElements = el.querySelectorAll('.word');
|
||||
|
||||
const opacityTl = gsap.fromTo(
|
||||
wordElements,
|
||||
{ opacity: props.baseOpacity, willChange: 'opacity' },
|
||||
{
|
||||
ease: 'none',
|
||||
opacity: 1,
|
||||
stagger: 0.05,
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
scroller,
|
||||
start: 'top bottom-=20%',
|
||||
end: props.wordAnimationEnd,
|
||||
scrub: true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (opacityTl.scrollTrigger) {
|
||||
scrollTriggerInstances.push(opacityTl.scrollTrigger);
|
||||
}
|
||||
|
||||
if (props.enableBlur) {
|
||||
const blurTl = gsap.fromTo(
|
||||
wordElements,
|
||||
{ opacity: props.baseOpacity, willChange: 'opacity' },
|
||||
{ filter: `blur(${props.blurStrength}px)` },
|
||||
{
|
||||
ease: 'none',
|
||||
opacity: 1,
|
||||
filter: 'blur(0px)',
|
||||
stagger: 0.05,
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
scroller,
|
||||
start: 'top bottom-=20%',
|
||||
end: props.wordAnimationEnd,
|
||||
scrub: true,
|
||||
},
|
||||
scrub: true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (opacityTl.scrollTrigger) {
|
||||
scrollTriggerInstances.push(opacityTl.scrollTrigger);
|
||||
}
|
||||
|
||||
if (props.enableBlur) {
|
||||
const blurTl = gsap.fromTo(
|
||||
wordElements,
|
||||
{ filter: `blur(${props.blurStrength}px)` },
|
||||
{
|
||||
ease: 'none',
|
||||
filter: 'blur(0px)',
|
||||
stagger: 0.05,
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
scroller,
|
||||
start: 'top bottom-=20%',
|
||||
end: props.wordAnimationEnd,
|
||||
scrub: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (blurTl.scrollTrigger) {
|
||||
scrollTriggerInstances.push(blurTl.scrollTrigger);
|
||||
}
|
||||
if (blurTl.scrollTrigger) {
|
||||
scrollTriggerInstances.push(blurTl.scrollTrigger);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initializeAnimation();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
scrollTriggerInstances.forEach(trigger => trigger.kill());
|
||||
});
|
||||
|
||||
watch(
|
||||
[
|
||||
() => props.children,
|
||||
() => props.scrollContainerRef,
|
||||
() => props.enableBlur,
|
||||
() => props.baseRotation,
|
||||
() => props.baseOpacity,
|
||||
() => props.rotationEnd,
|
||||
() => props.wordAnimationEnd,
|
||||
() => props.blurStrength
|
||||
],
|
||||
() => {
|
||||
initializeAnimation();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
scrollTriggerInstances.forEach(trigger => trigger.kill());
|
||||
});
|
||||
|
||||
watch(
|
||||
[
|
||||
() => props.children,
|
||||
() => props.scrollContainerRef,
|
||||
() => props.enableBlur,
|
||||
() => props.baseRotation,
|
||||
() => props.baseOpacity,
|
||||
() => props.rotationEnd,
|
||||
() => props.wordAnimationEnd,
|
||||
() => props.blurStrength
|
||||
],
|
||||
() => {
|
||||
initializeAnimation();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user