prefetch-page
Version:
浏览器在空闲时预加载可见区域的超链接,以加速后续页面的加载速度 | Browser prefetchs visible area hyperlinks at idle time to speed up subsequent page loads
128 lines (113 loc) • 4.76 kB
JavaScript
;
const { hostname, href } = location;
// Store the prefetched url
let isFetch = [];
/**
* Create a prefetched link tag to insert into the head
* @param {String} href Prefetched url
*/
function createLink(href) {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = href;
document.head.appendChild(link);
}
/**
* Perform prefetching
* @param {String} url Prefetched url
*/
function prefetch(url) {
const nav = navigator;
const { saveData, effectiveType } = nav.connection || nav.mozConnection || nav.webkitConnection || {};
// If 2G is used or the save-data function is enabled, prefetch is not performed
if (saveData || /2g/.test(effectiveType)) return
// Preventing duplicate prefetching
if (!isFetch.includes(url)) {
isFetch.push(url);
createLink(url);
}
}
/**
* Whether to support prefetch and IntersectionObserver
* @returns {Boolean}
*/
function isSupports() {
const link = document.createElement('link');
return link.relList && link.relList.supports && link.relList.supports('prefetch') && IntersectionObserver
}
function getEl(el) {
try {
if (typeof el === 'string') {
const _el = document.querySelector(el);
if (_el.nodeType === 1) return _el
}
if (el.nodeType === 1) return el
// eslint-disable-next-line no-empty
} catch (error) {}
return document
}
/**
* https://github.com/Lete114/prefetch-page
* Example:
* prefetch({ el: '#box' }) // Select all a tag links under the box tag whose id attribute is
* prefetch({ el: document.body })
* prefetch({ origins: '*' }) // Allows all origins to be prefetched
* prefetch({ origins: ['example.com', 'www.example.com', 'blog.example.com'] })
* prefetch({ limit: 10 })
* prefetch({ threshold: 25 }) // Trigger prefetching after entering 25% of the visible area
* prefetch({ delay: 1000 }) // Prefetch is triggered after staying in the visible area for 1 second
* prefetch({ customs: ['markdown.js', '404.html', 'https://www.example.com'] })
* @param {String|Element} [options.el] - All a tag links under the specified selector (default whole page)
* @param {Array} [options.origins] - Allowed origins to prefetch, Asterisk (*) allows all origins to be prefetched (default [location.hostname])
* @param {Number} [options.limit] - The total number of prefetches to allow (default unlimited)
* @param {Number} [options.threshold] - Percentage of access to the visible area (default 1 pixel appears in the visible area is prefetched)
* @param {Number} [options.delay] - Time each link needs to stay inside viewport before prefetching (milliseconds) (default no delay)
* @param {Array} [options.customs] - Custom prefetch resource (default [])
* @returns {Function} - Executing this function will stop watching all the `<a>` tag hyperlinks that are not prefetched
*/
var index = (options) => {
if (!isSupports()) return
if (!options) options = {};
const el = getEl(options.el);
const origins = options.origins || [hostname];
const limit = options.limit || 1 / 0;
const threshold = (options.threshold || 0) / 100;
const delay = options.delay || 0;
const customs = options.customs || [];
const observer = new IntersectionObserver(observerCallback, { threshold });
function setDelay(callback, delay) {
if (!delay) return callback()
return setTimeout(callback, delay)
}
function observerCallback(entries) {
entries.forEach(({ isIntersecting, target }) => {
// Delayed prefetching
function delayFn() {
observer.unobserve(target); // Stop watching
if (isFetch.length < limit) prefetch(target.href);
}
// Whether the currently observed target appears in the visible window area
// Clear the delay if you leave the visible window during the delay time
if (isIntersecting) target.prefetch = setDelay(delayFn, delay);
else clearTimeout(target.prefetch);
});
}
el.querySelectorAll('a:not([target=_blank])').forEach((node) => {
// If no origins are set, then all origins are prefetch
// and vice versa only prefetch the set origins
if (origins === '*' || origins.includes(node.hostname)) {
// Avoid watching the current page
if (node.href === href) return
// Filtering hash, Avoid unnecessary observations
const { origin, pathname } = new URL(node.href, href);
origin + pathname === node.href ? observer.observe(node) : '';
}
});
// custom prefetch
for (let i = 0; i < customs.length; i++) prefetch(customs[i]);
return function () {
isFetch = [];
observer.disconnect();
}
};
module.exports = index;