fixed 友圈掉帧问题以及评论区边框样式bug

This commit is contained in:
Vaica committed 2026-01-22 12:54:52 +08:00
1 parent c70835138f
commit 40e1e60bc5
2 files changed
+388 -347

No files matched your search

+382 -343
View File
@@ -1,399 +1,438 @@
{{ define "main" }} {{ define "main" }}
<div id="pjax-container"> <div id="pjax-container">
<div class="post-content" itemprop="articleBody"> <div class="post-content" itemprop="articleBody">
{{ .Content }} {{ .Content }}
<div id="friend-circle-container" class="friend-circle-list"> <div id="friend-circle-container" class="friend-circle-list">
<div class="loading-container"> <div class="loading-container">
<div class="loading-spinner"></div> <div class="loading-spinner"></div>
<p>正在探索朋友们的新动态...</p> <p>正在探索朋友们的新动态...</p>
</div> </div>
</div>
</div> </div>
</div>
<style> <style>
/* Container Layout */ /* Container Layout */
.friend-circle-list { .friend-circle-list {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 12px; /* Tighter gap to make cards wider */ gap: 12px; /* Tighter gap to make cards wider */
margin-top: 20px; margin-top: 20px;
padding: 0; /* Remove padding to maximize width */ padding: 0; /* Remove padding to maximize width */
width: 100%; width: 100%;
} }
/* Mobile Layout */ /* Mobile Layout */
@media (max-width: 768px) { @media (max-width: 768px) {
.friend-circle-list { .friend-circle-list {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 10px; gap: 10px;
} }
} }
/* Card Style - Refined based on User Request */ /* Card Style - Refined based on User Request */
.fc-item { .fc-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 20px; padding: 20px;
border-radius: 10px; border-radius: 10px;
position: relative; position: relative;
height: 160px; height: 160px;
justify-content: space-between; justify-content: space-between;
background: rgba(255, 255, 255, 0.4); background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(16px) saturate(180%); backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%); -webkit-backdrop-filter: blur(16px) saturate(180%);
border: 1px solid #111827; border: 1px solid #111827;
box-shadow: none; box-shadow: none;
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); transition:
overflow: hidden; transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
opacity: 0; box-shadow 0.3s ease,
animation: ukFadeInUp 0.8s cubic-bezier(0.165, 0.84, 0.44, 1) forwards; background-color 0.3s ease,
text-decoration: none; border-color 0.3s ease;
} will-change: transform;
overflow: hidden;
opacity: 0;
animation: ukFadeInUp 0.6s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
text-decoration: none;
transform: translateZ(0); /* Enable GPU acceleration */
}
/* Light Mode Override - if theme is light, keep it readable but consistent layout */ .friend-circle-list a {
/* Assuming the user wants the dark look as shown in the reference, we prioritize that feel text-decoration: none;
}
/* Light Mode Override - if theme is light, keep it readable but consistent layout */
/* Assuming the user wants the dark look as shown in the reference, we prioritize that feel
or make it adapt. Let's make it adapt but keep the structure. */ or make it adapt. Let's make it adapt but keep the structure. */
:root { :root {
/* --fc-bg: rgba(255, 255, 255, 0.8); */ /* --fc-bg: rgba(255, 255, 255, 0.8); */
--fc-text: #333; --fc-text: #333;
--fc-meta-bg: rgba(0, 0, 0, 0.05); --fc-meta-bg: rgba(0, 0, 0, 0.05);
/* --fc-border: rgba(0, 0, 0, 0.05); */ /* --fc-border: rgba(0, 0, 0, 0.05); */
} }
[data-theme="dark"] { [data-theme="dark"] {
/* --fc-bg: rgba(35, 35, 35, 0.8); */ /* --fc-bg: rgba(35, 35, 35, 0.8); */
--fc-text: #fff; --fc-text: #fff;
--fc-meta-bg: rgba(255, 255, 255, 0.1); --fc-meta-bg: rgba(255, 255, 255, 0.1);
/* --fc-border: rgba(255, 255, 255, 0.1); */ /* --fc-border: rgba(255, 255, 255, 0.1); */
} }
/* /*
.fc-item { .fc-item {
background: var(--fc-bg); background: var(--fc-bg);
border-color: var(--fc-border); border-color: var(--fc-border);
} }
*/ */
/* UK Style Animation & Hover */ /* UK Style Animation & Hover */
.fc-item:hover { /* 优化后的 hover 效果 */
transform: translateY(-8px) scale(1.02); .fc-item:hover {
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.12); /* 位移+缩放:微调数值,更克制不夸张 */
background: rgba(255, 255, 255, 0.6); transform: translateY(-6px) scale(1.015);
border-color: #000; /* 阴影:分层阴影,更有立体感(主阴影+柔和光晕) */
z-index: 10; box-shadow:
} 0 12px 28px rgba(0, 0, 0, 0.1),
/* 主阴影:缩小范围,降低不透明度 */ 0 2px 8px rgba(0, 0, 0, 0.06); /* 次级光晕:增加柔和感 */
/* 背景:轻微提亮,保留通透感 */
background: rgba(255, 255, 255, 0.7);
/* 边框:轻微加亮,避免纯黑太突兀 */
border-color: #1a1a1a;
/* 层级:保持原有值 */
z-index: 10;
/* 光标:交互提示 */
cursor: pointer;
}
@keyframes ukFadeInUp { /* 可选:点击回弹效果,增强交互反馈 */
0% { .fc-item:active {
opacity: 0; transform: translateY(-3px) scale(1.005);
transform: translateY(40px) scale(0.95); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
} transition: all 0.15s ease;
100% { }
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* Top: Title */ @keyframes ukFadeInUp {
.fc-title { 0% {
font-size: 18px; opacity: 0;
font-weight: 700; transform: translateY(40px) scale(0.95);
color: var(--fc-text); }
text-decoration: none; 100% {
line-height: 1.4; opacity: 1;
z-index: 2; transform: translateY(0) scale(1);
display: -webkit-box; }
-webkit-line-clamp: 2; }
-webkit-box-orient: vertical;
overflow: hidden;
margin-right: 60px; /* Space for the image */
}
.fc-item:hover .fc-title { /* Top: Title */
color: #3498db; .fc-title {
} font-size: 18px;
font-weight: 700;
color: var(--fc-text);
text-decoration: none;
line-height: 1.4;
z-index: 2;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin-right: 60px; /* Space for the image */
}
/* Background Image (Decorative) */ .fc-item:hover .fc-title {
.fc-bg-image { color: #000000;
position: absolute; }
right: -20px;
bottom: -20px;
width: 140px;
height: 140px;
border-radius: 50%;
object-fit: cover;
opacity: 0.15;
transition: all 0.5s ease;
z-index: 0;
pointer-events: none;
filter: grayscale(20%);
}
.fc-item:hover .fc-bg-image { /* Background Image (Decorative) */
opacity: 0.3; .fc-bg-image {
transform: scale(1.1) rotate(5deg); position: absolute;
filter: grayscale(0%); right: -20px;
} bottom: -20px;
width: 140px;
height: 140px;
border-radius: 50%;
object-fit: cover;
opacity: 0.15;
transition: all 0.5s ease;
z-index: 0;
pointer-events: none;
filter: grayscale(20%);
}
/* Bottom Row: Author Pill & Date */ .fc-item:hover .fc-bg-image {
.fc-bottom { opacity: 0.3;
display: flex; transform: scale(1.1) rotate(deg);
justify-content: space-between; filter: grayscale(0%);
align-items: flex-end; }
z-index: 2;
}
/* Author Pill */ /* Bottom Row: Author Pill & Date */
.fc-author-pill { .fc-bottom {
display: flex; display: flex;
align-items: center; justify-content: space-between;
background: var(--fc-meta-bg); align-items: flex-end;
padding: 4px 10px 4px 4px; z-index: 2;
border-radius: 20px; }
transition: background 0.3s;
}
.fc-item:hover .fc-author-pill { /* Author Pill */
background: rgba(52, 152, 219, 0.1); .fc-author-pill {
} display: flex;
align-items: center;
background: var(--fc-meta-bg);
padding: 4px 10px 4px 4px;
border-radius: 20px;
transition: background 0.3s;
}
.fc-avatar { .fc-item:hover .fc-author-pill {
width: 28px; background: rgba(52, 152, 219, 0.1);
height: 28px; }
border-radius: 50%;
object-fit: cover;
margin-right: 8px;
border: 1px solid rgba(255,255,255,0.2);
}
.fc-author-name { .fc-avatar {
font-size: 13px; width: 28px;
font-weight: 600; height: 28px;
color: var(--fc-text); border-radius: 50%;
opacity: 0.9; object-fit: cover;
} margin-right: 8px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* Date */ .fc-author-name {
.fc-date { font-size: 13px;
font-size: 12px; font-weight: 600;
color: var(--fc-text); color: var(--fc-text);
opacity: 0.6; opacity: 0.9;
display: flex; }
align-items: center;
}
.fc-date i { /* Date */
margin-right: 4px; .fc-date {
font-size: 11px; font-size: 12px;
} color: var(--fc-text);
opacity: 0.6;
display: flex;
align-items: center;
}
/* FontAwesome fallback if not available, simple calendar icon via CSS or Unicode */ .fc-date i {
.fc-icon-calendar::before { margin-right: 4px;
content: '\f133'; font-size: 11px;
font-family: "Font Awesome 5 Free", "FontAwesome"; }
font-weight: 900;
}
/* Loading Animation */ /* FontAwesome fallback if not available, simple calendar icon via CSS or Unicode */
.loading-container { .fc-icon-calendar::before {
grid-column: 1 / -1; content: "\f133";
display: flex; font-family: "Font Awesome 5 Free", "FontAwesome";
flex-direction: column; font-weight: 900;
align-items: center; }
padding: 60px;
color: #888;
}
.loading-spinner { /* Loading Animation */
width: 40px; .loading-container {
height: 40px; grid-column: 1 / -1;
border: 3px solid rgba(127,127,127,0.2); display: flex;
border-radius: 50%; flex-direction: column;
border-top-color: #3498db; align-items: center;
animation: spin 1s ease-in-out infinite; padding: 60px;
margin-bottom: 15px; color: #888;
} }
/* Dark Mode Specific Adjustments to match the reference exactly */ .loading-spinner {
[data-theme="dark"] .fc-item { width: 40px;
background: rgba(35, 35, 35, 0.65); /* More transparent for glass effect */ height: 40px;
box-shadow: none; border: 3px solid rgba(127, 127, 127, 0.2);
border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 50%;
} border-top-color: #3498db;
animation: spin 1s ease-in-out infinite;
margin-bottom: 15px;
}
[data-theme="dark"] .fc-author-pill { /* Dark Mode Specific Adjustments to match the reference exactly */
background: #1a1a1a; [data-theme="dark"] .fc-item {
border: 1px solid #333; background: rgba(
} 35,
35,
35,
0.65
); /* More transparent for glass effect */
box-shadow: none;
border: 1px solid rgba(255, 255, 255, 0.1);
}
[data-theme="dark"] .fc-bg-image { [data-theme="dark"] .fc-author-pill {
opacity: 0.2; background: #1a1a1a;
} border: 1px solid #333;
}
/* Removed unused fadeInUp to keep clean, utilizing ukFadeInUp instead */ [data-theme="dark"] .fc-bg-image {
opacity: 0.2;
}
@keyframes spin { /* Removed unused fadeInUp to keep clean, utilizing ukFadeInUp instead */
to { transform: rotate(360deg); }
}
/* Load More Button */
.fc-load-more-btn {
grid-column: 1 / -1;
padding: 12px 30px;
margin: 30px auto 10px;
background: #000; /* Black background */
border: 1px solid rgba(255,255,255,0.1);
border-radius: 30px;
color: #fff;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.fc-load-more-btn:hover { @keyframes spin {
background: #333; to {
color: #fff; transform: rotate(360deg);
transform: translateY(-2px); }
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); }
} /* Load More Button */
.fc-load-more-btn {
grid-column: 1 / -1;
padding: 12px 30px;
margin: 30px auto 10px;
background: #000; /* Black background */
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 30px;
color: #fff;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
[data-theme="dark"] .fc-load-more-btn { .fc-load-more-btn:hover {
background: #000; background: #333;
border-color: rgba(255, 255, 255, 0.2); color: #fff;
} transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .fc-load-more-btn:hover { [data-theme="dark"] .fc-load-more-btn {
background: #222; background: #000;
} border-color: rgba(255, 255, 255, 0.2);
}
/* Artalk Dark Mode Adjustment */ [data-theme="dark"] .fc-load-more-btn:hover {
[data-theme="dark"] .artalk .atk-main-editor { background: #222;
border: 1px solid rgba(255, 255, 255, 0.1) !important; }
}
</style>
<script> /* Artalk Dark Mode Adjustment */
function timeAgo(dateStr) { [data-theme="dark"] .artalk .atk-main-editor {
const date = new Date(dateStr); border: 1px solid rgba(255, 255, 255, 0.1) !important;
const year = date.getFullYear(); }
const month = String(date.getMonth() + 1).padStart(2, '0'); </style>
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
let allPosts = []; <script>
let currentIndex = 0; // 使用全局对象管理友圈逻辑
const BATCH_SIZE = 10; window.FriendCircle = {
allPosts: [],
currentIndex: 0,
BATCH_SIZE: 10,
function renderPosts(posts) { timeAgo: function (dateStr) {
const container = document.getElementById('friend-circle-container'); const date = new Date(dateStr);
// Check if button exists, if so, remove it temporarily to append items before it? const year = date.getFullYear();
// Or just insertAdjacentHTML 'beforeend' and then append button. const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
},
// Remove existing button if any renderPosts: function (posts) {
const existingBtn = document.getElementById('fc-load-more-btn'); const container = document.getElementById("friend-circle-container");
if (existingBtn) existingBtn.remove(); if (!container) return;
let html = ''; const existingBtn = document.getElementById("fc-load-more-btn");
posts.forEach((post, index) => { if (existingBtn) existingBtn.remove();
const dateStr = timeAgo(post.date);
// Stagger delay based on index in this batch
const delay = index * 0.05;
const bgImageSrc = post.image || post.avatar; let html = "";
posts.forEach((post, index) => {
const dateStr = this.timeAgo(post.date);
// 减少延迟时间,避免动画积压
const delay = index * 0.05;
const bgImageSrc = post.image || post.avatar;
html += ` html += `
<a href="${post.link}" target="_blank" class="fc-item" style="animation-delay: ${delay}s"> <a href="${post.link}" target="_blank" class="fc-item" style="animation-delay: ${delay}s">
<div class="fc-title" title="${post.title}">${post.title}</div> <div class="fc-title" title="${post.title}">${post.title}</div>
<img src="${bgImageSrc}" class="fc-bg-image" loading="lazy" onerror="this.style.opacity=0">
<img src="${bgImageSrc}" class="fc-bg-image" loading="lazy" onerror="this.style.opacity=0"> <div class="fc-bottom">
<div class="fc-author-pill">
<div class="fc-bottom"> <img src="${post.avatar}" class="fc-avatar" loading="lazy" onerror="this.src='https://cravatar.cn/avatar/default?s=50'">
<div class="fc-author-pill"> <span class="fc-author-name">${post.author}</span>
<img src="${post.avatar}" class="fc-avatar" loading="lazy" onerror="this.src='https://cravatar.cn/avatar/default?s=50'"> </div>
<span class="fc-author-name">${post.author}</span> <div class="fc-date">
<i class="ri-calendar-line"></i> ${dateStr}
</div>
</div> </div>
<div class="fc-date"> </a>
<i class="ri-calendar-line"></i> ${dateStr} `;
</div> });
</div>
</a>
`;
});
container.insertAdjacentHTML('beforeend', html); container.insertAdjacentHTML("beforeend", html);
// Check if we need a load more button if (this.currentIndex < this.allPosts.length) {
if (currentIndex < allPosts.length) { const btn = document.createElement("button");
const btn = document.createElement('button'); btn.id = "fc-load-more-btn";
btn.id = 'fc-load-more-btn'; btn.className = "fc-load-more-btn";
btn.className = 'fc-load-more-btn'; btn.innerText = "加载更多";
btn.innerText = '加载更多'; btn.onclick = () => this.loadMore();
btn.onclick = loadMore; container.appendChild(btn);
container.appendChild(btn); } else {
} else { if (window.fcUpdateTime) {
// Add update time footer if all loaded const footer = document.createElement("div");
// We need to retrieve updateTime from somewhere, or just don't show it repeatedly. footer.style.cssText =
// Let's store updateTime globally or just append it at the very end. "grid-column: 1 / -1; text-align: center; margin-top: 20px; font-size: 12px; color: #999; opacity: 0.6;";
if (window.fcUpdateTime) { footer.innerText = `最近更新: ${window.fcUpdateTime}`;
const footer = document.createElement('div'); container.appendChild(footer);
footer.style.cssText = "grid-column: 1 / -1; text-align: center; margin-top: 20px; font-size: 12px; color: #999; opacity: 0.6;"; }
footer.innerText = `最近更新: ${window.fcUpdateTime}`; }
container.appendChild(footer); },
}
loadMore: function () {
const nextBatch = this.allPosts.slice(
this.currentIndex,
this.currentIndex + this.BATCH_SIZE,
);
this.currentIndex += this.BATCH_SIZE;
// 使用 requestAnimationFrame 优化渲染性能
requestAnimationFrame(() => {
this.renderPosts(nextBatch);
});
},
init: function () {
const container = document.getElementById("friend-circle-container");
if (!container) return;
// 防止重复初始化导致掉帧
if (this.isFetching) return;
this.isFetching = true;
fetch("/json/friend_circle_data.json?t=" + new Date().getTime())
.then((res) => {
if (!res.ok) throw new Error("Network response was not ok");
return res.json();
})
.then((data) => {
if (!data.posts || data.posts.length === 0) {
container.innerHTML =
'<div class="empty" style="grid-column: 1/-1; text-align:center; padding:20px;">暂无动态</div>';
return;
} }
}
function loadMore() { this.allPosts = data.posts;
const nextBatch = allPosts.slice(currentIndex, currentIndex + BATCH_SIZE); window.fcUpdateTime = new Date(data.updated).toLocaleString();
currentIndex += BATCH_SIZE; this.currentIndex = 0;
renderPosts(nextBatch); container.innerHTML = "";
} this.loadMore();
})
.catch((err) => {
console.error("Friend Circle Error:", err);
container.innerHTML =
'<div class="error" style="grid-column: 1/-1; text-align:center; padding:20px; color:#d9534f;">加载失败,请稍后刷新重试</div>';
})
.finally(() => {
this.isFetching = false;
});
},
};
function loadFriendCircle() { // 移除多余的 pjax:complete 监听,因为脚本在 PJAX 容器内,每次导航都会重新执行
const container = document.getElementById('friend-circle-container'); // 避免了重复触发 init 导致的性能问题
if (!container) return; // Prevent errors if container not found
// Reset state if needed, though loadMore will handle rendering // 立即执行初始化
// But we need to make sure we don't re-fetch if we don't have to? if (document.readyState === "loading") {
// Actually PJAX reload means we are on a "new" page state, so re-fetching is safer to ensure data is fresh and DOM is rebuilt. document.addEventListener("DOMContentLoaded", () =>
window.FriendCircle.init(),
fetch('/json/friend_circle_data.json?t=' + new Date().getTime()) );
.then(res => { } else {
if (!res.ok) throw new Error('Network response was not ok'); window.FriendCircle.init();
return res.json(); }
}) </script>
.then(data => {
if (!data.posts || data.posts.length === 0) {
container.innerHTML = '<div class="empty" style="grid-column: 1/-1; text-align:center; padding:20px;">暂无动态</div>';
return;
}
allPosts = data.posts;
window.fcUpdateTime = new Date(data.updated).toLocaleString();
currentIndex = 0;
// Clear loading spinner
container.innerHTML = '';
loadMore(); // Load first batch
})
.catch(err => {
console.error('Friend Circle Error:', err);
container.innerHTML = '<div class="error" style="grid-column: 1/-1; text-align:center; padding:20px; color:#d9534f;">加载失败,请稍后刷新重试</div>';
});
}
// Initial load
document.addEventListener('DOMContentLoaded', loadFriendCircle);
// PJAX support - listen for pjax:complete event
document.addEventListener('pjax:complete', loadFriendCircle);
if (document.readyState === 'interactive' || document.readyState === 'complete') {
loadFriendCircle();
}
</script>
</div> </div>
{{ end }} {{ end }}
+6 -4
View File
@@ -2304,15 +2304,17 @@ input[type=submit] {
stroke: var(--text-highlight-color); stroke: var(--text-highlight-color);
} }
/* 通用样式 */
.artalk .atk-main-editor input.atk-nick, .artalk .atk-main-editor input.atk-nick,
.artalk .atk-main-editor input.atk-email, .artalk .atk-main-editor input.atk-email,
.artalk .atk-main-editor input.atk-link { .artalk .atk-main-editor input.atk-link {
color: var(--text-content-color)!important; color: var(--text-content-color)!important;
} }
[data-theme="dark"] .artalk .atk-main-editor input.atk-nick, /* 暗黑模式样式:重复类选择器,最大化权重 */
[data-theme="dark"] .artalk .atk-main-editor input.atk-email, [data-theme="dark"] .artalk.artalk .atk-main-editor input.atk-nick,
[data-theme="dark"] .artalk .atk-main-editor input.atk-link { [data-theme="dark"] .artalk.artalk .atk-main-editor input.atk-email,
[data-theme="dark"] .artalk.artalk .atk-main-editor input.atk-link {
color: var(--text-content-dark-color)!important; color: var(--text-content-dark-color)!important;
} }
@@ -2429,7 +2431,7 @@ img[atk-emoticon] {
color: var(--text-content-dark-color) color: var(--text-content-dark-color)
} }
.atk-main-editor { .artalk .atk-main-editor {
background: 0 0!important; background: 0 0!important;
border: 1px solid var(--frame-dark-color)!important border: 1px solid var(--frame-dark-color)!important
} }