94 lines
3.7 KiB
JavaScript
94 lines
3.7 KiB
JavaScript
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}`);
|
|
return res.json();
|
|
})
|
|
.then(posts => {
|
|
const postLinks = posts
|
|
.map(post => post && post.href)
|
|
.filter(href => typeof href === 'string' && href.startsWith('/posts/'));
|
|
|
|
if (postLinks.length === 0) {
|
|
console.warn('No posts available for random navigation.');
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
startCardFlight(posts);
|
|
finish(postLinks[Math.floor(Math.random() * postLinks.length)]);
|
|
})
|
|
.catch(error => {
|
|
console.error('Random post navigation failed:', error);
|
|
reset();
|
|
});
|
|
}
|