Files
BA-VitePress-Pages/.vitepress/theme/components/Welcome-Box.vue
2025-11-26 15:17:27 +08:00

242 lines
5.2 KiB
Vue

<template>
<div
class="welcome-box"
ref="welcomeBoxRef"
@mousemove="parallax"
@mouseleave="reset"
:style="{ transform: `rotateY(${calcY}deg) rotateX(${calcX}deg)` }"
>
<span class="welcome-text">{{ welcomeText }}</span>
<transition name="fade" appear>
<div
class="info-box"
:style="{
background: `linear-gradient(${angle}deg, var(--infobox-background-initial), var(--infobox-background-final))`,
}"
>
<img @dragstart.prevent src="../assets/banner/avatar.webp" alt="" class="avatar" />
<span class="name">{{ name }}</span>
<span class="motto">
{{ mottoText }}
<span class="pointer"></span>
</span>
<ul>
<li v-for="item in social" :key="item.url">
<a :href="item.url" target="_blank" rel="noopener noreferrer">
<i :class="`iconfont icon-${item.icon} social`"></i>
</a>
</li>
</ul>
</div>
</transition>
</div>
</template>
<script setup lang="ts">
import { useData } from 'vitepress'
import { ref, onMounted } from 'vue'
const themeConfig = useData().theme.value
const name = themeConfig.name
const welcomeText = themeConfig.welcomeText
const motto = themeConfig.motto
const social = themeConfig.social
const multiple = 30
const welcomeBoxRef = ref<HTMLElement | null>(null)
const calcY = ref(0)
const calcX = ref(0)
const angle = ref(0)
const parallax = (e: MouseEvent) => {
if (welcomeBoxRef.value) {
window.requestAnimationFrame(() => {
const box = welcomeBoxRef.value!.getBoundingClientRect()
calcY.value = (e.clientX - box.x - box.width / 2) / multiple
calcX.value = -(e.clientY - box.y - box.height / 2) / multiple
angle.value = Math.floor(
getMouseAngle(e.clientY - box.y - box.height / 2, e.clientX - box.x - box.width / 2),
)
})
}
}
const getMouseAngle = (x: number, y: number) => {
const radians = Math.atan2(y, x)
let angle = radians * (180 / Math.PI)
if (angle < 0) {
angle += 360
}
return angle
}
const reset = () => {
calcX.value = calcY.value = angle.value = 0
}
let index = 0
const mottoText = ref('')
const randomMotto = motto[Math.floor(Math.random() * motto.length)]
const addNextCharacter = () => {
if (index < randomMotto.length) {
mottoText.value += randomMotto[index]
index++
setTimeout(addNextCharacter, Math.random() * 150 + 50)
}
}
onMounted(() => {
addNextCharacter()
})
</script>
<style scoped lang="less">
.welcome-box {
margin-top: 4.2vw;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 100;
transition: transform 0.2s, color 0.5s, text-shadow 0.5s;
}
.welcome-text {
font-size: 4.5vw;
font-weight: bold;
color: var(--welcome-text-color);
text-shadow: var(--welcome-text-shadow);
text-align: center;
margin-bottom: 5vw;
user-select: none;
}
.info-box {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
padding: 6vh 2vw 3vh;
width: 40vw;
border-radius: 3vw;
box-shadow: var(--info-box-shadow);
backdrop-filter: var(--blur-val) saturate(120%);
.avatar {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
width: 7.5vw;
height: 7.5vw;
border-radius: 50%;
border: solid 3px var(--infobox-border-color);
transition: transform 0.6s ease, box-shadow 0.4s ease, filter 0.5s;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
cursor: pointer;
user-select: none;
filter: var(--img-brightness);
&:hover {
transform: translate(-50%, -50%) rotate(1turn) scale(1.1);
box-shadow: 0 0 7px rgba(0, 0, 0, 0.6);
}
}
.name {
font-size: 1.5vw;
margin-top: 3vh;
}
.motto {
font-size: 1vw;
font-weight: bold;
animation: color-change 0.8s linear infinite;
margin-top: 3vh;
text-align: center;
.pointer {
display: inline-block;
margin: -0.5vh 0 0;
padding: 0;
vertical-align: middle;
width: 2px;
height: 1vw;
animation: color-change 0.8s linear infinite;
background-color: var(--pointerColor);
}
@keyframes color-change {
0%,
40% {
--pointerColor: var(--font-color-grey);
}
60%,
100% {
--pointerColor: transparent;
}
}
}
ul {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 3.5vh;
width: 12vw;
padding: 0;
.social {
font-size: 1.5vw;
font-weight: 600;
transition: all 0.5s;
color: var(--font-color-grey);
&:hover {
filter: drop-shadow(0 0 5px var(--font-color-grey));
}
}
}
}
@media (max-width: 768px) {
.welcome-text {
font-size: 5vh;
margin-bottom: 10vh;
}
.info-box {
padding: 5vh 6vw 2vh;
width: 75vw;
border-radius: 4vh;
.avatar {
width: 10vh;
height: 10vh;
}
.name {
font-size: 2.5vh;
margin-top: 1.8vh;
}
.motto {
font-size: 1.5vh;
margin-top: 1.5vh;
.pointer {
height: 1.5vh;
}
}
ul {
margin-top: 1.8vh;
width: 32vw;
.social {
font-size: 2vh;
}
}
}
}
</style>