feat: expand square loader set and tune bouncing

This commit is contained in:
JOJO 2026-04-05 01:22:50 +08:00
parent dc60c7a123
commit 3b521e9616
9 changed files with 396 additions and 17 deletions

View File

@ -12,43 +12,53 @@
<style scoped> <style scoped>
.bouncing-squares-loader { .bouncing-squares-loader {
--square-size: 3px; --loader-scale: 0.45;
--square-gap: 10px; --square-size: 7.6px; /* 7.6 * 0.45 ≈ 3.42px,略大一点 */
display: grid; --square-gap: 8.9px; /* 固定间距,缩放后约 4px */
grid-template-columns: repeat(3, var(--square-size)); --start-x: 3px;
column-gap: var(--square-gap); position: relative;
align-items: end; width: 40px;
width: calc(3 * var(--square-size) + 2 * var(--square-gap));
height: 20px; height: 20px;
transform: scale(var(--loader-scale));
transform-origin: center;
} }
.square { .square {
position: absolute;
width: var(--square-size); width: var(--square-size);
height: var(--square-size); height: var(--square-size);
left: 15%;
background-color: var(--claude-text-tertiary); background-color: var(--claude-text-tertiary);
transform-origin: center bottom; transform-origin: 50%;
animation: bounce-square .5s alternate infinite ease; animation: bounce-square 0.5s alternate infinite ease;
} }
@keyframes bounce-square { @keyframes bounce-square {
0% { 0% {
transform: translateY(0) scaleX(1.7) scaleY(0.7); top: 20px;
height: calc(var(--square-size) * 0.667);
transform: scaleX(1.7);
} }
40% { 40% {
transform: translateY(-8px) scaleX(1) scaleY(1); height: var(--square-size);
transform: scaleX(1);
} }
100% { 100% {
transform: translateY(-17px) scaleX(1) scaleY(1); top: 0%;
} }
} }
.square:nth-child(1) {
left: var(--start-x);
}
.square:nth-child(2) { .square:nth-child(2) {
animation-delay: .2s; left: calc(var(--start-x) + var(--square-size) + var(--square-gap));
animation-delay: 0.2s;
} }
.square:nth-child(3) { .square:nth-child(3) {
animation-delay: .3s; left: calc(var(--start-x) + (var(--square-size) + var(--square-gap)) * 2);
animation-delay: 0.3s;
} }
</style> </style>

View File

@ -0,0 +1,44 @@
<template>
<div class="chaotic-orbit-loader"></div>
</template>
<script setup lang="ts">
// Based on Uiverse (dovatgabriel), square-styled
</script>
<style scoped>
.chaotic-orbit-loader {
--size: 24px;
--speed: 1.5s;
position: relative;
width: var(--size);
height: var(--size);
display: flex;
align-items: center;
justify-content: center;
animation: rotate calc(var(--speed) * 1.667) linear infinite;
}
.chaotic-orbit-loader::before,
.chaotic-orbit-loader::after {
content: "";
position: absolute;
width: 3px;
height: 3px;
background: var(--claude-text-tertiary);
will-change: transform;
}
.chaotic-orbit-loader::before { animation: orbit var(--speed) linear infinite; }
.chaotic-orbit-loader::after { animation: orbit var(--speed) linear calc(var(--speed) / -2) infinite; }
@keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes orbit {
0% { transform: translate(12px) scale(.74); opacity:.65; }
25% { transform: translate(0) scale(.47); opacity:.3; }
50% { transform: translate(-12px) scale(.74); opacity:.65; }
75% { transform: translate(0) scale(1); opacity:1; }
100% { transform: translate(12px) scale(.74); opacity:.65; }
}
</style>

View File

@ -0,0 +1,70 @@
<template>
<div class="dot-spinner-loader">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</template>
<script setup lang="ts">
// Based on Uiverse (abrahamcalsin), square-styled
</script>
<style scoped>
.dot-spinner-loader {
--size: 22px;
--speed: .9s;
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
width: var(--size);
height: var(--size);
}
.dot {
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 100%;
}
.dot::before {
content: "";
width: 3px;
height: 3px;
background: var(--claude-text-tertiary);
transform: scale(0);
opacity: .5;
animation: pulse calc(var(--speed) * 1.111) ease-in-out infinite;
}
.dot:nth-child(2) { transform: rotate(45deg); }
.dot:nth-child(2)::before { animation-delay: calc(var(--speed) * -0.875); }
.dot:nth-child(3) { transform: rotate(90deg); }
.dot:nth-child(3)::before { animation-delay: calc(var(--speed) * -0.75); }
.dot:nth-child(4) { transform: rotate(135deg); }
.dot:nth-child(4)::before { animation-delay: calc(var(--speed) * -0.625); }
.dot:nth-child(5) { transform: rotate(180deg); }
.dot:nth-child(5)::before { animation-delay: calc(var(--speed) * -0.5); }
.dot:nth-child(6) { transform: rotate(225deg); }
.dot:nth-child(6)::before { animation-delay: calc(var(--speed) * -0.375); }
.dot:nth-child(7) { transform: rotate(270deg); }
.dot:nth-child(7)::before { animation-delay: calc(var(--speed) * -0.25); }
.dot:nth-child(8) { transform: rotate(315deg); }
.dot:nth-child(8)::before { animation-delay: calc(var(--speed) * -0.125); }
@keyframes pulse {
0%, 100% { transform: scale(0); opacity: .5; }
50% { transform: scale(1); opacity: 1; }
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<div class="jumping-seq-loader">
<div class="sq"></div>
<div class="sq"></div>
<div class="sq"></div>
<div class="sq"></div>
<div class="sq"></div>
</div>
</template>
<script setup lang="ts">
// Based on Uiverse (EddyBel), square-styled
</script>
<style scoped>
.jumping-seq-loader {
position: relative;
display: flex;
gap: 5px;
}
.sq {
width: 3px;
height: 3px;
background: var(--claude-text-tertiary);
animation: jump 2s ease infinite;
}
.sq:nth-child(2n) { animation-delay: 300ms; }
.sq:nth-child(3n) { animation-delay: 600ms; }
@keyframes jump {
0%, 100% { transform: translateY(0); }
25% { transform: translateY(-10px) scale(0.66); }
50% { transform: translateY(0); }
75% { transform: translateY(3px) scale(0.9); }
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<div class="leap-frog-loader">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</template>
<script setup lang="ts">
// Based on Uiverse (dovatgabriel), square-styled
</script>
<style scoped>
.leap-frog-loader {
--size: 24px;
--speed: 2s;
position: relative;
width: var(--size);
height: var(--size);
display: flex;
align-items: center;
justify-content: space-between;
}
.dot {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
animation: leap var(--speed) ease infinite;
}
.dot::before {
content: "";
width: 3px;
height: 3px;
background: var(--claude-text-tertiary);
}
.dot:nth-child(2) {
transform: translateX(calc(var(--size) * 0.4));
animation-delay: calc(var(--speed) / -1.5);
}
.dot:nth-child(3) {
transform: translateX(calc(var(--size) * 0.8));
animation-delay: calc(var(--speed) / -3);
}
@keyframes leap {
0% { transform: translateX(0) rotate(0deg); }
33.333% { transform: translateX(0) rotate(180deg); }
66.666% { transform: translateX(calc(var(--size) * -0.4)) rotate(180deg); }
99.999% { transform: translateX(calc(var(--size) * -0.8)) rotate(180deg); }
100% { transform: translateX(0) rotate(0deg); }
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<div class="newton-loader">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</template>
<script setup lang="ts">
// Based on Uiverse (dovatgabriel), square-styled
</script>
<style scoped>
.newton-loader {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
}
.dot {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 25%;
transform-origin: center top;
}
.dot::after {
content: "";
position: absolute;
top: 75%;
width: 3px;
height: 3px;
background: var(--claude-text-tertiary);
}
.dot:first-child {
animation: swing 1.2s linear infinite;
}
.dot:last-child {
animation: swing2 1.2s linear infinite;
}
@keyframes swing {
0% { transform: rotate(0deg); animation-timing-function: ease-out; }
25% { transform: rotate(70deg); animation-timing-function: ease-in; }
50% { transform: rotate(0deg); animation-timing-function: linear; }
}
@keyframes swing2 {
0% { transform: rotate(0deg); animation-timing-function: linear; }
50% { transform: rotate(0deg); animation-timing-function: ease-out; }
75% { transform: rotate(-70deg); animation-timing-function: ease-in; }
}
</style>

View File

@ -0,0 +1,50 @@
<template>
<div class="orbit-loader">
<div class="orbe" style="--index: 0"></div>
<div class="orbe" style="--index: 1"></div>
<div class="orbe" style="--index: 2"></div>
<div class="orbe" style="--index: 3"></div>
<div class="orbe" style="--index: 4"></div>
</div>
</template>
<script setup lang="ts">
// Based on Uiverse (krlozCJ), square + blur, no glow
</script>
<style scoped>
.orbit-loader {
--size-loader: 24px;
--size-orbe: 3px;
width: var(--size-loader);
height: var(--size-loader);
position: relative;
transform: rotate(45deg);
}
.orbe {
position: absolute;
width: 100%;
height: 100%;
--delay: calc(var(--index) * 0.1s);
opacity: calc(1 - calc(0.18 * var(--index)));
animation: orbit 1.5s var(--delay) ease-in-out infinite;
}
.orbe::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: var(--size-orbe);
height: var(--size-orbe);
background: var(--claude-text-tertiary);
filter: blur(0.35px);
}
@keyframes orbit {
0% {}
80% { transform: rotate(360deg); }
100% { transform: rotate(360deg); }
}
</style>

View File

@ -0,0 +1,31 @@
<template>
<div class="shadow-rolling-loader"></div>
</template>
<script setup lang="ts">
// Based on Uiverse (alexruix), square-styled
</script>
<style scoped>
.shadow-rolling-loader {
--sq: 3px;
width: var(--sq);
height: var(--sq);
position: relative;
left: -10px;
color: var(--claude-text-tertiary);
animation: roll 2s linear infinite;
}
@keyframes roll {
0% { box-shadow: 0 0 transparent, 0 0 transparent, 0 0 transparent, 0 0 transparent; }
12% { box-shadow: 6px 0 currentColor, 0 0 transparent, 0 0 transparent, 0 0 transparent; }
25% { box-shadow: 12px 0 currentColor, 6px 0 currentColor, 0 0 transparent, 0 0 transparent; }
36% { box-shadow: 18px 0 currentColor, 12px 0 currentColor, 6px 0 currentColor, 0 0 transparent; }
50% { box-shadow: 24px 0 currentColor, 18px 0 currentColor, 12px 0 currentColor, 6px 0 currentColor; }
62% { box-shadow: 36px 0 transparent, 24px 0 currentColor, 18px 0 currentColor, 12px 0 currentColor; }
75% { box-shadow: 36px 0 transparent, 36px 0 transparent, 24px 0 currentColor, 18px 0 currentColor; }
87% { box-shadow: 36px 0 transparent, 36px 0 transparent, 36px 0 transparent, 24px 0 currentColor; }
100% { box-shadow: 36px 0 transparent, 36px 0 transparent, 36px 0 transparent, 36px 0 transparent; }
}
</style>

View File

@ -6,6 +6,13 @@ import BouncingSquaresLoader from './BouncingSquaresLoader.vue';
import FiveSquaresLoader from './FiveSquaresLoader.vue'; import FiveSquaresLoader from './FiveSquaresLoader.vue';
import NineGridLoader from './NineGridLoader.vue'; import NineGridLoader from './NineGridLoader.vue';
import RippleLoader from './RippleLoader.vue'; import RippleLoader from './RippleLoader.vue';
import NewtonCradleSquaresLoader from './NewtonCradleSquaresLoader.vue';
import DotSpinnerSquaresLoader from './DotSpinnerSquaresLoader.vue';
import OrbitBlurSquaresLoader from './OrbitBlurSquaresLoader.vue';
import ShadowRollingSquaresLoader from './ShadowRollingSquaresLoader.vue';
import JumpingSequenceSquaresLoader from './JumpingSequenceSquaresLoader.vue';
import ChaoticOrbitSquaresLoader from './ChaoticOrbitSquaresLoader.vue';
import LeapFrogSquaresLoader from './LeapFrogSquaresLoader.vue';
// 加载动画池 // 加载动画池
export const loaderPool: Component[] = [ export const loaderPool: Component[] = [
@ -15,7 +22,14 @@ export const loaderPool: Component[] = [
BouncingSquaresLoader, BouncingSquaresLoader,
FiveSquaresLoader, FiveSquaresLoader,
NineGridLoader, NineGridLoader,
RippleLoader RippleLoader,
NewtonCradleSquaresLoader,
DotSpinnerSquaresLoader,
OrbitBlurSquaresLoader,
ShadowRollingSquaresLoader,
JumpingSequenceSquaresLoader,
ChaoticOrbitSquaresLoader,
LeapFrogSquaresLoader
]; ];
// 至少间隔多少次后才允许重复(例如 4 表示最近 4 次不重复) // 至少间隔多少次后才允许重复(例如 4 表示最近 4 次不重复)