跳转动画效果
This commit is contained in:
+73
-2
@@ -1,4 +1,72 @@
|
||||
function randomPost() {
|
||||
function randomPost(trigger) {
|
||||
const source = trigger instanceof Element
|
||||
? trigger
|
||||
: document.activeElement?.closest?.('.nav-random, [onclick*="randomPost"]');
|
||||
const overlay = document.createElement('div');
|
||||
const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
overlay.className = 'random-post-transition';
|
||||
overlay.setAttribute('role', 'status');
|
||||
overlay.setAttribute('aria-live', 'polite');
|
||||
overlay.innerHTML = `
|
||||
<div class="random-post-flight" aria-hidden="true"></div>
|
||||
<div class="random-post-transition-card">
|
||||
<i class="fa-solid fa-shuffle" aria-hidden="true"></i>
|
||||
<span>\u6b63\u5728\u62bd\u53d6\u4e00\u7bc7\u6587\u7ae0</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
source?.classList.add('is-randomizing');
|
||||
source?.setAttribute('aria-busy', 'true');
|
||||
document.body.appendChild(overlay);
|
||||
requestAnimationFrame(() => overlay.classList.add('is-visible'));
|
||||
|
||||
const finish = href => {
|
||||
const delay = reduceMotion ? 80 : 1250;
|
||||
window.setTimeout(() => {
|
||||
location.href = href;
|
||||
}, delay);
|
||||
};
|
||||
|
||||
const startCardFlight = posts => {
|
||||
if (reduceMotion) return;
|
||||
|
||||
const flight = overlay.querySelector('.random-post-flight');
|
||||
if (!flight) return;
|
||||
|
||||
const candidates = posts
|
||||
.filter(post => post && typeof post.href === 'string' && post.href.startsWith('/posts/'))
|
||||
.map(post => ({
|
||||
title: typeof post.title === 'string' && post.title.trim() ? post.title.trim() : '\u672a\u547d\u540d\u6587\u7ae0',
|
||||
meta: typeof post.category === 'string' && post.category.trim() ? post.category.trim() : '\u968f\u673a\u6587\u7ae0',
|
||||
}));
|
||||
|
||||
const pool = candidates.length ? candidates : [{ title: '\u968f\u673a\u6587\u7ae0', meta: '\u6b63\u5728\u62bd\u53d6' }];
|
||||
const count = Math.min(9, Math.max(5, pool.length));
|
||||
|
||||
for (let index = 0; index < count; index += 1) {
|
||||
const post = pool[Math.floor(Math.random() * pool.length)];
|
||||
const card = document.createElement('div');
|
||||
card.className = 'random-post-flying-card';
|
||||
card.style.setProperty('--flight-delay', `${index * 0.11}s`);
|
||||
card.style.setProperty('--flight-y', `${-42 + Math.random() * 84}px`);
|
||||
card.style.setProperty('--flight-rotate', `${-8 + Math.random() * 16}deg`);
|
||||
const meta = document.createElement('small');
|
||||
const title = document.createElement('strong');
|
||||
meta.textContent = post.meta;
|
||||
title.textContent = post.title;
|
||||
card.append(meta, title);
|
||||
flight.appendChild(card);
|
||||
}
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
source?.classList.remove('is-randomizing');
|
||||
source?.removeAttribute('aria-busy');
|
||||
overlay.classList.remove('is-visible');
|
||||
window.setTimeout(() => overlay.remove(), reduceMotion ? 0 : 180);
|
||||
};
|
||||
|
||||
fetch('/api/posts.json')
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error(`Failed to load posts: ${res.status}`);
|
||||
@@ -11,12 +79,15 @@ function randomPost() {
|
||||
|
||||
if (postLinks.length === 0) {
|
||||
console.warn('No posts available for random navigation.');
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
location.href = postLinks[Math.floor(Math.random() * postLinks.length)];
|
||||
startCardFlight(posts);
|
||||
finish(postLinks[Math.floor(Math.random() * postLinks.length)]);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Random post navigation failed:', error);
|
||||
reset();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@
|
||||
<i class="fa-solid fa-hashtag" aria-hidden="true"></i>
|
||||
<span>标签</span>
|
||||
</a>
|
||||
<button type="button" data-context-action="random-post">
|
||||
<i class="fa-solid fa-shuffle" aria-hidden="true"></i>
|
||||
<span>随机文章</span>
|
||||
</button>
|
||||
<button type="button" data-context-action="comments" data-comments-item>
|
||||
<i class="fa-regular fa-comment-dots" aria-hidden="true"></i>
|
||||
<span>跳转评论区</span>
|
||||
@@ -239,6 +243,7 @@
|
||||
if (action === 'copy-image') copyImage();
|
||||
if (action === 'open-image') openImage();
|
||||
if (action === 'copy-image-url') copyText(new URL(getImageUrl(), window.location.href).toString());
|
||||
if (action === 'random-post' && typeof window.randomPost === 'function') window.randomPost(actionTarget);
|
||||
if (action === 'comments') jumpToComments();
|
||||
if (action === 'print') window.print();
|
||||
});
|
||||
|
||||
@@ -190,6 +190,41 @@ a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-random {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav-random::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 3px;
|
||||
border-radius: inherit;
|
||||
background:
|
||||
linear-gradient(120deg, transparent 8%, rgba(255, 255, 255, 0.82) 34%, transparent 58%);
|
||||
opacity: 0;
|
||||
transform: translateX(-135%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.nav-random.is-randomizing {
|
||||
color: var(--accent);
|
||||
border-color: rgba(15, 118, 110, 0.22);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.76),
|
||||
0 10px 24px rgba(15, 118, 110, 0.16);
|
||||
}
|
||||
|
||||
.nav-random.is-randomizing::after {
|
||||
opacity: 1;
|
||||
animation: random-button-sheen 0.72s ease both;
|
||||
}
|
||||
|
||||
.nav-random.is-randomizing .nav-icon {
|
||||
animation: random-button-spin 0.72s cubic-bezier(0.2, 0.8, 0.2, 1) both;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
display: inline-grid;
|
||||
width: 16px;
|
||||
@@ -356,6 +391,136 @@ a {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.random-post-transition {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1200;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: var(--page-gutter);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
background: rgba(247, 245, 239, 0.34);
|
||||
backdrop-filter: blur(6px) saturate(145%);
|
||||
-webkit-backdrop-filter: blur(6px) saturate(145%);
|
||||
transition: opacity 0.18s ease;
|
||||
}
|
||||
|
||||
.random-post-transition.is-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.random-post-flight {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.random-post-flying-card {
|
||||
position: absolute;
|
||||
top: calc(50% + var(--flight-y));
|
||||
left: 50%;
|
||||
width: min(260px, 68vw);
|
||||
border: 1px solid rgba(255, 255, 255, 0.64);
|
||||
border-radius: 8px;
|
||||
padding: 12px 14px;
|
||||
color: #2f4358;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.88), rgba(238, 250, 246, 0.68)),
|
||||
rgba(255, 255, 255, 0.74);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.86),
|
||||
0 18px 48px rgba(44, 55, 63, 0.16);
|
||||
opacity: 0;
|
||||
transform: translate3d(-50vw, -50%, 0) rotate(var(--flight-rotate)) scale(0.82);
|
||||
animation: random-card-fly 1.16s cubic-bezier(0.16, 0.84, 0.28, 1) var(--flight-delay) both;
|
||||
}
|
||||
|
||||
.random-post-flying-card small,
|
||||
.random-post-flying-card strong {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.random-post-flying-card small {
|
||||
color: var(--muted);
|
||||
font-size: 0.74rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.random-post-flying-card strong {
|
||||
margin-top: 4px;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.random-post-transition-card {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: min(280px, 100%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.66);
|
||||
border-radius: 999px;
|
||||
padding: 16px 22px;
|
||||
color: var(--accent);
|
||||
font-size: 1rem;
|
||||
font-weight: 750;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.84), rgba(238, 250, 246, 0.62)),
|
||||
rgba(255, 255, 255, 0.7);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.88),
|
||||
0 24px 64px rgba(44, 55, 63, 0.2);
|
||||
transform: translateY(8px) scale(0.96);
|
||||
transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
|
||||
.random-post-transition.is-visible .random-post-transition-card {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
.random-post-transition-card::before,
|
||||
.random-post-transition-card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -10px;
|
||||
border: 1px solid rgba(15, 118, 110, 0.24);
|
||||
border-radius: inherit;
|
||||
opacity: 0;
|
||||
animation: random-card-pulse 1.1s ease-out infinite;
|
||||
}
|
||||
|
||||
.random-post-transition-card::after {
|
||||
animation-delay: 0.28s;
|
||||
}
|
||||
|
||||
.random-post-transition-card i {
|
||||
display: inline-grid;
|
||||
width: 22px;
|
||||
min-width: 22px;
|
||||
height: 22px;
|
||||
place-items: center;
|
||||
animation: random-button-spin 0.9s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
.random-post-transition-card span {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.random-post-transition-card span::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 1.25em;
|
||||
text-align: left;
|
||||
animation: random-loading-dots 1s steps(4, end) infinite;
|
||||
}
|
||||
|
||||
.has-control-console {
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -588,6 +753,45 @@ a {
|
||||
0 8px 18px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .nav-random.is-randomizing {
|
||||
color: var(--accent);
|
||||
border-color: rgba(139, 224, 213, 0.32);
|
||||
background: rgba(125, 211, 199, 0.14);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.12),
|
||||
0 10px 24px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .random-post-transition {
|
||||
background: rgba(3, 10, 12, 0.38);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .random-post-transition-card {
|
||||
border-color: rgba(166, 184, 194, 0.22);
|
||||
background:
|
||||
linear-gradient(135deg, rgba(25, 35, 42, 0.9), rgba(10, 17, 22, 0.76)),
|
||||
rgba(14, 22, 28, 0.84);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1),
|
||||
0 28px 74px rgba(0, 0, 0, 0.42);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .random-post-transition-card::before,
|
||||
:root[data-theme='dark'] .random-post-transition-card::after {
|
||||
border-color: rgba(139, 224, 213, 0.26);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .random-post-flying-card {
|
||||
border-color: rgba(166, 184, 194, 0.2);
|
||||
color: #edf7f5;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(25, 35, 42, 0.88), rgba(10, 17, 22, 0.72)),
|
||||
rgba(14, 22, 28, 0.78);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1),
|
||||
0 22px 54px rgba(0, 0, 0, 0.34);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .control-console-backdrop {
|
||||
background: rgba(3, 10, 12, 0.58);
|
||||
}
|
||||
@@ -4514,3 +4718,99 @@ meting-js {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes random-button-spin {
|
||||
0% {
|
||||
transform: rotate(0deg) scale(1);
|
||||
}
|
||||
|
||||
55% {
|
||||
transform: rotate(210deg) scale(1.18);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes random-button-sheen {
|
||||
0% {
|
||||
transform: translateX(-135%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(135%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes random-card-pulse {
|
||||
0% {
|
||||
opacity: 0.55;
|
||||
transform: scale(0.94);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1.16);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes random-card-fly {
|
||||
0% {
|
||||
opacity: 0;
|
||||
filter: blur(2px);
|
||||
transform: translate3d(-58vw, -50%, 0) rotate(calc(var(--flight-rotate) - 8deg)) scale(0.74);
|
||||
}
|
||||
|
||||
22% {
|
||||
opacity: 0.88;
|
||||
filter: blur(0);
|
||||
}
|
||||
|
||||
58% {
|
||||
opacity: 0.92;
|
||||
transform: translate3d(-50%, -50%, 0) rotate(var(--flight-rotate)) scale(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
filter: blur(2px);
|
||||
transform: translate3d(58vw, -50%, 0) rotate(calc(var(--flight-rotate) + 10deg)) scale(0.78);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes random-loading-dots {
|
||||
0% {
|
||||
content: "";
|
||||
}
|
||||
|
||||
25% {
|
||||
content: ".";
|
||||
}
|
||||
|
||||
50% {
|
||||
content: "..";
|
||||
}
|
||||
|
||||
75%,
|
||||
100% {
|
||||
content: "...";
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.nav-random.is-randomizing::after,
|
||||
.nav-random.is-randomizing .nav-icon,
|
||||
.random-post-flying-card,
|
||||
.random-post-transition-card::before,
|
||||
.random-post-transition-card::after,
|
||||
.random-post-transition-card i,
|
||||
.random-post-transition-card span::after {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.random-post-transition,
|
||||
.random-post-transition-card {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user