优化一下博客的性能
This commit is contained in:
1 parent
15421ff0ac
commit
427b17d7a0
24 files changed
+2045
-148
No files matched your search
@@ -0,0 +1,202 @@
|
||||
window.BrowserCache = (function () {
|
||||
var PREFIX = 'ying-cache:';
|
||||
var DEBUG_KEY = PREFIX + 'debug';
|
||||
var DEBUG = detectDebug();
|
||||
|
||||
function detectDebug() {
|
||||
try {
|
||||
if (typeof window === 'undefined' || !window.location) return false;
|
||||
var loc = window.location;
|
||||
var host = loc.hostname || '';
|
||||
var protocol = loc.protocol || '';
|
||||
var search = loc.search || '';
|
||||
|
||||
// Explicit URL override wins over everything.
|
||||
if (/(^|[?&])debug-cache=1(&|$)/.test(search)) return true;
|
||||
if (/(^|[?&])debug-cache=0(&|$)/.test(search)) return false;
|
||||
|
||||
// Persisted toggle (set via setDebug) wins over auto-detection.
|
||||
try {
|
||||
var stored = localStorage.getItem(DEBUG_KEY);
|
||||
if (stored === '1') return true;
|
||||
if (stored === '0') return false;
|
||||
} catch (e) {}
|
||||
|
||||
// Auto-enable on local dev origins.
|
||||
if (protocol === 'file:') return true;
|
||||
if (host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0') return true;
|
||||
} catch (e) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
function setDebug(enabled) {
|
||||
DEBUG = !!enabled;
|
||||
try {
|
||||
localStorage.setItem(DEBUG_KEY, DEBUG ? '1' : '0');
|
||||
} catch (e) {}
|
||||
return DEBUG;
|
||||
}
|
||||
|
||||
function isDebug() {
|
||||
return DEBUG;
|
||||
}
|
||||
|
||||
function isStorageAvailable() {
|
||||
try {
|
||||
var key = PREFIX + '__test__';
|
||||
localStorage.setItem(key, '1');
|
||||
localStorage.removeItem(key);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function buildKey(key) {
|
||||
return PREFIX + key;
|
||||
}
|
||||
|
||||
function log(tagOrEvent, payload) {
|
||||
if (!DEBUG || typeof console === 'undefined' || !console.info) return;
|
||||
var label = tagOrEvent && tagOrEvent.charAt(0) === '[' ? tagOrEvent : '[BrowserCache][' + tagOrEvent + ']';
|
||||
console.info(label, payload || {});
|
||||
}
|
||||
|
||||
function formatDuration(ms) {
|
||||
if (!ms || ms <= 0) return '0s';
|
||||
var totalSeconds = Math.floor(ms / 1000);
|
||||
var hours = Math.floor(totalSeconds / 3600);
|
||||
var minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
var seconds = totalSeconds % 60;
|
||||
if (hours > 0) return hours + 'h ' + minutes + 'm';
|
||||
if (minutes > 0) return minutes + 'm ' + seconds + 's';
|
||||
return seconds + 's';
|
||||
}
|
||||
|
||||
function remove(key) {
|
||||
if (!isStorageAvailable()) return;
|
||||
try {
|
||||
localStorage.removeItem(buildKey(key));
|
||||
log('remove', { key: buildKey(key) });
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function read(key, options) {
|
||||
options = options || {};
|
||||
if (!isStorageAvailable()) {
|
||||
log('storage-unavailable', { key: buildKey(key) });
|
||||
return null;
|
||||
}
|
||||
|
||||
var fullKey = buildKey(key);
|
||||
|
||||
try {
|
||||
var raw = localStorage.getItem(fullKey);
|
||||
if (!raw) {
|
||||
log('miss', { key: fullKey });
|
||||
return null;
|
||||
}
|
||||
|
||||
var payload = JSON.parse(raw);
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
remove(key);
|
||||
log('invalid-payload', { key: fullKey });
|
||||
return null;
|
||||
}
|
||||
|
||||
if (payload.version !== options.version) {
|
||||
remove(key);
|
||||
log('version-mismatch', { key: fullKey, expected: options.version, actual: payload.version });
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof payload.cachedAt !== 'number' || !('data' in payload)) {
|
||||
remove(key);
|
||||
log('invalid-shape', { key: fullKey });
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof options.validate === 'function' && !options.validate(payload.data)) {
|
||||
remove(key);
|
||||
log('validate-failed', { key: fullKey });
|
||||
return null;
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
var age = now - payload.cachedAt;
|
||||
var softTtl = options.softTtl || 0;
|
||||
var hardTtl = options.hardTtl || softTtl;
|
||||
|
||||
if (hardTtl > 0 && age > hardTtl) {
|
||||
remove(key);
|
||||
log('expired', { key: fullKey, age: formatDuration(age), hardTtl: formatDuration(hardTtl) });
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = {
|
||||
key: fullKey,
|
||||
data: payload.data,
|
||||
cachedAt: payload.cachedAt,
|
||||
age: age,
|
||||
ageText: formatDuration(age),
|
||||
softTtl: softTtl,
|
||||
softTtlText: formatDuration(softTtl),
|
||||
hardTtl: hardTtl,
|
||||
hardTtlText: formatDuration(hardTtl),
|
||||
isFresh: softTtl <= 0 || age <= softTtl,
|
||||
isStale: softTtl > 0 && age > softTtl
|
||||
};
|
||||
|
||||
log(result.isFresh ? 'hit-fresh' : 'hit-stale', {
|
||||
key: fullKey,
|
||||
age: result.ageText,
|
||||
softTtl: result.softTtlText,
|
||||
hardTtl: result.hardTtlText
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
remove(key);
|
||||
log('parse-failed', { key: fullKey, message: e && e.message ? e.message : e });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function write(key, options, data) {
|
||||
options = options || {};
|
||||
if (!isStorageAvailable()) {
|
||||
log('storage-unavailable', { key: buildKey(key) });
|
||||
return;
|
||||
}
|
||||
|
||||
var fullKey = buildKey(key);
|
||||
|
||||
try {
|
||||
localStorage.setItem(fullKey, JSON.stringify({
|
||||
version: options.version,
|
||||
cachedAt: Date.now(),
|
||||
data: data
|
||||
}));
|
||||
log('write', {
|
||||
key: fullKey,
|
||||
softTtl: formatDuration(options.softTtl || 0),
|
||||
hardTtl: formatDuration(options.hardTtl || options.softTtl || 0)
|
||||
});
|
||||
} catch (e) {
|
||||
if (DEBUG && typeof console !== 'undefined' && console.warn) {
|
||||
console.warn('BrowserCache write failed:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
read: read,
|
||||
write: write,
|
||||
remove: remove,
|
||||
buildKey: buildKey,
|
||||
formatDuration: formatDuration,
|
||||
log: log,
|
||||
setDebug: setDebug,
|
||||
isDebug: isDebug
|
||||
};
|
||||
})();
|
||||
@@ -198,16 +198,21 @@ window.initFloatingTools = function() {
|
||||
}
|
||||
|
||||
if (container) {
|
||||
window.lightboxObserver = new MutationObserver(function(mutations) {
|
||||
var syncLightboxState = function() {
|
||||
var lightbox = document.querySelector('.view-image');
|
||||
if (lightbox) {
|
||||
container.classList.add('hidden');
|
||||
} else {
|
||||
container.classList.remove('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
window.lightboxObserver = new MutationObserver(function() {
|
||||
syncLightboxState();
|
||||
});
|
||||
|
||||
window.lightboxObserver.observe(document.body, { childList: true });
|
||||
window.lightboxObserver.observe(document.body, { childList: true, subtree: true });
|
||||
syncLightboxState();
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,77 +1,59 @@
|
||||
(function() {
|
||||
// Default configuration
|
||||
var defaults = {
|
||||
offsetBottom: 100, // Distance from bottom to start loading
|
||||
showTime: 1100, // Transition duration (ms)
|
||||
queueInterval: 200 // Time between showing successive images in queue
|
||||
offsetBottom: 100,
|
||||
showTime: 1100,
|
||||
queueInterval: 200
|
||||
};
|
||||
|
||||
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 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;
|
||||
|
||||
// Load immediately without queue
|
||||
loadImage(img);
|
||||
|
||||
// Stop observing this image once it's triggered
|
||||
self.unobserve(img);
|
||||
}
|
||||
if (!entry.isIntersecting) return;
|
||||
|
||||
var img = entry.target;
|
||||
loadImage(img);
|
||||
self.unobserve(img);
|
||||
});
|
||||
}, {
|
||||
rootMargin: "0px 0px " + defaults.offsetBottom + "px 0px",
|
||||
threshold: 0.01
|
||||
rootMargin: "0px 0px " + defaults.offsetBottom + "px 0px",
|
||||
threshold: 0.01
|
||||
});
|
||||
|
||||
observeImages();
|
||||
}
|
||||
|
||||
// Removed addToQueue and processQueue functions as we want instant loading
|
||||
function getPendingImages() {
|
||||
return document.querySelectorAll('img.lazyload[data-lazy-src], .post-content img[data-lazy-src]:not(.lazy-loaded)');
|
||||
}
|
||||
|
||||
function observeImages() {
|
||||
var images = document.querySelectorAll('img.lazyload, .post-content img:not(.lazy-loaded)');
|
||||
|
||||
var images = getPendingImages();
|
||||
|
||||
images.forEach(function(img) {
|
||||
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')) {
|
||||
observer.observe(img);
|
||||
}
|
||||
observer.observe(img);
|
||||
});
|
||||
}
|
||||
|
||||
function markLoaded(img, callback) {
|
||||
img.classList.add('lazy-loaded');
|
||||
img.removeAttribute('data-lazy-src');
|
||||
if (callback) callback();
|
||||
}
|
||||
|
||||
function loadImage(img, callback) {
|
||||
var src = img.getAttribute('data-lazy-src');
|
||||
if (!src) {
|
||||
@@ -79,37 +61,28 @@
|
||||
return;
|
||||
}
|
||||
|
||||
img.decoding = 'async';
|
||||
|
||||
var temp = new Image();
|
||||
|
||||
var onComplete = function() {
|
||||
// Swap source
|
||||
temp.decoding = 'async';
|
||||
|
||||
temp.onload = function() {
|
||||
img.src = src;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
img.classList.add('lazy-loaded');
|
||||
img.removeAttribute('data-lazy-src');
|
||||
// Execute callback immediately after starting animation logic
|
||||
if (callback) callback();
|
||||
});
|
||||
requestAnimationFrame(function() {
|
||||
markLoaded(img, callback);
|
||||
});
|
||||
};
|
||||
|
||||
temp.onload = onComplete;
|
||||
temp.onerror = function() {
|
||||
// Fallback
|
||||
img.src = src;
|
||||
requestAnimationFrame(() => {
|
||||
img.classList.add('lazy-loaded');
|
||||
if (callback) callback();
|
||||
});
|
||||
markLoaded(img, callback);
|
||||
};
|
||||
|
||||
temp.src = src;
|
||||
}
|
||||
|
||||
function loadAllImages() {
|
||||
var images = document.querySelectorAll('img.lazyload');
|
||||
var images = getPendingImages();
|
||||
images.forEach(function(img) {
|
||||
loadImage(img);
|
||||
});
|
||||
@@ -117,5 +90,4 @@
|
||||
|
||||
window.lazyLoadInit = init;
|
||||
window.lazyLoadRefresh = observeImages;
|
||||
|
||||
})();
|
||||
@@ -50,6 +50,16 @@ function pjax_reload() {
|
||||
}
|
||||
}
|
||||
|
||||
function refreshHeatmap() {
|
||||
if (typeof renderHeatmap === 'function') {
|
||||
try {
|
||||
renderHeatmap();
|
||||
} catch (e) {
|
||||
console.error('renderHeatmap error:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pjax发送时期
|
||||
document.addEventListener("pjax:send", function () {
|
||||
// pjax动画减少圆圈动画
|
||||
@@ -141,4 +151,6 @@ document.addEventListener("pjax:complete", function () {
|
||||
window.initSearch();
|
||||
} catch(e) { console.error('initSearch error:', e); }
|
||||
}
|
||||
|
||||
refreshHeatmap();
|
||||
});
|
||||
Reference in new issue
Block a user