编辑:[db:作者] 时间:2024-08-25 01:38:38
一、page-skeleton-webpack-plugin
page-skeleton-webpack-plugin是一款由ElemeFE团队开拓的webpack 插件,该插件的目的是根据你项目中不同的路由页面天生相应的骨架屏页面,并将骨架屏页面通过 webpack 打包到对应的静态路由页面中。
二、插件自动天生骨架屏的紧张事理
通过无头浏览器puppeteer打开要天生骨架屏的页面等待页面渲染完后注入提取骨架屏的脚本(把稳:一定要等页面完备渲染完,不然提取的DOM不完全)对页面中元素进行删减或增长,对已有元素通过层叠样式进行覆盖,这样达到在不改变页面布局下,隐蔽图片和笔墨,通过样式覆盖,使得其展示为灰色块。然后将修正后的 HTML 和 CSS 样式提取出来天生骨架屏。
先demo展示一下如何自动天生骨架屏,后续再通过代码详细剖析如何天生骨架屏:
安装运行环境
依赖环境:
puppeteernodejs v8.x安装puppeteer可参考:https://www.jianshu.com/p/a9a55c03f768
启动puppeteer并打开要天生骨架屏的页面
const puppeteer = require('puppeteer');const devices = require('puppeteer/DeviceDescriptors');const iPhone = devices['iPhone 6'];const { Skeleton } = require('page-skeleton-webpack-plugin');let skeleton = new Skeleton();(async () => { const browser = await (puppeteer.launch({ //设置超时时间 timeout: 15000, //如果是访问https页面 此属性会忽略https缺点 ignoreHTTPSErrors: true, // 打开开拓者工具, 当此值为true时, headless总为false devtools: true, // 非headless模式,为了能直不雅观看到页面天生骨架屏的过程 headless: false })); const page = await browser.newPage(); // 由于是移动端,设置仿照iphone6 await page.emulate(iPhone); // 打开m站首页 await page.goto('https://m.to8to.com/sz'); // 等待首屏bannar加载完成 await page.waitForSelector('.ad-data-report-carousel'); // 开始build骨架屏 await skeleton.makeSkeleton(page);})();
接下来剖析makeSkeleton是如何天生骨架屏代码
入口代码在page-skeleton-webpack-plugin/src/skeleton.js
通过page.addScriptTag向puppeteer注入脚本并初始化,脚本路径在page-skeleton-webpack-plugin/src/script/index.js。实行genSkeleton方法天生骨架屏async makeSkeleton(page) { const {defer} = this.options // 把天生骨架屏代码注入puppeteer同时实行初始化 await page.addScriptTag({content: this.scriptContent}) // 延迟逻辑,用于等待某些异步操作,图1我已经利用waitForSelector,以是这个可以不用管 await sleep(defer) // 实行genSkeleton方法 await page.evaluate((options) => { Skeleton.genSkeleton(options) }, this.options) }
初始化核心逻辑:
初始化参数解释:const pluginDefaultConfig = { port: '8989', // 该配置工具可以配置一个 color 字段,用于决定骨架页面中笔墨块的的颜色,颜色值支持16进制、RGB等。 text: { color: '#EEEEEE' }, // 该配置接管 3 个字段,color、shape、shapeOpposite。color 和 shape 用于确定骨架页面中图片块的颜色和形状, // 颜色值支持16 进制和 RGB等,形状支持两个列举值,circle (矩形)和 rect(圆形)。 // shapeOpposite 字段接管一个数组,数组中每个元素是一个 DOM 选择器,用于选择 DOM 元素, // 当选择 DOM 的形状将和配置的 shape 形状相反,例如,配置的是 rect那么, // shapeOpposite 中的图片块将在骨架页面中显示成 circle 形状(圆形),详细怎么配置可以参考该部分末端的默认配置。 image: { shape: 'rect', // `rect` | `circle` color: '#EFEFEF', shapeOpposite: [] }, // 该配置接管两个字段,color 和 excludes。color 用来确定骨架页面中被视为按钮块的颜色, // excludes 接管一个数组,数组中元素是 DOM 选择器,用来选择元素,该数组中的元素将不被视为按钮块 button: { color: '#EFEFEF', excludes: [] }, // 该配置接管 3 个字段,color、shape、shapeOpposite。color 和 shape 用于确定骨架页面中 svg 块的颜色和形状, // 颜色值支持16 进制和 RGB等,同时也支持 transparent 列举值,设置为 transparent 后, // svg 块将是透明块。形状支持两个列举值,circle (矩形)和 rect(圆形)。 // shapeOpposite 字段接管一个数组,数组中每个元素是一个 DOM 选择器,用于选择 DOM 元素, // 当选择 DOM 的形状将和配置的 shape 形状相反,例如,配置的是 rect那么, // shapeOpposite 中的 svg 块将在骨架页面中显示成 circle 形状(圆形),详细怎么配置可以参考该部分末端的默认配置。 svg: { color: '#EFEFEF', shape: 'circle', // circle | rect shapeOpposite: [] }, // 该配置接管两个字段,color 和 shape。color 用来确定骨架页面中被视为伪元素块的颜色, // shape 用来设置伪元素块的形状,接管两个列举值:circle 和 rect。 pseudo: { color: '#EFEFEF', // or transparent shape: 'circle' // circle | rect }, device: 'iPhone 6', debug: false, minify: { minifyCSS: { level: 2 }, removeComments: true, removeAttributeQuotes: true, removeEmptyAttributes: false }, defer: 5000, // 如果你有不须要进行骨架处理的元素,那么将该元素的 CSS 选择器写入该数组。 excludes: [], // 不须要天生页面骨架,且须要从 DOM 中移除的元素,配置值为移除元素的 CSS 选择器。 remove: [], // 不须要移除,但是通过设置其透明度为 0,来隐蔽该元素,配置值为隐蔽元素的 CSS 选择器。 hide: [], // 该数组中元素是 CSS 选择器,当选择的元素将被被插件处理成一个色块,色块的颜色和按钮块颜色同等。内部元素将不再做分外处理,笔墨将隐蔽。 grayBlock: [], cookies: [], // 其接管的列举值rem, vw, vh, vmin, vmax。 cssUnit: 'rem', // 天生骨架页面(shell.html)中 css 值保留的小数位数,默认值是 4。 decimal: 4, logLevel: 'info', quiet: false, noInfo: false, logTime: true};递归遍历DOM树,将DOM分类成文本块、按钮块、图片块、SVG块、伪类元素块等。
// ele 为 document.documentElement; 递归遍历DOM树;(function preTraverse(ele) { // styles为元素中所有可用的css属性列表 const styles = getComputedStyle(ele); // 检讨元素是否有伪元素 const hasPseudoEle = checkHasPseudoEle(ele); // 判断元素是否在可视区域内(是否是首屏元素),非首屏元素将要移除 if (!inViewPort(ele) || DISPLAY_NONE.test(ele.getAttribute('style'))) {return toRemove.push(ele) } // 自定义要处理为色块的元素 if (~grayEle.indexOf(ele)) { // eslint-disable-line no-bitwisereturn grayBlocks.push(ele) } // 自定义不须要处理为骨架的元素 if (~excludesEle.indexOf(ele)) return false // eslint-disable-line no-bitwise if (hasPseudoEle) {pseudos.push(hasPseudoEle); } if (checkHasBorder(styles)) {ele.style.border = 'none'; } // 列表元素统一处理为默认样式 if (ele.children.length > 0 && /UL|OL/.test(ele.tagName)) {listHandle(ele); } // 有子节点遍历处理 if (ele.children && ele.children.length > 0) {Array.from(ele.children).forEach(child => preTraverse(child)); } // 将所有拥有 textChildNode 子元素的元素的笔墨颜色设置成背景色,这样就不会在显示笔墨了。 if (ele.childNodes && Array.from(ele.childNodes).some(n => n.nodeType === Node.TEXT_NODE)) {transparent(ele); } // 统一文本下划线的颜色 if (checkHasTextDecoration(styles)) {ele.style.textDecorationColor = TRANSPARENT; } // 隐蔽所有 svg 元素 if (ele.tagName === 'svg') {return svgs.push(ele) } // 有背景色或背景图的元素 if (EXT_REG.test(styles.background) || EXT_REG.test(styles.backgroundImage)) {return hasImageBackEles.push(ele) } // 背景渐变元素 if (GRADIENT_REG.test(styles.background) || GRADIENT_REG.test(styles.backgroundImage)) {return gradientBackEles.push(ele) } if (ele.tagName === 'IMG' || isBase64Img(ele)) {return imgs.push(ele) } if (ele.nodeType === Node.ELEMENT_NODE &&(ele.tagName === 'BUTTON' || (ele.tagName === 'A' && ele.getAttribute('role') === 'button')) ) {return buttons.push(ele) } if (ele.childNodes &&ele.childNodes.length === 1 &&ele.childNodes[0].nodeType === Node.TEXT_NODE &&/\S/.test(ele.childNodes[0].textContent) ) {return texts.push(ele) }}(rootElement));将分类好的文本块、图片块等处理天生骨架构造代码
svgs.forEach(e => svgHandler(e, svg, cssUnit, decimal));texts.forEach(e => { textHandler(e, text, cssUnit, decimal)});buttons.forEach(e => buttonHandler(e, button));hasImageBackEles.forEach(e => backgroundHandler(e, image));imgs.forEach(e => imgHandler(e, image));pseudos.forEach(e => pseudosHandler(e, pseudo));gradientBackEles.forEach(e => backgroundHandler(e, image));grayBlocks.forEach(e => grayHandler(e, button));
详细各块的骨架构造如何天生的接下来会逐一剖析
1、SVG块天生骨架构造
判断svg元素是否不可见,不可见则直接删除元素// 宽高为0或设置隐蔽的元素直接移除(aria是为残疾人士等供应无障碍访问动态、可交互Web内容的技能规范)if (width === 0 || height === 0 || ele.getAttribute('aria-hidden') === 'true') { return removeElement(ele)}
非隐蔽的元素,会把 svg 元素内部所有元素删除,减少最终生成的骨架页面体积,其次,设置svg 元素的宽、高和形状等。
// 设置shapeOpposite的元素的终极形状和shape配置的相反const finalShape = shapeOpposite.indexOf(ele) > -1 ? getOppositeShape(shape) : shape;// 清空元素的内部构造 innerHTML = ''emptyElement(ele);const shapeClassName = CLASS_NAME_PREFEX + shape;// 根据rect or cirle设置border-radius属性,同时set到styleCacheshapeStyle(shape);Object.assign(ele.style, { width: px2relativeUtil(width, cssUnit, decimal), height: px2relativeUtil(height, cssUnit, decimal),});addClassName(ele, [shapeClassName]);// color是自定义svg配置中的color属性,可设置16进制设置及transparent列举值if (color === TRANSPARENT) { // 设置为透明块 setOpacity(ele);} else { // 设置背景色 const className = CLASS_NAME_PREFEX + 'svg'; const rule = `{ background: ${color} !important;}`; addStyle(`.${className}`, rule); ele.classList.add(className);}
2、按钮块天生骨架构造
button块的处理相比拟较大略,去除边框和阴影,设定好统一的背景色和笔墨,按钮块就处理完成了。
function buttonHandler(ele, {color, excludes}) { if (excludes.indexOf(ele) > -1) return false const classname = CLASS_NAME_PREFEX + 'button'; const rule = `{ color: ${color} !important; background: ${color} !important; border: none !important; box-shadow: none !important; }`; addStyle(`.${classname}`, rule); ele.classList.add(classname);}
3、背景块天生骨架构造
背景块指有背景图或者背景色的元素。统一设置背景色即可。
function backgroundHandler(ele, {color, shape}) { const imageClass = CLASS_NAME_PREFEX + 'image'; const shapeClass = CLASS_NAME_PREFEX + shape; const rule = `{ background: ${color} !important; }`; addStyle(`.${imageClass}`, rule); shapeStyle(shape); addClassName(ele, [imageClass, shapeClass]);}
4、图片块天生骨架构造
设置元素宽高、11像素透明gif图的base64编码值添补图片设置背景色、形状去除无用属性(alt)function imgHandler(ele, {color, shape, shapeOpposite}) { const {width, height} = ele.getBoundingClientRect(); const attrs = { width, height, src: SMALLEST_BASE64 // 11像素透明gif图 }; const finalShape = shapeOpposite.indexOf(ele) > -1 ? getOppositeShape(shape) : shape; setAttributes(ele, attrs); const className = CLASS_NAME_PREFEX + 'image'; const shapeName = CLASS_NAME_PREFEX + finalShape; const rule = `{ background: ${color} !important; }`; addStyle(`.${className}`, rule); shapeStyle(finalShape); addClassName(ele, [className, shapeName]); if (ele.hasAttribute('alt')) { ele.removeAttribute('alt'); }}
5、伪元素块处理骨架构造
伪元素::before和::after去除背景图、统一为透明背景色设置形状(矩形or圆角)function pseudosHandler({ele, hasBefore, hasAfter}, {color, shape, shapeOpposite}) { if (!shapeOpposite) shapeOpposite = [] const finalShape = shapeOpposite.indexOf(ele) > -1 ? getOppositeShape(shape) : shape; const PSEUDO_CLASS = `${CLASS_NAME_PREFEX}pseudo`; const PSEUDO_RECT_CLASS = `${CLASS_NAME_PREFEX}pseudo-rect`; const PSEUDO_CIRCLE_CLASS = `${CLASS_NAME_PREFEX}pseudo-circle`; const rules = { [`.${PSEUDO_CLASS}::before, .${PSEUDO_CLASS}::after`]: `{ background: ${color} !important; background-image: none !important; color: transparent !important; border-color: transparent !important; }`, [`.${PSEUDO_RECT_CLASS}::before, .${PSEUDO_RECT_CLASS}::after`]: `{ border-radius: 0 !important; }`, [`.${PSEUDO_CIRCLE_CLASS}::before, .${PSEUDO_CIRCLE_CLASS}::after`]: `{ border-radius: 50% !important; }` }; Object.keys(rules).forEach(key => { addStyle(key, rules[key]); }); addClassName(ele, [PSEUDO_CLASS, finalShape === 'circle' ? PSEUDO_CIRCLE_CLASS : PSEUDO_RECT_CLASS]);}
6、文本块处理骨架构造
文本块相对处理起来会比较繁芜些,以是放到末了来讲。
文本块定义:任何包含文本节点的元素都是文本块。
打算文本块的文本行数、笔墨高度(即要绘制的文本块高度=fontSize):
打算文本行数 ( 元素高度 - 高下padding ) / 行高打算文本高度比 = 字体高度/行高(默认1 / 1.4)// 文本行数 =( 高度 - 高下padding ) / 行高const lineCount = (height - parseFloat(paddingTop, 10) - parseFloat(paddingBottom, 10)) / parseFloat(lineHeight, 10) | 0; // eslint-disable-line no-bitwise// 文本高度比 = 字体高度/行高let textHeightRatio = parseFloat(fontSize, 10) / parseFloat(lineHeight, 10);if (Number.isNaN(textHeightRatio)) { textHeightRatio = 1 / 1.4; // default number}
通过线性渐变天生条纹背景的文本块:
const firstColorPoint = ((1 - textHeightRatio) / 2 100).toFixed(decimal);const secondColorPoint = (((1 - textHeightRatio) / 2 + textHeightRatio) 100).toFixed(decimal);const backgroundSize = `100% ${px2relativeUtil(lineHeight, cssUnit, decimal)}`;const className = CLASS_NAME_PREFEX + 'text-' + firstColorPoint.toString(32).replace(/\./g, '-');const rule = `{ background-image: linear-gradient(transparent ${firstColorPoint}%, ${color} 0%, ${color} ${secondColorPoint}%, transparent 0%) !important; background-size: ${backgroundSize}; position: ${position} !important;}`;
单行文本须要打算文本宽度和text-aligin属性
const textWidthPercent = textWidth / (width - parseInt(paddingRight, 10) - parseInt(paddingLeft, 10));ele.style.backgroundSize = `${(textWidthPercent > 1 ? 1 : textWidthPercent) 100}% ${px2relativeUtil(lineHeight, cssUnit, decimal)}`;switch (textAlign) {case 'left': // do nothingbreakcase 'center': ele.style.backgroundPositionX = '50%'; breakcase 'right': ele.style.backgroundPositionX = '100%'; break}
以上便是elementUI开源的骨架屏插件的紧张逻辑啦。当然还有涉及工程化干系的逻辑这里就没贴出来了,后续可以再逐步磋商。
我抽空把天生骨架屏的逻辑单独抽出来,方便大家定制对骨架屏的工程化处理及调试
https://github.com/wookaoer/page-skeleton-core
本站所发布的文字与图片素材为非商业目的改编或整理,版权归原作者所有,如侵权或涉及违法,请联系我们删除,如需转载请保留原文地址:http://www.baanla.com/bx/65367.html
下一篇:返回列表
Copyright 2005-20203 www.baidu.com 版权所有 | 琼ICP备2023011765号-4 | 统计代码
声明:本站所有内容均只可用于学习参考,信息与图片素材来源于互联网,如内容侵权与违规,请与本站联系,将在三个工作日内处理,联系邮箱:123456789@qq.com