修复动画掉帧的问题,增加昼夜切换的过渡动画

This commit is contained in:
zqlit committed 2026-05-24 14:00:12 +08:00
1 parent c1658c0cc6
commit 0135299e55
7 files changed
+246 -131

No files matched your search

+99
View File
@@ -41,6 +41,32 @@
--card-shadow-dark: 0 18px 45px rgba(0, 0, 0, 0.9), 0 0 0 1px rgba(148, 163, 184, 0.45); --card-shadow-dark: 0 18px 45px rgba(0, 0, 0, 0.9), 0 0 0 1px rgba(148, 163, 184, 0.45);
} }
/* ── Theme switch transition ── */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.45s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
::view-transition-old(root) {
animation-name: theme-fade-out;
}
::view-transition-new(root) {
animation-name: theme-fade-in;
animation-delay: 0.04s;
}
@keyframes theme-fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes theme-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
[data-theme="dark"] body { [data-theme="dark"] body {
color: var(--text-content-dark-color); color: var(--text-content-dark-color);
background-color: var(--background-dark-color); background-color: var(--background-dark-color);
@@ -3736,6 +3762,79 @@ input[type=submit] {
} }
} }
/* ── Toast ── */
.ying-toast-container {
position: fixed;
top: 18px;
left: 50%;
transform: translateX(-50%);
z-index: 10000;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
pointer-events: none;
}
.ying-toast {
padding: 9px 18px;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
line-height: 1.3;
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.28s ease, transform 0.28s ease;
pointer-events: auto;
-webkit-backdrop-filter: blur(20px) saturate(180%);
backdrop-filter: blur(20px) saturate(180%);
will-change: transform, opacity;
}
.ying-toast--visible {
opacity: 1;
transform: translateY(0);
}
.ying-toast--success {
background: rgba(34, 197, 94, 0.88);
color: #fff;
box-shadow: 0 8px 32px rgba(34, 197, 94, 0.18);
}
.ying-toast--error {
background: rgba(239, 68, 68, 0.88);
color: #fff;
box-shadow: 0 8px 32px rgba(239, 68, 68, 0.18);
}
.ying-toast--warning {
background: rgba(245, 158, 11, 0.88);
color: #fff;
box-shadow: 0 8px 32px rgba(245, 158, 11, 0.18);
}
.ying-toast--info {
background: rgba(29, 61, 108, 0.85);
color: #f0f4fb;
box-shadow: 0 8px 32px rgba(29, 61, 108, 0.18);
}
[data-theme="dark"] .ying-toast--info {
background: rgba(159, 196, 244, 0.22);
color: #f3f7fd;
-webkit-backdrop-filter: blur(24px) saturate(180%);
backdrop-filter: blur(24px) saturate(180%);
}
@media screen and (max-width: 480px) {
.ying-toast {
padding: 8px 14px;
font-size: 12px;
border-radius: 5px;
}
}
.floating-tools a { .floating-tools a {
width: 40px; width: 40px;
height: 40px; height: 40px;
@@ -63,17 +63,21 @@ window.initInfiniteScroll = function() {
// 1. Get new posts // 1. Get new posts
const newPosts = doc.querySelectorAll(`${containerSelector} ${itemSelector}`); const newPosts = doc.querySelectorAll(`${containerSelector} ${itemSelector}`);
// 2. Append posts // 2. Append posts with batch layout read/write to avoid thrashing
if (newPosts.length > 0) { if (newPosts.length > 0) {
newPosts.forEach(post => { // Phase 1: append all and set initial opacity
// Remove UIkit scrollspy attributes to prevent interference and use manual animation newPosts.forEach(function(post) {
post.removeAttribute('uk-scrollspy-class'); post.removeAttribute('uk-scrollspy-class');
post.style.opacity = 0; post.style.opacity = 0;
container.appendChild(post); container.appendChild(post);
});
// Trigger reflow
void post.offsetWidth; // Phase 2: force single layout read for all new posts
var postArr = Array.prototype.slice.call(newPosts);
postArr.forEach(function(post) { void post.offsetWidth; });
// Phase 3: batch animate in
postArr.forEach(function(post) {
post.style.transition = 'opacity 0.5s ease'; post.style.transition = 'opacity 0.5s ease';
post.style.opacity = 1; post.style.opacity = 1;
}); });
+22 -10
View File
@@ -5,6 +5,16 @@ window.initSearch = function() {
var searchLoadingPromise = null; var searchLoadingPromise = null;
function debounce(fn, delay) {
var timer;
return function() {
var context = this;
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function() { fn.apply(context, args); }, delay);
};
}
function loadSearchIndex() { function loadSearchIndex() {
if (window.searchIndex) { if (window.searchIndex) {
return Promise.resolve(window.searchIndex); return Promise.resolve(window.searchIndex);
@@ -91,7 +101,7 @@ function initSearchModal() {
} }
}); });
searchInput.addEventListener('input', function() { searchInput.addEventListener('input', debounce(function() {
var query = this.value.trim().toLowerCase(); var query = this.value.trim().toLowerCase();
if (query.length === 0) { if (query.length === 0) {
@@ -135,7 +145,7 @@ function initSearchModal() {
} else { } else {
searchResults.innerHTML = '<p class="search-tip">未找到相关文章</p>'; searchResults.innerHTML = '<p class="search-tip">未找到相关文章</p>';
} }
}); }), 200);
} }
} }
} }
@@ -224,7 +234,7 @@ function initInlineSearch() {
}); });
} }
input.addEventListener('input', function() { input.addEventListener('input', debounce(function() {
var query = this.value.trim().toLowerCase(); var query = this.value.trim().toLowerCase();
var inputGroup = this.closest('.search-input-group'); var inputGroup = this.closest('.search-input-group');
@@ -285,19 +295,21 @@ function initInlineSearch() {
resultsContainer.innerHTML = '<div class="search-result-item no-result">暂无相关内容</div>'; resultsContainer.innerHTML = '<div class="search-result-item no-result">暂无相关内容</div>';
resultsContainer.classList.add('show'); resultsContainer.classList.add('show');
} }
}); }), 200);
// Close on click outside // Close on click outside
if (!window.inlineSearchClickOutside) { if (!window.inlineSearchClickOutside) {
window.inlineSearchClickOutside = function(e) { window.inlineSearchClickOutside = function(e) {
var header = document.getElementById('essay-header');
var searchContainer = document.getElementById('search-container'); var searchContainer = document.getElementById('search-container');
if (!searchContainer || searchContainer.style.display !== 'block') return;
var header = document.getElementById('essay-header');
var resultsContainer = document.getElementById('inline-search-results'); var resultsContainer = document.getElementById('inline-search-results');
if (header && searchContainer && resultsContainer && searchContainer.style.display === 'block') { if (header && resultsContainer &&
if (!searchContainer.contains(e.target) && !header.contains(e.target) && !resultsContainer.contains(e.target)) { !searchContainer.contains(e.target) &&
window.toggleSearchMode(false); !header.contains(e.target) &&
} !resultsContainer.contains(e.target)) {
window.toggleSearchMode(false);
} }
}; };
document.addEventListener('click', window.inlineSearchClickOutside); document.addEventListener('click', window.inlineSearchClickOutside);
+23 -34
View File
@@ -1,35 +1,24 @@
function checkParent(element, classNames) { var excludedClasses = ['talks_comments','tiaozhuan-button', 'floating-tools'];
while (element) { window.addEventListener('load', function() {
if (element.classList && classNames.some(cn => element.classList.contains(cn))) { document.body.addEventListener('click', function(e) {
return true; var target = e.target.closest('a');
} if (!target) return;
element = element.parentElement;
} if (target.href.startsWith('javascript:') ||
return false; target.hostname === window.location.hostname ||
} target.href.includes('usj.cc') ||
var excludedClasses = ['talks_comments','tiaozhuan-button', 'floating-tools']; // 添加需要排除的a标签类名class target.href.includes('douban.com') ||
window.addEventListener('load', (event) => { target.href.includes('bilibili.com') ||
document.body.addEventListener('click', function(e) { target.href.includes('github.com') ||
let target = e.target; target.href.includes('zql.im') ||
while (target && target.nodeName !== 'A') { target.href.includes('5b2.cn') ||
target = target.parentNode; target.href.includes('beian.miit.gov.cn') ||
} target.href.includes('upyun.com')) return;
if (target && target.nodeName === 'A' &&
!checkParent(target, excludedClasses) && if (excludedClasses.some(function(cn) { return target.classList.contains(cn); })) return;
!target.href.startsWith('javascript:') &&
!target.href.includes('usj.cc') &&
!target.href.includes('douban.com') &&
!target.href.includes('bilibili.com') &&
!target.href.includes('github.com') &&
!target.href.includes('zql.im') &&
!target.href.includes('5b2.cn') &&
!target.href.includes('https://beian.miit.gov.cn') &&
!target.href.includes('https://www.upyun.com') &&
target.hostname !== window.location.hostname) {
e.preventDefault(); e.preventDefault();
let encodedUrl = btoa(target.href); // Base64 encode the URL var encodedUrl = btoa(target.href);
let url = '/tiaozhuan.html?target=' + encodedUrl; window.open('/tiaozhuan.html?target=' + encodedUrl, '_blank');
window.open(url, '_blank'); });
} });
});
});
+63
View File
@@ -0,0 +1,63 @@
/**
* Toast — lightweight notification
* Usage: window.Toast.success('msg'), Toast.error('msg'), Toast.warning('msg'), Toast.info('msg')
* window.Toast.show('msg', { type: 'info', duration: 2500 })
*/
(function() {
var container = null;
var counter = 0;
function ensureContainer() {
if (container) return;
container = document.createElement('div');
container.className = 'ying-toast-container';
document.body.appendChild(container);
}
function remove(el, delay) {
setTimeout(function() {
el.classList.remove('ying-toast--visible');
el.addEventListener('transitionend', function onEnd(e) {
if (e.propertyName !== 'opacity') return;
el.removeEventListener('transitionend', onEnd);
if (el.parentNode) el.parentNode.removeChild(el);
});
}, delay);
}
window.Toast = {
show: function(text, options) {
options = options || {};
var type = options.type || 'info';
var duration = options.duration || 2200;
ensureContainer();
var el = document.createElement('div');
el.className = 'ying-toast ying-toast--' + type;
el.textContent = text;
el.setAttribute('role', 'status');
el.setAttribute('aria-live', 'polite');
container.appendChild(el);
/* Force layout then animate in */
void el.offsetWidth;
el.classList.add('ying-toast--visible');
remove(el, duration);
},
success: function(text, duration) {
this.show(text, { type: 'success', duration: duration });
},
error: function(text, duration) {
this.show(text, { type: 'error', duration: duration });
},
warning: function(text, duration) {
this.show(text, { type: 'warning', duration: duration });
},
info: function(text, duration) {
this.show(text, { type: 'info', duration: duration });
}
};
})();
+26 -77
View File
@@ -9,69 +9,13 @@ window.isMobile = function() {
// Toast Message Helper // Toast Message Helper
window.showToast = function(text, type) { window.showToast = function(text, type) {
type = type || 'info'; // default to info if (window.Toast) {
if (typeof Qmsg !== 'undefined') { var t = type || 'info';
// Map common types to Qmsg methods if (typeof window.Toast[t] === 'function') {
if (typeof Qmsg[type] === 'function') { window.Toast[t](text);
Qmsg[type](text);
} else { } else {
Qmsg.info(text); window.Toast.info(text);
} }
} else {
// Fallback custom toast
let toast = document.querySelector('.custom-toast');
if (!toast) {
toast = document.createElement('div');
toast.className = 'custom-toast';
toast.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.8);
color: white;
padding: 8px 16px;
border-radius: 4px;
z-index: 9999;
font-size: 14px;
opacity: 0;
transition: opacity 0.3s, background-color 0.3s;
pointer-events: none;
display: flex;
align-items: center;
gap: 8px;
`;
// Add dark mode support class
toast.setAttribute('data-toast', 'true');
document.body.appendChild(toast);
}
// Update toast style based on theme
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
if (isDark) {
toast.style.background = 'rgba(255, 255, 255, 0.9)';
toast.style.color = '#333';
toast.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
} else {
toast.style.background = 'rgba(0,0,0,0.8)';
toast.style.color = 'white';
toast.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
}
// Simple icon mapping for fallback
let icon = '';
if (type === 'success') icon = '✅ ';
else if (type === 'error') icon = '❌ ';
else if (type === 'warning') icon = '⚠️ ';
toast.innerText = icon + text;
toast.style.opacity = '1';
if (window.toastTimeout) clearTimeout(window.toastTimeout);
window.toastTimeout = setTimeout(() => {
toast.style.opacity = '0';
}, 2500);
} }
}; };
@@ -85,25 +29,30 @@ window.initThemeToggle = function() {
avatar.parentNode.replaceChild(newAvatar, avatar); avatar.parentNode.replaceChild(newAvatar, avatar);
newAvatar.addEventListener('click', function(e) { newAvatar.addEventListener('click', function(e) {
// Prevent if clicking on something interactive inside (unlikely for avatar)
e.preventDefault(); e.preventDefault();
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
window.showToast('已切换至 ' + (newTheme === 'dark' ? '深色' : '浅色') + '模式', 'success');
// Sync Artalk Theme var html = document.documentElement;
if (window.artalk && typeof window.artalk.setDarkMode === 'function') { var currentTheme = html.getAttribute('data-theme');
window.artalk.setDarkMode(newTheme === 'dark'); var newTheme = currentTheme === 'dark' ? 'light' : 'dark';
var apply = function() {
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
window.showToast('已切换至 ' + (newTheme === 'dark' ? '深色' : '浅色') + '模式', 'success');
if (window.artalk && typeof window.artalk.setDarkMode === 'function') {
window.artalk.setDarkMode(newTheme === 'dark');
}
window.dispatchEvent(new Event('resize'));
};
if (document.startViewTransition) {
document.startViewTransition(function() { apply(); });
} else {
apply();
} }
// Trigger generic resize event to force charts/etc to redraw if needed
window.dispatchEvent(new Event('resize'));
}); });
}; };
+2 -3
View File
@@ -48,7 +48,6 @@
</div> </div>
</div> </div>
{{ $iconfont := resources.Get "js/libs/iconfont.js" }} {{ $iconfont := resources.Get "js/libs/iconfont.js" }}
{{ $lightgallery := resources.Get "js/libs/lightgallery.umd.min.js" }}
{{ $uikit := resources.Get "js/libs/uikit.min.js" }} {{ $uikit := resources.Get "js/libs/uikit.min.js" }}
{{ $easylazyload := resources.Get "js/modules/lazyload.js" }} {{ $easylazyload := resources.Get "js/modules/lazyload.js" }}
{{ $viewimage := resources.Get "js/libs/view-image.min.js" }} {{ $viewimage := resources.Get "js/libs/view-image.min.js" }}
@@ -56,8 +55,8 @@
{{ $main := resources.Get "js/main.js" }} {{ $main := resources.Get "js/main.js" }}
{{ $mypjax := resources.Get "js/modules/mypjax.js" }} {{ $mypjax := resources.Get "js/modules/mypjax.js" }}
{{ $nprogress := resources.Get "js/libs/nprogress.js" }} {{ $nprogress := resources.Get "js/libs/nprogress.js" }}
{{ $message := resources.Get "js/libs/message.min.js" }}
{{ $tiaozhuan := resources.Get "js/modules/tiaozhuan.js" }} {{ $tiaozhuan := resources.Get "js/modules/tiaozhuan.js" }}
{{ $toast := resources.Get "js/modules/toast.js" }}
{{ $reward := resources.Get "js/modules/reward.js" }} {{ $reward := resources.Get "js/modules/reward.js" }}
{{ $echarts := resources.Get "js/libs/echarts.min.js" }} {{ $echarts := resources.Get "js/libs/echarts.min.js" }}
{{ $artalk := resources.Get "js/libs/Artalk.js" }} {{ $artalk := resources.Get "js/libs/Artalk.js" }}
@@ -73,7 +72,7 @@
{{ $artalkModule := resources.Get "js/modules/artalk.js" }} {{ $artalkModule := resources.Get "js/modules/artalk.js" }}
{{ $pjaxLib := resources.Get "js/libs/pjax.js" }} {{ $pjaxLib := resources.Get "js/libs/pjax.js" }}
{{ $scripts := slice $iconfont $lightgallery $uikit $easylazyload $viewimage $barrage $utils $loader $cache $search $floatingTools $paragraphComments $infiniteScroll $artalkModule $nprogress $pjaxLib $main $mypjax $message $tiaozhuan $reward $echarts $artalk | resources.Concat "js/bundle.js" | resources.Minify | resources.Fingerprint }} {{ $scripts := slice $iconfont $uikit $easylazyload $viewimage $barrage $utils $loader $cache $search $floatingTools $paragraphComments $infiniteScroll $artalkModule $nprogress $pjaxLib $main $mypjax $toast $tiaozhuan $reward $echarts $artalk | resources.Concat "js/bundle.js" | resources.Minify | resources.Fingerprint }}
<script defer src="{{ $scripts.RelPermalink }}"></script> <script defer src="{{ $scripts.RelPermalink }}"></script>
<script> <script>