fix: clean up code formatting and improve hover effect styling

This commit is contained in:
David Haz
2025-07-25 17:29:20 +03:00
parent 6b73cc4df9
commit ef84a39c64
6 changed files with 59 additions and 66 deletions

View File

@@ -23,4 +23,4 @@ export const variableProximity = createCodeObject(code, 'TextAnimations/Variable
const containerRef = ref<HTMLElement | null>(null); const containerRef = ref<HTMLElement | null>(null);
</script>` </script>`
}); });

View File

@@ -10,17 +10,15 @@
v-for="(src, idx) in images" v-for="(src, idx) in images"
:key="idx" :key="idx"
ref="cardRefs" ref="cardRefs"
class="absolute w-[200px] aspect-square border-[5px] border-white rounded-[25px] overflow-hidden shadow-[0_4px_10px_rgba(0,0,0,0.2)] bg-[#f8f9fa] opacity-0" class="absolute w-[200px] aspect-square border-[5px] border-white rounded-[25px] overflow-hidden shadow-[0_4px_10px_rgba(0,0,0,0.2)] bg-[#0b0b0b] opacity-0"
:style="{ transform: transformStyles[idx] ?? 'none' }" :style="{ transform: transformStyles[idx] ?? 'none' }"
@mouseenter="() => pushSiblings(idx)" @mouseenter="() => pushSiblings(idx)"
@mouseleave="resetSiblings" @mouseleave="resetSiblings"
> >
<div v-if="!imageLoaded[idx]" class="absolute inset-0 z-[1] flex items-center justify-center bg-black/80"> <div v-if="!imageLoaded[idx]" class="absolute inset-0 z-[1] bg-[#0b0b0b] overflow-hidden shimmer-container"></div>
<div class="w-[75px] h-[75px] border-[3px] border-gray-400 border-t-[#27FF64] rounded-full animate-spin"></div>
</div>
<img <img
class="absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ease-in-out z-[2]" class="absolute inset-0 w-full h-full object-cover z-[2] transition-opacity duration-700 ease-out"
:src="src" :src="src"
:alt="`card-${idx}`" :alt="`card-${idx}`"
:style="{ opacity: imageLoaded[idx] ? 1 : 0 }" :style="{ opacity: imageLoaded[idx] ? 1 : 0 }"
@@ -165,13 +163,34 @@ const playEntranceAnimation = () => {
}; };
onMounted(playEntranceAnimation); onMounted(playEntranceAnimation);
watch(() => props.images, async () => { watch(
await nextTick(); () => props.images,
gsap.set(cardRefs.value, { opacity: 0, scale: 0 }); async () => {
playEntranceAnimation(); await nextTick();
}); gsap.set(cardRefs.value, { opacity: 0, scale: 0 });
playEntranceAnimation();
}
);
onUnmounted(() => { onUnmounted(() => {
gsap.killTweensOf(cardRefs.value); gsap.killTweensOf(cardRefs.value);
}); });
</script> </script>
<style scoped>
.shimmer-container {
background: linear-gradient(110deg, transparent 40%, rgba(255, 255, 255, 0.1) 50%, transparent 60%);
background-size: 600% 600%;
background-position: -600% 0;
animation: shimmer-sweep 6s infinite;
}
@keyframes shimmer-sweep {
0% {
background-position: -600% 0;
}
100% {
background-position: 200% 0;
}
}
</style>

View File

@@ -378,7 +378,7 @@ const ParticleCard = defineComponent({
const GlobalSpotlight = defineComponent({ const GlobalSpotlight = defineComponent({
name: 'GlobalSpotlight', name: 'GlobalSpotlight',
props: { props: {
gridRef: {type: [Object, null] as PropType<HTMLDivElement | null>, required: true}, gridRef: { type: [Object, null] as PropType<HTMLDivElement | null>, required: true },
disableAnimations: { type: Boolean, default: false }, disableAnimations: { type: Boolean, default: false },
enabled: { type: Boolean, default: true }, enabled: { type: Boolean, default: true },
spotlightRadius: { type: Number, default: DEFAULT_SPOTLIGHT_RADIUS }, spotlightRadius: { type: Number, default: DEFAULT_SPOTLIGHT_RADIUS },

View File

@@ -26,12 +26,11 @@
> >
{{ letter }} {{ letter }}
</span> </span>
<span <span v-if="wordIndex < words.length - 1" class="inline-block">&nbsp;</span>
v-if="wordIndex < words.length - 1" </span>
class="inline-block" <span class="absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap clip-rect-0 border-0">
>&nbsp;</span> {{ props.label }}
</span> </span>
<span class="absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap clip-rect-0 border-0">{{ props.label }}</span>
</span> </span>
</template> </template>
@@ -57,7 +56,7 @@ const props = withDefaults(defineProps<VariableProximityProps>(), {
falloff: 'linear', falloff: 'linear',
className: '', className: '',
style: () => ({}), style: () => ({}),
onClick: undefined, onClick: undefined
}); });
const rootRef = ref<HTMLElement | null>(null); const rootRef = ref<HTMLElement | null>(null);
@@ -90,7 +89,7 @@ const parsedSettings = computed(() => {
return Array.from(fromSettings.entries()).map(([axis, fromValue]) => ({ return Array.from(fromSettings.entries()).map(([axis, fromValue]) => ({
axis, axis,
fromValue, fromValue,
toValue: toSettings.get(axis) ?? fromValue, toValue: toSettings.get(axis) ?? fromValue
})); }));
}); });
@@ -100,15 +99,17 @@ const calculateDistance = (x1: number, y1: number, x2: number, y2: number) =>
const calculateFalloff = (distance: number) => { const calculateFalloff = (distance: number) => {
const norm = Math.min(Math.max(1 - distance / props.radius, 0), 1); const norm = Math.min(Math.max(1 - distance / props.radius, 0), 1);
switch (props.falloff) { switch (props.falloff) {
case 'exponential': return norm ** 2; case 'exponential':
case 'gaussian': return Math.exp(-((distance / (props.radius / 2)) ** 2) / 2); return norm ** 2;
case 'gaussian':
return Math.exp(-((distance / (props.radius / 2)) ** 2) / 2);
case 'linear': case 'linear':
default: return norm; default:
return norm;
} }
}; };
const getLetterKey = (wordIndex: number, letterIndex: number) => const getLetterKey = (wordIndex: number, letterIndex: number) => `${wordIndex}-${letterIndex}`;
`${wordIndex}-${letterIndex}`;
const getGlobalLetterIndex = (wordIndex: number, letterIndex: number) => { const getGlobalLetterIndex = (wordIndex: number, letterIndex: number) => {
let globalIndex = 0; let globalIndex = 0;
@@ -175,12 +176,7 @@ const animationLoop = () => {
const letterCenterX = rect.left + rect.width / 2 - containerRect.left; const letterCenterX = rect.left + rect.width / 2 - containerRect.left;
const letterCenterY = rect.top + rect.height / 2 - containerRect.top; const letterCenterY = rect.top + rect.height / 2 - containerRect.top;
const distance = calculateDistance( const distance = calculateDistance(mousePosition.value.x, mousePosition.value.y, letterCenterX, letterCenterY);
mousePosition.value.x,
mousePosition.value.y,
letterCenterX,
letterCenterY
);
if (distance >= props.radius) { if (distance >= props.radius) {
return; return;

View File

@@ -1,4 +1,3 @@
<template> <template>
<TabbedLayout> <TabbedLayout>
<template #preview> <template #preview>
@@ -18,26 +17,11 @@
</div> </div>
<Customize> <Customize>
<PreviewSwitch <PreviewSwitch title="Enable Hover Effect" v-model="enableHover" />
title="Enable Hover Effect"
v-model="enableHover"
/>
<PreviewSlider <PreviewSlider title="Animation Delay" v-model="animationDelay" :min="0.1" :max="2" :step="0.1" />
title="Animation Delay"
v-model="animationDelay"
:min="0.1"
:max="2"
:step="0.1"
/>
<PreviewSlider <PreviewSlider title="Animation Stagger" v-model="animationStagger" :min="0" :max="0.3" :step="0.01" />
title="Animation Stagger"
v-model="animationStagger"
:min="0"
:max="0.3"
:step="0.01"
/>
</Customize> </Customize>
<PropTable :data="propData" /> <PropTable :data="propData" />
@@ -76,11 +60,11 @@ const animationStagger = ref(0.08);
const { rerenderKey, forceRerender } = useForceRerender(); const { rerenderKey, forceRerender } = useForceRerender();
const images = ref([ const images = ref([
'https://picsum.photos/400/400?grayscale', 'https://picsum.photos/id/287/300/300?grayscale',
'https://picsum.photos/500/500?grayscale', 'https://picsum.photos/id/1001/300/300?grayscale',
'https://picsum.photos/600/600?grayscale', 'https://picsum.photos/id/1027/300/300?grayscale',
'https://picsum.photos/700/700?grayscale', 'https://picsum.photos/id/1025/300/300?grayscale',
'https://picsum.photos/300/300?grayscale' 'https://picsum.photos/id/1026/300/300?grayscale'
]); ]);
const transformStyles = ref([ const transformStyles = ref([
@@ -126,7 +110,7 @@ const propData = [
name: 'animationStagger', name: 'animationStagger',
type: 'number', type: 'number',
default: '-', default: '-',
description: 'Time (in seconds) between each card\'s animation.' description: "Time (in seconds) between each card's animation."
}, },
{ {
name: 'easeType', name: 'easeType',
@@ -144,7 +128,7 @@ const propData = [
name: 'enableHover', name: 'enableHover',
type: 'boolean', type: 'boolean',
default: 'false', default: 'false',
description: 'If true, hovering pushes siblings aside and flattens the hovered card\'s rotation.' description: "If true, hovering pushes siblings aside and flattens the hovered card's rotation."
} }
]; ];
</script> </script>

View File

@@ -17,13 +17,7 @@
</div> </div>
<Customize> <Customize>
<PreviewSlider <PreviewSlider title="Radius" v-model="radius" :min="50" :max="300" :step="10" />
title="Radius"
v-model="radius"
:min="50"
:max="300"
:step="10"
/>
<div class="flex gap-2 flex-wrap"> <div class="flex gap-2 flex-wrap">
<button <button
@@ -80,13 +74,13 @@ const propData = [
{ {
name: 'fromFontVariationSettings', name: 'fromFontVariationSettings',
type: 'string', type: 'string',
default: '"\'wght\' 400, \'opsz\' 9"', default: "\"'wght' 400, 'opsz' 9\"",
description: 'The starting variation settings.' description: 'The starting variation settings.'
}, },
{ {
name: 'toFontVariationSettings', name: 'toFontVariationSettings',
type: 'string', type: 'string',
default: '"\'wght\' 800, \'opsz\' 40"', default: "\"'wght' 800, 'opsz' 40\"",
description: 'The variation settings to reach at cursor proximity.' description: 'The variation settings to reach at cursor proximity.'
}, },
{ {
@@ -128,5 +122,5 @@ const propData = [
]; ];
</script> </script>
<style scoped> <style scoped>
@import url("https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght@8..144,100..1000&display=swap"); @import url('https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght@8..144,100..1000&display=swap');
</style> </style>