修复动画掉帧的问题,增加昼夜切换的过渡动画
This commit is contained in:
1 parent
94471c4c89
commit
624d0bda0f
7 files changed
+246
-131
No files matched your search
@@ -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);
|
||||
}
|
||||
|
||||
/* ── 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 {
|
||||
color: var(--text-content-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 {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
@@ -63,17 +63,21 @@ window.initInfiniteScroll = function() {
|
||||
// 1. Get new posts
|
||||
const newPosts = doc.querySelectorAll(`${containerSelector} ${itemSelector}`);
|
||||
|
||||
// 2. Append posts
|
||||
// 2. Append posts with batch layout read/write to avoid thrashing
|
||||
if (newPosts.length > 0) {
|
||||
newPosts.forEach(post => {
|
||||
// Remove UIkit scrollspy attributes to prevent interference and use manual animation
|
||||
// Phase 1: append all and set initial opacity
|
||||
newPosts.forEach(function(post) {
|
||||
post.removeAttribute('uk-scrollspy-class');
|
||||
post.style.opacity = 0;
|
||||
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.opacity = 1;
|
||||
});
|
||||
|
||||
@@ -5,6 +5,16 @@ window.initSearch = function() {
|
||||
|
||||
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() {
|
||||
if (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();
|
||||
|
||||
if (query.length === 0) {
|
||||
@@ -135,7 +145,7 @@ function initSearchModal() {
|
||||
} else {
|
||||
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 inputGroup = this.closest('.search-input-group');
|
||||
|
||||
@@ -285,19 +295,21 @@ function initInlineSearch() {
|
||||
resultsContainer.innerHTML = '<div class="search-result-item no-result">暂无相关内容</div>';
|
||||
resultsContainer.classList.add('show');
|
||||
}
|
||||
});
|
||||
}), 200);
|
||||
|
||||
// Close on click outside
|
||||
if (!window.inlineSearchClickOutside) {
|
||||
window.inlineSearchClickOutside = function(e) {
|
||||
var header = document.getElementById('essay-header');
|
||||
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');
|
||||
|
||||
if (header && searchContainer && resultsContainer && searchContainer.style.display === 'block') {
|
||||
if (!searchContainer.contains(e.target) && !header.contains(e.target) && !resultsContainer.contains(e.target)) {
|
||||
window.toggleSearchMode(false);
|
||||
}
|
||||
|
||||
if (header && resultsContainer &&
|
||||
!searchContainer.contains(e.target) &&
|
||||
!header.contains(e.target) &&
|
||||
!resultsContainer.contains(e.target)) {
|
||||
window.toggleSearchMode(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', window.inlineSearchClickOutside);
|
||||
|
||||
@@ -1,35 +1,24 @@
|
||||
function checkParent(element, classNames) {
|
||||
while (element) {
|
||||
if (element.classList && classNames.some(cn => element.classList.contains(cn))) {
|
||||
return true;
|
||||
}
|
||||
element = element.parentElement;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var excludedClasses = ['talks_comments','tiaozhuan-button', 'floating-tools']; // 添加需要排除的a标签类名class
|
||||
window.addEventListener('load', (event) => {
|
||||
document.body.addEventListener('click', function(e) {
|
||||
let target = e.target;
|
||||
while (target && target.nodeName !== 'A') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (target && target.nodeName === 'A' &&
|
||||
!checkParent(target, excludedClasses) &&
|
||||
!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) {
|
||||
var excludedClasses = ['talks_comments','tiaozhuan-button', 'floating-tools'];
|
||||
window.addEventListener('load', function() {
|
||||
document.body.addEventListener('click', function(e) {
|
||||
var target = e.target.closest('a');
|
||||
if (!target) return;
|
||||
|
||||
if (target.href.startsWith('javascript:') ||
|
||||
target.hostname === window.location.hostname ||
|
||||
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('beian.miit.gov.cn') ||
|
||||
target.href.includes('upyun.com')) return;
|
||||
|
||||
if (excludedClasses.some(function(cn) { return target.classList.contains(cn); })) return;
|
||||
|
||||
e.preventDefault();
|
||||
let encodedUrl = btoa(target.href); // Base64 encode the URL
|
||||
let url = '/tiaozhuan.html?target=' + encodedUrl;
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
});
|
||||
});
|
||||
var encodedUrl = btoa(target.href);
|
||||
window.open('/tiaozhuan.html?target=' + encodedUrl, '_blank');
|
||||
});
|
||||
});
|
||||
@@ -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 });
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -9,69 +9,13 @@ window.isMobile = function() {
|
||||
|
||||
// Toast Message Helper
|
||||
window.showToast = function(text, type) {
|
||||
type = type || 'info'; // default to info
|
||||
if (typeof Qmsg !== 'undefined') {
|
||||
// Map common types to Qmsg methods
|
||||
if (typeof Qmsg[type] === 'function') {
|
||||
Qmsg[type](text);
|
||||
if (window.Toast) {
|
||||
var t = type || 'info';
|
||||
if (typeof window.Toast[t] === 'function') {
|
||||
window.Toast[t](text);
|
||||
} 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);
|
||||
|
||||
newAvatar.addEventListener('click', function(e) {
|
||||
// Prevent if clicking on something interactive inside (unlikely for avatar)
|
||||
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
|
||||
if (window.artalk && typeof window.artalk.setDarkMode === 'function') {
|
||||
window.artalk.setDarkMode(newTheme === 'dark');
|
||||
var html = document.documentElement;
|
||||
var currentTheme = html.getAttribute('data-theme');
|
||||
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'));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{{ $iconfont := resources.Get "js/libs/iconfont.js" }}
|
||||
{{ $lightgallery := resources.Get "js/libs/lightgallery.umd.min.js" }}
|
||||
{{ $uikit := resources.Get "js/libs/uikit.min.js" }}
|
||||
{{ $easylazyload := resources.Get "js/modules/lazyload.js" }}
|
||||
{{ $viewimage := resources.Get "js/libs/view-image.min.js" }}
|
||||
@@ -56,8 +55,8 @@
|
||||
{{ $main := resources.Get "js/main.js" }}
|
||||
{{ $mypjax := resources.Get "js/modules/mypjax.js" }}
|
||||
{{ $nprogress := resources.Get "js/libs/nprogress.js" }}
|
||||
{{ $message := resources.Get "js/libs/message.min.js" }}
|
||||
{{ $tiaozhuan := resources.Get "js/modules/tiaozhuan.js" }}
|
||||
{{ $toast := resources.Get "js/modules/toast.js" }}
|
||||
{{ $reward := resources.Get "js/modules/reward.js" }}
|
||||
{{ $echarts := resources.Get "js/libs/echarts.min.js" }}
|
||||
{{ $artalk := resources.Get "js/libs/Artalk.js" }}
|
||||
@@ -73,7 +72,7 @@
|
||||
{{ $artalkModule := resources.Get "js/modules/artalk.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>
|
||||
|
||||
Reference in new issue
Block a user