create-skeleton
Version:
a nice tool to make skeleton screen
328 lines (272 loc) • 8.41 kB
JavaScript
;
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);
}
});
};