智能检测浏览器能力,优先使用原生懒加载和WebP,优雅降级方案
// ==UserScript==
// @name WebP加载优化(此脚本转为图片加载而设计)
// @namespace http://tampermonkey.net/
// @version 1.4
// @description 智能检测浏览器能力,优先使用原生懒加载和WebP,优雅降级方案
// @author KiwiFruit
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 浏览器能力检测
const supportsLazyLoading = 'loading' in HTMLImageElement.prototype;
const supportsWebP = document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0;
// 配置参数
const config = {
rootMargin: '200px 0px', // 提前200px加载
threshold: 0.01,
retryLimit: 2,
retryDelay: 3000,
webpQuality: 75,
placeholder: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNmMGYwZjAiLz48L3N2Zz4='
};
// 添加样式
const style = document.createElement('style');
style.textContent = `
img[data-src], img[data-webp-src] {
opacity: 0;
transition: opacity 0.4s ease-in-out;
background-color: #f5f5f5;
background-image: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
img.loaded {
opacity: 1;
background: none;
}
img.error {
opacity: 0.7;
background: #ffebee;
position: relative;
}
img.error::after {
content: "图片加载失败";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #d32f2f;
font-size: 12px;
background: rgba(255,255,255,0.8);
padding: 2px 6px;
border-radius: 3px;
}
`;
document.head.appendChild(style);
// 图片处理函数
function processImage(img) {
if (img.dataset.processed) return;
// 设置占位符
if (!img.src && !img.dataset.src) {
img.src = config.placeholder;
}
// 原生懒加载处理
if (supportsLazyLoading) {
img.setAttribute('loading', 'lazy');
// WebP处理
if (supportsWebP && img.dataset.webpSrc) {
img.src = img.dataset.webpSrc;
} else if (img.dataset.src) {
img.src = img.dataset.src;
}
img.onload = () => img.classList.add('loaded');
img.onerror = () => handleImageError(img);
img.dataset.processed = 'true';
return;
}
// 非原生懒加载处理
if (img.dataset.src || img.dataset.webpSrc) {
img.removeAttribute('src');
observer.observe(img);
}
}
// 图片错误处理
function handleImageError(img) {
img.classList.add('error');
const retryCount = parseInt(img.dataset.retryCount || '0');
if (retryCount < config.retryLimit) {
img.dataset.retryCount = retryCount + 1;
setTimeout(() => {
img.src = img.dataset.src || img.dataset.webpSrc || img.src;
}, config.retryDelay);
}
}
// IntersectionObserver回调
function handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
observer.unobserve(img);
// 设置图片源
if (supportsWebP && img.dataset.webpSrc) {
img.src = img.dataset.webpSrc;
} else if (img.dataset.src) {
img.src = img.dataset.src;
}
img.onload = () => img.classList.add('loaded');
img.onerror = () => handleImageError(img);
img.dataset.processed = 'true';
}
});
}
// 创建观察者
const observer = new IntersectionObserver(handleIntersection, {
rootMargin: config.rootMargin,
threshold: config.threshold
});
// 处理现有图片
document.querySelectorAll('img[data-src], img[data-webp-src]').forEach(processImage);
// 监听动态添加的图片
const mutationObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.tagName === 'IMG') {
processImage(node);
} else if (node.nodeType === 1) {
node.querySelectorAll('img[data-src], img[data-webpSrc]').forEach(processImage);
}
});
});
});
mutationObserver.observe(document.body, {
childList: true,
subtree: true
});
// 性能监控
if (window.performance && window.performance.mark) {
window.performance.mark('lazyLoadStart');
window.addEventListener('load', () => {
window.performance.mark('lazyLoadEnd');
window.performance.measure('lazyLoad', 'lazyLoadStart', 'lazyLoadEnd');
const measures = window.performance.getEntriesByName('lazyLoad');
if (measures.length > 0) {
console.log(`[懒加载优化] 初始化耗时: ${measures[0].duration.toFixed(2)}ms`);
}
});
}
})();