Add Crosshair animation component

This commit is contained in:
onmax
2025-07-19 12:54:17 +02:00
parent 8cc0e53874
commit f2dde88db5
5 changed files with 413 additions and 1 deletions

View File

@@ -0,0 +1,140 @@
<template>
<TabbedLayout>
<template #preview>
<div ref="containerRef" class="demo-container relative min-h-[300px] overflow-hidden">
<Crosshair :container-ref="targeted ? containerElement : null" :color="color" />
<div class="flex flex-col justify-center items-center">
<a
ref="linkRef"
href="https://github.com/DavidHDev/vue-bits"
class="text-center font-black text-[2rem] md:text-[4rem] transition-all duration-300 ease-in-out hover:text-[#5227ff]"
:style="{ minWidth: minWidth + 'px' }"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
{{ linkText }}
</a>
<p class="relative -top-[10px] text-[#444] text-sm">(hover the text)</p>
</div>
<span
ref="hiddenRef"
class="absolute invisible whitespace-nowrap pointer-events-none overflow-hidden text-center font-black text-[2rem] md:text-[4rem]"
aria-hidden="true"
>
{{ linkText }}
</span>
</div>
<Customize>
<PreviewColor title="Crosshair Color" v-model="color" />
<button
class="bg-[#170D27] hover:bg-[#271E37] text-white text-xs px-3 py-2 border border-[#271E37] rounded-[10px] h-8 transition-colors mt-2"
@click="toggleTargeted"
>
Cursor Container
<span :class="targeted ? 'text-green-400' : 'text-orange-400'">
&nbsp;{{ targeted ? 'Viewport' : 'Targeted' }}
</span>
</button>
</Customize>
<PropTable :data="propData" />
<Dependencies :dependency-list="['gsap']" />
</template>
<template #code>
<CodeExample :code-object="crosshair" />
</template>
<template #cli>
<CliInstallation :command="crosshair.cli" />
</template>
</TabbedLayout>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, useTemplateRef, nextTick, computed } from 'vue';
import TabbedLayout from '@/components/common/TabbedLayout.vue';
import PropTable from '@/components/common/PropTable.vue';
import Dependencies from '@/components/code/Dependencies.vue';
import CliInstallation from '@/components/code/CliInstallation.vue';
import CodeExample from '@/components/code/CodeExample.vue';
import Customize from '@/components/common/Customize.vue';
import PreviewColor from '@/components/common/PreviewColor.vue';
import Crosshair from '@/content/Animations/Crosshair/Crosshair.vue';
import { crosshair } from '@/constants/code/Animations/crosshairCode';
const DEFAULT_TEXT = 'Aim... aand...';
const linkText = ref(DEFAULT_TEXT);
const color = ref('#ffffff');
const targeted = ref(true);
const minWidth = ref(0);
const containerRef = useTemplateRef<HTMLDivElement>('containerRef');
const linkRef = useTemplateRef<HTMLAnchorElement>('linkRef');
const hiddenRef = useTemplateRef<HTMLSpanElement>('hiddenRef');
const containerElement = computed(() => containerRef.value);
const propData = [
{
name: 'color',
type: 'string',
default: "'white'",
description: 'Color of the crosshair lines.'
},
{
name: 'containerRef',
type: 'Ref<HTMLElement | null>',
default: 'null',
description: 'Optional container ref to limit crosshair to specific element. If null, crosshair will be active on entire viewport.'
}
];
const handleMouseEnter = () => {
linkText.value = 'Shoot!!!';
};
const handleMouseLeave = () => {
linkText.value = DEFAULT_TEXT;
};
const toggleTargeted = () => {
targeted.value = !targeted.value;
};
const updateMinWidth = async () => {
await nextTick();
if (hiddenRef.value) {
const width = hiddenRef.value.getBoundingClientRect().width;
if (minWidth.value < width) {
minWidth.value = width;
}
}
};
onMounted(() => {
updateMinWidth();
});
let resizeObserver: ResizeObserver | null = null;
onMounted(() => {
if (hiddenRef.value) {
resizeObserver = new ResizeObserver(() => {
updateMinWidth();
});
resizeObserver.observe(hiddenRef.value);
}
});
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect();
}
});
</script>