Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/constants/Categories.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Highlighted sidebar items
export const NEW = ['Color Bends', 'Ghost Cursor', 'Laser Flow', 'Liquid Ether', 'Pixel Blast', 'Floating Lines', 'Light Pillar'];
export const UPDATED = [];
export const UPDATED = ['Animated Content', 'Fade Content'];

// Used for main sidebar navigation
export const CATEGORIES = [
Expand Down
12 changes: 10 additions & 2 deletions src/constants/code/Animations/animatedContentCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export const animatedContent = createCodeObject(code, 'Animations/AnimatedConten
:scale="1"
:threshold="0.1"
:delay="0"
:disappear-after="0"
:disappear-duration="0.5"
disappear-ease="power3.in"
@complete="handleComplete"
@disappearance-complete="handleDisappearance"
>
<div class="your-content">
Content to animate
Expand All @@ -27,7 +31,11 @@ export const animatedContent = createCodeObject(code, 'Animations/AnimatedConten
import AnimatedContent from "./AnimatedContent.vue";
const handleComplete = () => {
console.log("Animation completed!");,
});
console.log("Animation completed!");
};
const handleDisappearance = () => {
console.log("Disappearance completed!");
};
</script>`
});
18 changes: 17 additions & 1 deletion src/constants/code/Animations/fadeContentCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@ import code from '@content/Animations/FadeContent/FadeContent.vue?raw';
import { createCodeObject } from '@/types/code';

export const fadeContent = createCodeObject(code, 'Animations/FadeContent', {
installation: `npm install gsap`,
usage: `<template>
<FadeContent
:blur="true"
:duration="1000"
:delay="200"
:threshold="0.1"
:initial-opacity="0"
easing="ease-out"
ease="power2.out"
:disappear-after="0"
:disappear-duration="500"
disappear-ease="power2.in"
class-name="my-fade-content"
:on-complete="handleComplete"
:on-disappearance-complete="handleDisappearance"
@complete="handleComplete"
@disappearance-complete="handleDisappearance"
>
<div class="content-to-fade">
<h2>This content will fade in!</h2>
Expand All @@ -21,5 +29,13 @@ export const fadeContent = createCodeObject(code, 'Animations/FadeContent', {

<script setup lang="ts">
import FadeContent from "./FadeContent.vue";

const handleComplete = () => {
console.log("Fade completed!");
};

const handleDisappearance = () => {
console.log("Disappearance completed!");
};
</script>`
});
120 changes: 53 additions & 67 deletions src/content/Animations/AnimatedContent/AnimatedContent.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<script setup lang="ts">
import { onMounted, onUnmounted, watch, useTemplateRef } from 'vue';
import { onMounted, onUnmounted, useTemplateRef } from 'vue';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

interface AnimatedContentProps {
container?: string | HTMLElement | null;
distance?: number;
direction?: 'vertical' | 'horizontal';
reverse?: boolean;
Expand All @@ -16,10 +17,14 @@ interface AnimatedContentProps {
scale?: number;
threshold?: number;
delay?: number;
disappearAfter?: number;
disappearDuration?: number;
disappearEase?: string | ((progress: number) => number);
className?: string;
}

const props = withDefaults(defineProps<AnimatedContentProps>(), {
container: null,
distance: 100,
direction: 'vertical',
reverse: false,
Expand All @@ -30,106 +35,87 @@ const props = withDefaults(defineProps<AnimatedContentProps>(), {
scale: 1,
threshold: 0.1,
delay: 0,
disappearAfter: 0,
disappearDuration: 0.5,
disappearEase: 'power3.in',
className: ''
});

const emit = defineEmits<{
complete: [];
disappearanceComplete: [];
}>();

const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
let scrollTriggerInstance: ScrollTrigger | null = null;
let timelineInstance: gsap.core.Timeline | null = null;

onMounted(() => {
const el = containerRef.value;
if (!el) return;

let scrollerTarget: HTMLElement | string | null =
props.container || document.getElementById('snap-main-container') || null;

if (typeof scrollerTarget === 'string') {
scrollerTarget = document.querySelector(scrollerTarget) as HTMLElement | null;
}

const axis = props.direction === 'horizontal' ? 'x' : 'y';
const offset = props.reverse ? -props.distance : props.distance;
const startPct = (1 - props.threshold) * 100;

gsap.set(el, {
[axis]: offset,
scale: props.scale,
opacity: props.animateOpacity ? props.initialOpacity : 1
opacity: props.animateOpacity ? props.initialOpacity : 1,
visibility: 'visible'
});

timelineInstance = gsap.timeline({
paused: true,
delay: props.delay,
onComplete: () => {
emit('complete');
if (props.disappearAfter > 0) {
gsap.to(el, {
[axis]: props.reverse ? props.distance : -props.distance,
scale: 0.8,
opacity: props.animateOpacity ? props.initialOpacity : 0,
delay: props.disappearAfter,
duration: props.disappearDuration,
ease: props.disappearEase,
onComplete: () => emit('disappearanceComplete')
});
}
}
});

gsap.to(el, {
timelineInstance.to(el, {
[axis]: 0,
scale: 1,
opacity: 1,
duration: props.duration,
ease: props.ease,
delay: props.delay,
onComplete: () => emit('complete'),
scrollTrigger: {
trigger: el,
start: `top ${startPct}%`,
toggleActions: 'play none none none',
once: true
}
ease: props.ease
});
});

watch(
() => [
props.distance,
props.direction,
props.reverse,
props.duration,
props.ease,
props.initialOpacity,
props.animateOpacity,
props.scale,
props.threshold,
props.delay
],
() => {
const el = containerRef.value;
if (!el) return;

ScrollTrigger.getAll().forEach(t => t.kill());
gsap.killTweensOf(el);

const axis = props.direction === 'horizontal' ? 'x' : 'y';
const offset = props.reverse ? -props.distance : props.distance;
const startPct = (1 - props.threshold) * 100;

gsap.set(el, {
[axis]: offset,
scale: props.scale,
opacity: props.animateOpacity ? props.initialOpacity : 1
});

gsap.to(el, {
[axis]: 0,
scale: 1,
opacity: 1,
duration: props.duration,
ease: props.ease,
delay: props.delay,
onComplete: () => emit('complete'),
scrollTrigger: {
trigger: el,
start: `top ${startPct}%`,
toggleActions: 'play none none none',
once: true
}
});
},
{ deep: true }
);
scrollTriggerInstance = ScrollTrigger.create({
trigger: el,
scroller: scrollerTarget,
start: `top ${startPct}%`,
once: true,
onEnter: () => timelineInstance?.play()
});
});

onUnmounted(() => {
const el = containerRef.value;
if (el) {
ScrollTrigger.getAll().forEach(t => t.kill());
gsap.killTweensOf(el);
}
scrollTriggerInstance?.kill();
timelineInstance?.kill();
});
</script>

<template>
<div ref="containerRef" :class="`animated-content ${props.className}`">
<div ref="containerRef" :class="className" style="visibility: hidden">
<slot />
</div>
</template>
Expand Down
Loading