UNPKG

create-skeleton

Version:
328 lines (272 loc) 8.41 kB
"use strict"; module.exports = function evaluatePage() { const ELEMENTS = ["audio", "button", "canvas", "code", "img", "input", "pre", "svg", "textarea", "video", "xmp"]; const blocks = []; const win_w = window.innerWidth; const win_h = window.innerHeight; const agrs = parse(arguments[0]); const classProps = { position: "fixed", zIndex: 2, background: agrs.background }; if (agrs.animation) { classProps.animation = agrs.animation; } createCommonClass(classProps); function parse(value) { let val = {}; Object.entries(value).forEach(i => { const [k, v] = i; const [key, type] = k.split("-"); if (type === "function") { val[key] = eval("(" + v + ")"); } else { val[key] = v; } }); return val; } function drawBlock({ width, height, top, left, zIndex = 2, background = agrs.background, radius, subClas } = {}) { const styles = ["height:" + height + "%"]; if (!subClas) { styles.push("top:" + top + "%", "left:" + left + "%", "width:" + width + "%"); } if (classProps.zIndex !== zIndex) { styles.push("z-index:" + zIndex); } if (classProps.background !== background) { styles.push("background:" + background); } radius && radius != "0px" && styles.push("border-radius:" + radius); blocks.push(`<div class="_${subClas ? " __" : ""}" style="${styles.join(";")}"></div>`); } function wPercent(x) { return parseFloat(x / win_w * 100).toFixed(3); } function hPercent(x) { return parseFloat(x / win_h * 100).toFixed(3); } function getType(arg) { return Object.prototype.toString.call(arg).toLowerCase().match(/\s(\w+)/)[1]; } function getStyle(node, attr) { return (node.nodeType === 1 ? getComputedStyle(node)[attr] : "") || ""; } function getRootNode(el) { if (!el) return el; return typeof el === "object" ? el : getType(el) === "string" ? document.querySelector(el) : null; } function includeNode(elements, node) { return ~elements.indexOf((node.tagName || "").toLowerCase()); } function isHideStyle(node) { if (getStyle(node, "display") === "none" || getStyle(node, "visibility") === "hidden" || getStyle(node, "opacity") == 0 || node.hidden) { return true; } const { t, l, w, h } = getRect(node); if (l < -w || t < -h) { return true; } } function isCustomCardBlock(node) { const bgStyle = getStyle(node, "background"); const bgColorReg = /rgba\([\s\S]+?0\)/gi; const bdReg = /(0px)|(none)/; const hasBgColor = !bgColorReg.test(bgStyle) || ~bgStyle.indexOf("gradient"); const hasNoBorder = ["top", "left", "right", "bottom"].some(item => { return bdReg.test(getStyle(node, "border-" + item)); }); const { w, h } = getRect(node); const customCardBlock = !!(hasBgColor && (!hasNoBorder || getStyle(node, "box-shadow") != "none") && w > 0 && h > 0 && w < 0.95 * win_w && h < 0.3 * win_h); return customCardBlock; } function getRect(node) { if (!node) return {}; const { top: t, left: l, width: w, height: h } = node.getBoundingClientRect(); return { t, l, w, h }; } function createCommonClass(styles) { const inlineStyle = ["<style>._{"]; for (let key in styles) { inlineStyle.push(`${key === "zIndex" ? "z-index" : key}:${styles[key]};`); } inlineStyle.push("}.__{top:0%;left:0%;width:100%;}</style>"); blocks.push(inlineStyle.join("")); } function inHeader(node) { if (agrs.header) { const height = parseInt(agrs.header.height); if (height) { const { t } = getRect(node); return t <= height; } } } class DrawSkeleton { constructor(option) { this.rootNode = getRootNode(option.rootNode) || document.body; this.offsetTop = option.offsetTop || 0; this.includeNode = option.includeNode; this.beforeDraw = option.beforeDraw; this.devtools = option.devtools; this.headless = option.headless; return this instanceof DrawSkeleton ? this : new DrawSkeleton(option); } drawHeader() { if (agrs.header) { const { height, background = agrs.background } = agrs.header; const hHeight = parseInt(height); if (hHeight) { drawBlock({ background, zIndex: 2, height: hPercent(hHeight), subClas: true }); } } } showBlocks() { const blocksHTML = blocks.join(""); if (blocks.length > 0 && this.devtools) { const { body } = document; let skeletonContainer = document.querySelector("#create-skeleton-container"); if (!skeletonContainer) { skeletonContainer = document.createElement("div"); skeletonContainer.id = "create-skeleton-container"; body.appendChild(skeletonContainer); } skeletonContainer.innerHTML = blocksHTML; document.body.style.overflow = this.originStyle.bodyOverflow; window.scrollTo(0, this.originStyle.scrollTop); } return blocksHTML; } // 手动点击骨架屏按钮生成骨架屏 async manualPainting() { return new Promise(resolve => { // 支持在打开页面的时候手动点击创建骨架屏 const { body } = document; const button = document.createElement("button"); button.style.cssText = `position: fixed; right:0; top:0; z-index: 999999; background:#1890ff; color:#FFFFFF; font-size: 10px; border-radius: 2px;`; button.innerHTML = "骨架屏"; button.id = "create-skeleton-btn"; button.onclick = () => resolve(this.start()); body.appendChild(button); }); } async init() { this.originStyle = { scrollTop: window.scrollY, bodyOverflow: getStyle(document.body, "overflow") }; window.scrollTo(0, this.offsetTop); document.body.style.cssText += "overflow:hidden!important;"; if (getType(this.beforeDraw) === "function") { this.beforeDraw(this); } drawBlock({ height: 100, zIndex: 1, background: "#fff", subClas: true }); if (this.headless === false) { return await this.manualPainting(); } return this.start(); } start() { const root = this.rootNode; this.drawHeader(); const filter = { acceptNode(node) { if (isHideStyle(node)) { return NodeFilter.FILTER_REJECT; } if (node.id === "create-skeleton-btn") { return NodeFilter.FILTER_SKIP; } if (includeNode(ELEMENTS, node) || getStyle(node, "backgroundImage").match(/url\(.+?\)/) || isCustomCardBlock(node) && !inHeader(node) || [].find.call(node.childNodes, n => n.nodeType === 3 && n.textContent.trim())) { return NodeFilter.FILTER_ACCEPT; } const { t, l, w, h } = getRect(node); if (!(w > 0 && h > 0 && l >= 0 && l < win_w && win_h - t >= 20 && t >= 0)) { return NodeFilter.FILTER_SKIP; } return NodeFilter.FILTER_SKIP; } }; const nodeIterator = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, filter); let node; while (node = nodeIterator.nextNode()) { var _this$includeNode; const { t, l, w, h } = getRect(node); if (((_this$includeNode = this.includeNode) === null || _this$includeNode === void 0 ? void 0 : _this$includeNode.call(this, node, drawBlock)) !== false) { drawBlock({ width: wPercent(w), height: hPercent(h), top: hPercent(t), left: wPercent(l), radius: getStyle(node, "border-radius") }); } } return this.showBlocks(); } } return new Promise((resolve, reject) => { try { const html = new DrawSkeleton(agrs).init(); resolve(html); } catch (e) { reject(e); } }); };