177 lines
4.2 KiB
Vue
177 lines
4.2 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
|
|
|
const props = defineProps({
|
|
text: {
|
|
type: String,
|
|
default: 'LOADING'
|
|
}
|
|
});
|
|
|
|
const characters = ref<HTMLSpanElement[]>([]);
|
|
const isDone = ref(false);
|
|
const cycleCount = 5;
|
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()-_=+{}|[]\\;\':"<>?,./`~'.split('');
|
|
let animationFrameId: number | null = null;
|
|
let resetTimeoutId: number | null = null;
|
|
|
|
// 将字符串拆分为单个字符的span
|
|
function letterize() {
|
|
const container = document.querySelector('.loading-word');
|
|
if (!container) return;
|
|
|
|
// 清除现有内容
|
|
container.innerHTML = '';
|
|
|
|
// 为每个字符创建span
|
|
for (let i = 0; i < props.text.length; i++) {
|
|
const span = document.createElement('span');
|
|
span.setAttribute('data-orig', props.text[i]);
|
|
span.textContent = '-';
|
|
span.className = `char${i+1}`;
|
|
container.appendChild(span);
|
|
}
|
|
|
|
// 获取所有span元素
|
|
characters.value = Array.from(container.querySelectorAll('span'));
|
|
}
|
|
|
|
// 获取随机字符
|
|
function getRandomChar() {
|
|
return chars[Math.floor(Math.random() * chars.length)];
|
|
}
|
|
|
|
// 动画循环
|
|
function animationLoop() {
|
|
let currentCycle = 0;
|
|
let currentLetterIndex = 0;
|
|
let isAnimationDone = false;
|
|
|
|
function loop() {
|
|
// 为未完成的字符设置随机字符和不透明度
|
|
for (let i = currentLetterIndex; i < characters.value.length; i++) {
|
|
const char = characters.value[i];
|
|
if (!char.classList.contains('done')) {
|
|
char.textContent = getRandomChar();
|
|
char.style.opacity = Math.random().toString();
|
|
}
|
|
}
|
|
|
|
if (currentCycle < cycleCount) {
|
|
// 继续当前周期
|
|
currentCycle++;
|
|
} else if (currentLetterIndex < characters.value.length) {
|
|
// 当前周期结束,显示下一个字符的原始值
|
|
const currentChar = characters.value[currentLetterIndex];
|
|
currentChar.textContent = currentChar.getAttribute('data-orig') || '';
|
|
currentChar.style.opacity = '1';
|
|
currentChar.classList.add('done');
|
|
currentLetterIndex++;
|
|
currentCycle = 0;
|
|
} else {
|
|
// 所有字符都已显示
|
|
isAnimationDone = true;
|
|
isDone.value = true;
|
|
}
|
|
|
|
if (!isAnimationDone) {
|
|
animationFrameId = requestAnimationFrame(loop);
|
|
} else {
|
|
// 等待一段时间后重置动画
|
|
resetTimeoutId = window.setTimeout(() => {
|
|
reset();
|
|
}, 750);
|
|
}
|
|
}
|
|
|
|
loop();
|
|
}
|
|
|
|
// 重置动画
|
|
function reset() {
|
|
isDone.value = false;
|
|
|
|
for (const char of characters.value) {
|
|
char.textContent = char.getAttribute('data-orig') || '';
|
|
char.classList.remove('done');
|
|
}
|
|
|
|
animationLoop();
|
|
}
|
|
|
|
// 清理所有定时器
|
|
function cleanup() {
|
|
if (animationFrameId !== null) {
|
|
cancelAnimationFrame(animationFrameId);
|
|
animationFrameId = null;
|
|
}
|
|
|
|
if (resetTimeoutId !== null) {
|
|
clearTimeout(resetTimeoutId);
|
|
resetTimeoutId = null;
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
letterize();
|
|
animationLoop();
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
cleanup();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="loading-screen">
|
|
<div class="loading-word"></div>
|
|
<div class="overlay"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.loading-screen {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: var(--voidraft-bg-gradient, radial-gradient(#222922, #000500));
|
|
z-index: 1000;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-family: var(--voidraft-mono-font, monospace),serif;
|
|
}
|
|
|
|
.loading-word {
|
|
color: var(--voidraft-loading-color, #fff);
|
|
font-size: 2.5em;
|
|
height: 2.5em;
|
|
line-height: 2.5em;
|
|
text-align: center;
|
|
text-shadow: var(--voidraft-loading-glow, 0 0 10px rgba(50, 255, 50, 0.5), 0 0 5px rgba(100, 255, 100, 0.5));
|
|
}
|
|
|
|
.loading-word span {
|
|
display: inline-block;
|
|
transform: translateX(100%) scale(0.9);
|
|
transition: transform 500ms;
|
|
}
|
|
|
|
.loading-word .done {
|
|
color: var(--voidraft-loading-done-color, #6f6);
|
|
transform: translateX(0) scale(1);
|
|
}
|
|
|
|
.overlay {
|
|
background-image: var(--voidraft-loading-overlay, linear-gradient(transparent 0%, rgba(10, 16, 10, 0.5) 50%));
|
|
background-size: 1000px 2px;
|
|
bottom: 0;
|
|
left: 0;
|
|
position: absolute;
|
|
right: 0;
|
|
top: 0;
|
|
pointer-events: none;
|
|
}
|
|
</style> |