Yazılım Panteri
Nuxt 3 GSAP custom page transitions
@kapa.ai
CustomLoader.vue
<template>
<div
class="indicator fixed top-0 left-0"
:style="{
pointerEvents: 'none',
width: '100%',
height:
${props.height}px
,
opacity: isLoading ? 1 : 0,
background: error ? props.errorColor : props.color || undefined,
backgroundSize: '100% auto', // Always full size
transform: scaleX(${Math.max(progress, 10)}%)
, // Ensure minimum width
transformOrigin: 'left',
transition: 'transform 0.1s, height 0.4s, opacity 0.4s',
zIndex: 999999
}"></div>
<div
id="transitionLayer"
class="fixed left-0 w-full bg-black z-[10000] flex flex-col items-center justify-start h-0 bottom-0 overflow-hidden"
ref="transitionLayer"></div>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import gsap from 'gsap';
// Props
const props = defineProps({
throttle: {
type: Number,
default: 200
},
duration: {
type: Number,
default: 2000
},
height: {
type: Number,
default: 3 // Adjust height for thicker loader
},
color: {
type: String,
default: 'repeating-linear-gradient(to right,#00dc82 0%,#34cdfe 50%,#0047e1 100%)'
},
errorColor: {
type: String,
default: 'repeating-linear-gradient(to right,#f87171 0%,#ef4444 100%)'
},
estimatedProgress: {
type: Function,
default: (duration, elapsed) => (2 / Math.PI) * 100 * Math.atan(((elapsed / duration) * 100) / 50)
}
});
// Refs
const transitionLayer = ref(null);
const animationComplete = ref(false); // Tracks animation state
// Loading Indicator
const { progress, isLoading, error, start, finish, clear } = useLoadingIndicator({
duration: props.duration,
throttle: props.throttle,
estimatedProgress: props.estimatedProgress
});
// Router instance
const router = useRouter();
// Watch for loading state
watch(progress, newVal => {
if (newVal === 100) {
startAnimation();
}
});
// Start Animation
function startAnimation() {
gsap.set(transitionLayer.value, { height: 0, top: 'auto', bottom: 0 });
gsap.to(transitionLayer.value, {
height: '100%',
duration: 1,
ease: 'power2.out',
onComplete: () => {
finishAnimation();
}
});
}
// Finish Animation
function finishAnimation() {
const appContainer = document.getElementById('app');
const navbar = document.getElementById('navbar');
const tl = gsap.timeline({
onComplete: () => {
// Remove GSAP-applied styles
gsap.set(appContainer, { clearProps: 'all' });
}
});
animationComplete.value = false;
gsap.set(transitionLayer.value, { height: '100%', top: 0, bottom: 'auto' });
tl.to(transitionLayer.value, {
height: 0,
top: '-100%',
duration: 1,
ease: 'power2.out'
});
tl.fromTo(
appContainer,
{
yPercent: 100
},
{
yPercent: 0,
duration: 0.8,
ease: 'power2.out'
},
'<+=0.1'
);
tl.play();
}
// Mount hook to ensure transition layer is set up
onMounted(() => {
animationComplete.value = false;
});
</script>
<style scoped>
#transitionLayer {
will-change: height;
}
.counter-enter-active,
.counter-leave-active {
transition: all 0.15s ease-out;
}
.counter-leave-active {
position: absolute;
}
.counter-enter-from {
transform: translateY(-300%);
}
.counter-enter-to {
transform: translateY(0%);
}
.counter-leave-from {
transform: translateY(0%);
}
.counter-leave-to {
transform: translateY(300%);
}
</style>
app.vue
<template>
<CustomLoader></CustomLoader>
<NuxtLayout>
<NuxtPage></NuxtPage>
</NuxtLayout>
</template>
<script setup></script>
i want to make a custom page loader. new page should mount after finishAnimation starts. important point is new page should continue to it's loading progress. just not affect visually until animation complete15 replies
Nuxt 3 GSAP custom page transitions
@kapa.ai
/middleware/loading.global.js
i'm using nested pages sometimes and page:finish hook called multiple times when that happen. for exapmle i have pages/profile.vue and also have pages/profile/[name]/index.vue.
is there any prevent or fix to call page:finish hook multiple times for same request?
15 replies
Need help for component library with vue 3
Hello guys! I need some advices and help about my Project. I'm currently working on a aceternity ui and shadcn ui like component library for vue and nuxt 3. I have prepared some examples and skeleton about it but i feel something is missing but i don't know how to fix it. I'm not a 10 year experienced guy worked on a Google or apple so i need some guidance.
Here is a demo version of the Project
https://nuxt3-component-library.pages.dev/docs/components/3d-card
And of course github repo
https://github.com/safakdinc/nuxt3-component-library
If you are interested, feel free to contact via dm.
Important reminder! Since this is an independent project, I cannot give you earnings references or promises of success. we'll wait and see what happens
4 replies
Nuxt 3 CORS error
i send a get request to the server. payload is a youtube link. it can be any youtube video. ytdl-core recieve this link and find it's id. after that it's return a link like the first picture. you can use it in <audio src> element and play it freely. but when this code runs, source.value = ctx.createMediaElementSource(audio.value); i get error message. i don't know cors and nuxt 3 server very well and i need help
6 replies
Nuxt 3 CORS error
/server/api/audio_link.js
import ytdl from 'ytdl-core';
export default defineEventHandler(async event => {
const query = getQuery(event);
const videoUrl = query.url;
const videoId = ytdl.getURLVideoID(videoUrl);
const audioInfo = await ytdl.getInfo(videoId);
const audioFormat = ytdl.chooseFormat(audioInfo.formats, { quality: 'highestaudio' });
return {
data: audioFormat.url
};
});
6 replies
Nuxt 3 CORS error
AudioVisualizer.vue
<template>
<div class="w-full h-[50vh] flex justify-center items-center box">
<div class="w-full h-full relative transition-all duration-500 flex gap-1 items-end" ref="visualizer"></div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import '@/assets/visualizer.css';
const visualizer = ref(null);
const audio = ref(null);
const ctx = new window.AudioContext();
const analyser = ctx.createAnalyser();
const source = ref(null);
analyser.fftSize = 64;
const bufferLength = analyser.frequencyBinCount;
let dataArray = new Uint8Array(bufferLength);
const elements = ref([]);
onMounted(async () => {
audio.value = document.querySelector('audio');
audio.value.crossOrigin = 'anonymous';
source.value = ctx.createMediaElementSource(audio.value);
source.value.connect(analyser);
source.value.connect(ctx.destination);
});
onMounted(async () => {
for (let i = 0; i < bufferLength; i++) {
let element = document.createElement('div');
element.classList.add('element');
elements.value.push(element);
visualizer.value.appendChild(element);
}
update();
});
const clamp = (num, min, max) => {
if (num >= max) {
return max;
} else if (num <= min) {
return min;
} else {
return num;
}
};
const update = () => {
requestAnimationFrame(update);
analyser.getByteFrequencyData(dataArray);
for (let i = 0; i < bufferLength; i++) {
let item = dataArray[i];
item = item > 150 ? item : item * 1.5;
elements.value[i].style.height =
${item}px
;
}
};
</script>6 replies