229 lines
5.9 KiB
JavaScript
229 lines
5.9 KiB
JavaScript
function imgEncrypt(buffer) {
|
|
var bytes = new Uint8Array(buffer);
|
|
var encoder = new TextEncoder();
|
|
var KEY = encoder.encode('2019ysapp7527');
|
|
for (var i = 0; i < Math.min(100, bytes.length); i++) {
|
|
bytes[i] ^= KEY[i % KEY.length];
|
|
}
|
|
var mime = 'image/jpeg';
|
|
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x38) {
|
|
mime = 'image/gif';
|
|
} else if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
|
|
mime = 'image/png';
|
|
} else if (
|
|
bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46 &&
|
|
bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50
|
|
) {
|
|
mime = 'image/webp';
|
|
}
|
|
var blob = new Blob([bytes], {
|
|
type: mime,
|
|
});
|
|
return URL.createObjectURL(blob);
|
|
}
|
|
|
|
var IMG_PLACEHOLDER = '/static/images/placeholder.png?v=2';
|
|
var lazyImageObserver = null;
|
|
var decryptedURLCache = Object.create(null);
|
|
var decryptInflight = Object.create(null);
|
|
var decryptQueue = [];
|
|
var decryptActive = 0;
|
|
// H5 弱网降低并发,优先完成 LCP 主图;PC 保持 4
|
|
var DECRYPT_CONCURRENCY =
|
|
window.matchMedia && window.matchMedia("(max-width: 767px)").matches ? 2 : 4;
|
|
|
|
function shouldDecryptImage(url) {
|
|
if (!url) return false;
|
|
var u = String(url).trim();
|
|
if (!u) return false;
|
|
if (u.indexOf('data:') === 0 || u.indexOf('blob:') === 0) return false;
|
|
if (u.indexOf('/static/') === 0) return false;
|
|
if (u.indexOf('placehold.co') >= 0) return false;
|
|
return true;
|
|
}
|
|
|
|
function applyImageResult(dom, type, url) {
|
|
if (!dom || !url) return;
|
|
if (type === 'bgImg') {
|
|
dom.style.setProperty('background-image', 'url("' + url + '")');
|
|
} else {
|
|
dom.src = url;
|
|
}
|
|
}
|
|
|
|
function fetchDecrypt(url, priority) {
|
|
if (decryptedURLCache[url]) {
|
|
return Promise.resolve(decryptedURLCache[url]);
|
|
}
|
|
if (decryptInflight[url]) {
|
|
return decryptInflight[url];
|
|
}
|
|
|
|
decryptInflight[url] = new Promise(function (resolve, reject) {
|
|
var run = function () {
|
|
decryptActive += 1;
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', url, true);
|
|
xhr.responseType = 'arraybuffer';
|
|
xhr.onload = function () {
|
|
decryptActive -= 1;
|
|
pumpDecryptQueue();
|
|
if (xhr.status === 200) {
|
|
var blobUrl = imgEncrypt(xhr.response);
|
|
decryptedURLCache[url] = blobUrl;
|
|
resolve(blobUrl);
|
|
return;
|
|
}
|
|
reject(xhr.status);
|
|
};
|
|
xhr.onerror = function () {
|
|
decryptActive -= 1;
|
|
pumpDecryptQueue();
|
|
reject('network');
|
|
};
|
|
xhr.send();
|
|
};
|
|
|
|
if (decryptActive < DECRYPT_CONCURRENCY) {
|
|
run();
|
|
} else if (priority) {
|
|
decryptQueue.unshift(run);
|
|
} else {
|
|
decryptQueue.push(run);
|
|
}
|
|
});
|
|
decryptInflight[url].then(
|
|
function () { delete decryptInflight[url]; },
|
|
function () { delete decryptInflight[url]; }
|
|
);
|
|
|
|
return decryptInflight[url];
|
|
}
|
|
|
|
function pumpDecryptQueue() {
|
|
while (decryptActive < DECRYPT_CONCURRENCY && decryptQueue.length) {
|
|
var next = decryptQueue.shift();
|
|
if (typeof next === 'function') next();
|
|
}
|
|
}
|
|
|
|
function processImage(url, dom, type, fallback) {
|
|
if (!url || !dom) {
|
|
return Promise.resolve('');
|
|
}
|
|
|
|
if (!shouldDecryptImage(url)) {
|
|
applyImageResult(dom, type, url);
|
|
return Promise.resolve(url);
|
|
}
|
|
|
|
var priority =
|
|
!!dom &&
|
|
(dom.getAttribute('fetchpriority') === 'high' ||
|
|
dom.getAttribute('data-lazy-eager') === '1');
|
|
|
|
return fetchDecrypt(url, priority)
|
|
.then(function (blobUrl) {
|
|
applyImageResult(dom, type, blobUrl);
|
|
return blobUrl;
|
|
})
|
|
.catch(function () {
|
|
if (dom.tagName === 'IMG') {
|
|
dom.src = fallback || IMG_PLACEHOLDER;
|
|
}
|
|
return '';
|
|
});
|
|
}
|
|
|
|
function loadLazyNode(node) {
|
|
if (!node || node.getAttribute('data-decrypted') === '1') {
|
|
return;
|
|
}
|
|
var src = node.getAttribute('data-src');
|
|
if (!src) {
|
|
return;
|
|
}
|
|
node.setAttribute('data-decrypted', '1');
|
|
if (node.tagName === 'IMG') {
|
|
if (!node.getAttribute('src')) {
|
|
node.src = IMG_PLACEHOLDER;
|
|
}
|
|
processImage(src, node);
|
|
return;
|
|
}
|
|
if (node.classList && node.classList.contains('blog-background')) {
|
|
processImage(src, node, 'bgImg');
|
|
}
|
|
}
|
|
|
|
function getLazyImageObserver() {
|
|
if (lazyImageObserver) {
|
|
return lazyImageObserver;
|
|
}
|
|
if (!('IntersectionObserver' in window)) {
|
|
return null;
|
|
}
|
|
lazyImageObserver = new IntersectionObserver(
|
|
function (entries) {
|
|
entries.forEach(function (entry) {
|
|
if (!entry.isIntersecting) {
|
|
return;
|
|
}
|
|
var node = entry.target;
|
|
lazyImageObserver.unobserve(node);
|
|
loadLazyNode(node);
|
|
});
|
|
},
|
|
{
|
|
rootMargin: '180px 0px',
|
|
threshold: 0.01,
|
|
}
|
|
);
|
|
return lazyImageObserver;
|
|
}
|
|
|
|
function observeLazyNode(node, eager) {
|
|
if (!node || node.getAttribute('data-decrypted') === '1') {
|
|
return;
|
|
}
|
|
if (
|
|
eager ||
|
|
node.getAttribute('fetchpriority') === 'high' ||
|
|
node.getAttribute('data-lazy-eager') === '1'
|
|
) {
|
|
loadLazyNode(node);
|
|
return;
|
|
}
|
|
var observer = getLazyImageObserver();
|
|
if (!observer) {
|
|
loadLazyNode(node);
|
|
return;
|
|
}
|
|
observer.observe(node);
|
|
}
|
|
|
|
function initLazyImages(root, options) {
|
|
root = root || document;
|
|
options = options || {};
|
|
var eager = !!options.eager;
|
|
|
|
root.querySelectorAll('img.lazy[data-src]:not([data-decrypted])').forEach(function (img) {
|
|
observeLazyNode(img, eager);
|
|
});
|
|
|
|
root.querySelectorAll('.blog-background.lazy[data-src]:not([data-decrypted])').forEach(function (node) {
|
|
observeLazyNode(node, eager);
|
|
});
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
initLazyImages();
|
|
});
|
|
|
|
window.initLazyImages = initLazyImages;
|
|
window.__lazyLoadImages = initLazyImages;
|
|
window.loadLazyNode = loadLazyNode;
|
|
window.processImage = processImage;
|
|
window.imgEncrypt = imgEncrypt;
|
|
window.shouldDecryptImage = shouldDecryptImage;
|