fixed 图片懒加载bug
This commit is contained in:
1 parent
5528aaf8a0
commit
1d14bca0ac
6 files changed
+216
-85
No files matched your search
@@ -44,6 +44,8 @@ ai_comment: >-
|
||||
|
||||
* **蕉太狼温馨提示**:在首页右下角加了一个小小的“蕉太狼”挂件。它会在早晨问好,深夜催睡。虽然只是简单的几行代码逻辑,但让博客多了一点“人情味”。
|
||||
|
||||
* **丝滑的图片加载**:给文章里的图片加了个“懒加载”动画。以前图片是“刷”的一下出来的,现在是当你滑到它的时候,它会优雅地从下往上浮现出来,那种顺滑的感觉,就像是德芙巧克力一样(夸张了哈),反正我是盯着看了好几遍。
|
||||
|
||||
## 效果展示
|
||||
|
||||
旧样式
|
||||
|
||||
@@ -689,36 +689,49 @@ ul {
|
||||
}
|
||||
.header-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start; /* Changed to flex-start to align with text */
|
||||
flex-direction: column; /* Stack vertically */
|
||||
align-content: flex-end; /* Push to bottom */
|
||||
justify-content: flex-end; /* Push to bottom */
|
||||
align-items: center; /* Center align horizontally */
|
||||
margin-left: 10px;
|
||||
height: 80px;
|
||||
width: calc(100vw - 140px); /* Ensure container takes available space */
|
||||
width: calc(100vw - 140px);
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.header-social {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 5px;
|
||||
width: 100%; /* Take full width to allow centering */
|
||||
justify-content: center; /* Center icons horizontally */
|
||||
gap: 12px; /* Increased gap for better spacing */
|
||||
margin-bottom: 2px; /* Add small space between rows */
|
||||
margin-right: 0;
|
||||
width: 100%;
|
||||
justify-content: center; /* Center align icons */
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.header-social a {
|
||||
margin: 0 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-bio {
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
line-height: 24px;
|
||||
min-height: 24px;
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%; /* Take full width */
|
||||
text-align: center; /* Center the text */
|
||||
width: 100%; /* Full width */
|
||||
text-align: center; /* Center align text */
|
||||
|
||||
/* Typewriter Effect Cursor */
|
||||
border-right: 2px solid transparent; /* Hidden by default, toggled by JS if needed, or use ::after */
|
||||
border-right: 2px solid transparent;
|
||||
}
|
||||
|
||||
/* Cursor Animation Class */
|
||||
@@ -743,7 +756,7 @@ ul {
|
||||
margin-bottom: 0;
|
||||
text-align: center;
|
||||
/* Reset gap for social icons as they rely on margin */
|
||||
gap: 0;
|
||||
gap: 2;
|
||||
}
|
||||
|
||||
.header-social a {
|
||||
@@ -4687,6 +4700,21 @@ html[data-theme="light"] .atk-loading-spinner svg circle {
|
||||
border-color: #333;
|
||||
}
|
||||
|
||||
/* Lazy Load Animation */
|
||||
.lazyload {
|
||||
opacity: 0;
|
||||
transform: translateY(30px); /* 稍微增加位移距离,增强上浮感 */
|
||||
filter: blur(5px); /* 减少模糊,避免过于影响性能 */
|
||||
transition: opacity 0.8s ease-out, transform 0.8s cubic-bezier(0.215, 0.61, 0.355, 1), filter 0.8s ease-out; /* 使用 ease-out 和自定义贝塞尔曲线,模拟自然惯性 */
|
||||
will-change: opacity, transform; /* 移除 filter 的 will-change 以节省内存 */
|
||||
}
|
||||
|
||||
.lazyload.lazy-loaded {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
filter: blur(0);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .ai-comment-card .atk-content p {
|
||||
color: var(--text-content-dark-color);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// 文章图片懒加载 (优先初始化,防止其他模块调用时出错)
|
||||
if (typeof lazyLoadInit === 'function') {
|
||||
lazyLoadInit({
|
||||
offsetBottom: 800,
|
||||
offsetTopm: 800,
|
||||
showTime: 1100
|
||||
offsetBottom: 50,
|
||||
offsetTopm: 50,
|
||||
showTime: 1100,
|
||||
queueInterval: 100
|
||||
});
|
||||
} else {
|
||||
console.warn('lazyLoadInit is not defined');
|
||||
|
||||
@@ -1,96 +1,143 @@
|
||||
(function() {
|
||||
// Default configuration
|
||||
var defaults = {
|
||||
showTime: 800,
|
||||
offsetBottom: 0,
|
||||
offsetTopm: 50,
|
||||
onLoadBackEnd: function (index, dom) { },
|
||||
onLoadBackStart: function (index, dom) { }
|
||||
offsetBottom: 100, // Distance from bottom to start loading
|
||||
showTime: 1100, // Transition duration (ms)
|
||||
queueInterval: 200 // Time between showing successive images in queue
|
||||
};
|
||||
|
||||
var observer;
|
||||
var imageQueue = []; // Queue for images waiting to be shown
|
||||
var isProcessingQueue = false;
|
||||
|
||||
function init(setting) {
|
||||
// Merge settings
|
||||
defaults = Object.assign({}, defaults, setting || {});
|
||||
|
||||
// Reset observer for PJAX support
|
||||
// Reset queue
|
||||
imageQueue = [];
|
||||
isProcessingQueue = false;
|
||||
|
||||
// Disconnect previous observer if exists (for PJAX)
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
observer = null;
|
||||
}
|
||||
|
||||
// Check for IntersectionObserver support
|
||||
if (!window.IntersectionObserver) {
|
||||
loadAllImages();
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize IntersectionObserver
|
||||
observer = new IntersectionObserver(function(entries, self) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
var img = entry.target;
|
||||
showImg(img);
|
||||
|
||||
// Add to queue instead of loading immediately
|
||||
addToQueue(img);
|
||||
|
||||
// Stop observing this image once it's triggered
|
||||
self.unobserve(img);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
rootMargin: defaults.offsetTopm + "px 0px " + defaults.offsetBottom + "px 0px"
|
||||
rootMargin: "0px 0px " + defaults.offsetBottom + "px 0px",
|
||||
threshold: 0.01
|
||||
});
|
||||
|
||||
processImages();
|
||||
observeImages();
|
||||
}
|
||||
|
||||
function processImages() {
|
||||
if (!observer) {
|
||||
init(); // Auto-initialize if not ready
|
||||
function addToQueue(img) {
|
||||
// Avoid duplicates
|
||||
if (!imageQueue.includes(img)) {
|
||||
imageQueue.push(img);
|
||||
processQueue();
|
||||
}
|
||||
// Expanded selector to catch more images
|
||||
var images = document.querySelectorAll('img.lazyload, img:not(.blog-img):not([data-comp])');
|
||||
}
|
||||
|
||||
function processQueue() {
|
||||
if (isProcessingQueue) return;
|
||||
if (imageQueue.length === 0) return;
|
||||
|
||||
isProcessingQueue = true;
|
||||
|
||||
var img = imageQueue.shift();
|
||||
loadImage(img, function() {
|
||||
// After this image starts showing (or fails), process next after delay
|
||||
setTimeout(function() {
|
||||
isProcessingQueue = false;
|
||||
processQueue();
|
||||
}, defaults.queueInterval);
|
||||
});
|
||||
}
|
||||
|
||||
function observeImages() {
|
||||
var images = document.querySelectorAll('img.lazyload, .post-content img:not(.lazy-loaded)');
|
||||
|
||||
images.forEach(function(img) {
|
||||
// Check if already processed or has data-lazy-src
|
||||
if (img.classList.contains('lazy-loaded')) return;
|
||||
|
||||
// Update transition duration based on settings
|
||||
if (defaults.showTime) {
|
||||
// We'll set this via inline style or rely on CSS class if it matches default
|
||||
// But for full control, let's set it
|
||||
img.style.transitionDuration = (defaults.showTime / 1000) + 's';
|
||||
}
|
||||
|
||||
if (img.getAttribute('data-lazy-src')) {
|
||||
// Already setup for lazy load, just observe
|
||||
observer.observe(img);
|
||||
} else if (img.getAttribute('src')) {
|
||||
// Convert to lazy load
|
||||
var src = img.getAttribute('src');
|
||||
img.setAttribute('data-lazy-src', src);
|
||||
img.setAttribute('src', 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==');
|
||||
observer.observe(img);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showImg(dom) {
|
||||
if (dom.getAttribute('data-comp')) return;
|
||||
dom.setAttribute('data-comp', 'true');
|
||||
|
||||
var src = dom.getAttribute('data-lazy-src');
|
||||
if (!src) return;
|
||||
function loadImage(img, callback) {
|
||||
var src = img.getAttribute('data-lazy-src');
|
||||
if (!src) {
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a temporary image to preload
|
||||
var temp = new Image();
|
||||
temp.onload = function() {
|
||||
// Prepare for transition
|
||||
dom.style.opacity = 0;
|
||||
dom.style.transition = "opacity " + (defaults.showTime / 1000) + "s ease-in-out";
|
||||
|
||||
|
||||
var onComplete = function() {
|
||||
// Swap source
|
||||
dom.setAttribute('src', src);
|
||||
img.src = src;
|
||||
|
||||
// Force reflow
|
||||
void dom.offsetWidth;
|
||||
|
||||
// Fade in
|
||||
dom.style.opacity = 1;
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
img.classList.add('lazy-loaded');
|
||||
img.removeAttribute('data-lazy-src');
|
||||
// Execute callback immediately after starting animation logic
|
||||
if (callback) callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
temp.onload = onComplete;
|
||||
temp.onerror = function() {
|
||||
// On error, show anyway
|
||||
dom.setAttribute('src', src);
|
||||
dom.style.opacity = 1;
|
||||
// Fallback
|
||||
img.src = src;
|
||||
requestAnimationFrame(() => {
|
||||
img.classList.add('lazy-loaded');
|
||||
if (callback) callback();
|
||||
});
|
||||
};
|
||||
|
||||
temp.src = src;
|
||||
}
|
||||
|
||||
function tryAgain() {
|
||||
processImages();
|
||||
function loadAllImages() {
|
||||
var images = document.querySelectorAll('img.lazyload');
|
||||
images.forEach(function(img) {
|
||||
loadImage(img);
|
||||
});
|
||||
}
|
||||
|
||||
window.lazyLoadInit = init;
|
||||
window.lazyLoadAgain = tryAgain;
|
||||
window.lazyLoadRefresh = observeImages;
|
||||
|
||||
})();
|
||||
@@ -67,9 +67,10 @@ document.addEventListener("pjax:success", function () {
|
||||
lazyLoadInit({
|
||||
// coverColor: "white",
|
||||
// coverDiv: "<div style='display: flex; justify-content: center; align-items: center; height: 200px; color: #000000;'><span>优世界·一个记录生活点滴的小站</span></div>",
|
||||
offsetBottom: 800,
|
||||
offsetTopm: 800,
|
||||
showTime: 1100
|
||||
offsetBottom: 50,
|
||||
offsetTopm: 50,
|
||||
showTime: 1100,
|
||||
queueInterval: 100
|
||||
});
|
||||
}
|
||||
// 激活文章图片灯箱
|
||||
|
||||
@@ -348,34 +348,86 @@ function initInlineSearch() {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset text content initially but keep it ready
|
||||
// Pre-fill first character to avoid empty bubble on mobile (where CSS forces visibility)
|
||||
if (text.length > 0) {
|
||||
searchHintText.textContent = text.charAt(0);
|
||||
} else {
|
||||
searchHintText.textContent = '';
|
||||
}
|
||||
|
||||
// Show wrapper initially
|
||||
setTimeout(function() {
|
||||
// Add visible class (triggers fade in on desktop)
|
||||
var typeInterval;
|
||||
var hideTimeout;
|
||||
|
||||
function runTypewriter(str, callback) {
|
||||
if (typeInterval) clearInterval(typeInterval);
|
||||
if (hideTimeout) clearTimeout(hideTimeout);
|
||||
|
||||
// Ensure visible
|
||||
searchHintWrapper.classList.add('visible');
|
||||
|
||||
// Typewriter effect
|
||||
var i = 1;
|
||||
var typeInterval = setInterval(function() {
|
||||
if (i >= text.length) {
|
||||
// Pre-fill first char
|
||||
if (str.length > 0) {
|
||||
searchHintText.textContent = str.charAt(0);
|
||||
}
|
||||
|
||||
typeInterval = setInterval(function() {
|
||||
if (i >= str.length) {
|
||||
clearInterval(typeInterval);
|
||||
|
||||
// Hide after 8 seconds (give more time to read greetings)
|
||||
setTimeout(function() {
|
||||
searchHintWrapper.classList.remove('visible');
|
||||
}, 8000);
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
searchHintText.textContent = text.substring(0, i + 1);
|
||||
searchHintText.textContent = str.substring(0, i + 1);
|
||||
i++;
|
||||
}, 100);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Check session storage for Jiaotailang tip
|
||||
if (!sessionStorage.getItem('jiaotailang_shown')) {
|
||||
// Initial Delay
|
||||
setTimeout(function() {
|
||||
// Step 1: Show Greeting
|
||||
runTypewriter(text, function() {
|
||||
// Animation Finished
|
||||
|
||||
// Step 2: Check Mobile Logic
|
||||
if (window.isMobile && window.isMobile()) {
|
||||
// Wait 2s then show secret hint
|
||||
hideTimeout = setTimeout(function() {
|
||||
var secretHint = "悄悄咪咪告诉你 点击随笔开始搜索";
|
||||
runTypewriter(secretHint, function() {
|
||||
// Finished secret hint
|
||||
sessionStorage.setItem('jiaotailang_shown', 'true');
|
||||
// Hide after 5s
|
||||
hideTimeout = setTimeout(function() {
|
||||
searchHintWrapper.classList.remove('visible');
|
||||
}, 5000);
|
||||
});
|
||||
}, 2000);
|
||||
} else {
|
||||
// Desktop: Just hide after 8s
|
||||
sessionStorage.setItem('jiaotailang_shown', 'true');
|
||||
hideTimeout = setTimeout(function() {
|
||||
searchHintWrapper.classList.remove('visible');
|
||||
}, 8000);
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
} else {
|
||||
// If already shown, set full text but don't auto-show
|
||||
searchHintText.textContent = text;
|
||||
}
|
||||
|
||||
// --- Hover Logic (Desktop) ---
|
||||
if (!window.isMobile || !window.isMobile()) {
|
||||
header.addEventListener('mouseenter', function() {
|
||||
// Stop any running animations
|
||||
if (typeInterval) clearInterval(typeInterval);
|
||||
if (hideTimeout) clearTimeout(hideTimeout);
|
||||
|
||||
searchHintWrapper.classList.add('visible');
|
||||
searchHintText.textContent = "点击随笔,开始搜索";
|
||||
});
|
||||
|
||||
header.addEventListener('mouseleave', function() {
|
||||
// Simply hide when leaving
|
||||
searchHintWrapper.classList.remove('visible');
|
||||
// Optional: Reset text to greeting in case it's shown again by some other logic?
|
||||
// But mostly it will be triggered by hover again.
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user