web页面录屏实现
副问题[/!--empirenews.page--]
在看到评述后,溘然意识到本身没有提前声名,本文可以说是一篇调研进修文,是我本身感受可行的一套方案,后续会去读读已经开源的一些相同的代码库,补足本身漏掉的一些细节,以是各人可以看成进修文,出产情形慎用。 录屏重现错误场景 假如你的应用有接入到web apm体系中,那么你也许就知道apm体系能帮你捕捉到页面产生的未捕捉错误,给堕落误栈,辅佐你定位到BUG。可是,有些时辰,当你不知道用户的详细操纵时,是没有步伐重现这个错误的,这时辰,假若有操纵录屏,你就可以清晰地相识到用户的操纵路径,从而复现这个BUG而且修复。 实现思绪 思绪一:操作Canvas截图 这个思绪较量简朴,就是操作canvas去画网页内容,较量著名的库有: html2canvas ,这个库的简朴道理是: 网络全部的DOM,存入一个queue中; 按照zIndex凭证次序将DOM一个个通过必然法则,把DOM和其CSS样式一路画到Canvas上。 这个实现是较量伟大的,可是我们可以直接行使,以是我们可以获取到我们想要的网页截图。 为了使得天生的视频较为流通,我们一秒中必要天生约莫25帧,也就是必要25张截图,思绪流程图如下: 可是,这个思绪有个最致命的不敷:为了视频流通,一秒中我们必要25张图,一张图300KB,当我们必要30秒的视频时,图的巨细总共为220M,这么大的收集开销明明不可。 思绪二:记录全部操纵重现 为了低就逮络开销,我们换个思绪,我们在最开始的页面基本上,记录下一步步操纵,在我们必要"播放"的时辰,凭证次序应用这些操纵,这样我们就能看到页面的变革了。这个思绪把鼠标操纵和DOM变革分隔: 鼠标变革: 监听mouseover变乱,记录鼠标的clientX和clientY。 重放的时辰行使js画出一个假的鼠标,按照坐标志录来变动"鼠标"的位置。 DOM变革: 对页面DOM举办一次全量快照。包罗样式的网络、JS剧本去除,并通过必然的法则给当前的每个DOM元素标志一个id。 监听全部也许对界面发生影响的变乱,譬喻种种鼠标变乱、输入变乱、转动变乱、缩放变乱等等,每个变乱都记录参数和方针元素,方针元素可所以适才记录的id,这样的每一次变革变乱可以记录为一次增量的快照。 将必然量的快照发送给后端。 在靠山按照快照和操纵链举办播放。 虽然这个声名是较量大略的,鼠标的记录较量简朴,我们不睁开讲,首要声名一下DOM监控的实现思绪。 页面初次全量快照 起首你也许会想到,要实现页面全量快照,可以直接行使 outerHTML const content = document.documentElement.outerHTML; 这样就简朴记录了页面的全部DOM,你只必要起首给DOM增进标志id,然后获得outerHTML,然后去除JS剧本。 可是,这里有个题目,行使 outerHTML 记录的DOM会将把邻近的两个TextNode归并为一个节点,而我们后续监控DOM变革时会行使 MutationObserver ,此时你必要大量的处理赏罚来兼容这种TextNode的归并,否则你在还原操纵的时辰无法定位到操纵的方针节点。 那么,我们有步伐保持页面DOM的原有布局吗? 谜底是必定的,在这里我们行使Virtual DOM来记录DOM布局,把documentElement酿成Virtual DOM,记录下来,后头还原的时辰从头天生DOM即可。 DOM转化为Virtual DOM 我们在这里只必要体谅两种Node范例: Node.TEXT_NODE 和 Node.ELEMENT_NODE 。同时,要留意,SVG和SVG子元素的建设必要行使API:createElementNS,以是,我们在记录Virtual DOM的时辰,必要留意namespace的记录,上代码: const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; const XML_NAMESPACES = ['xmlns', 'xmlns:svg', 'xmlns:xlink']; function createVirtualDom(element, isSVG = false) { switch (element.nodeType) { case Node.TEXT_NODE: return createVirtualText(element); case Node.ELEMENT_NODE: return createVirtualElement(element, isSVG || element.tagName.toLowerCase() === 'svg'); default: return null; } } function createVirtualText(element) { const vText = { text: element.nodeValue, type: 'VirtualText', }; if (typeof element.__flow !== 'undefined') { vText.__flow = element.__flow; } return vText; } function createVirtualElement(element, isSVG = false) { const tagName = element.tagName.toLowerCase(); const children = getNodeChildren(element, isSVG); const { attr, namespace } = getNodeAttributes(element, isSVG); const vElement = { tagName, type: 'VirtualElement', children, attributes: attr, namespace, }; if (typeof element.__flow !== 'undefined') { vElement.__flow = element.__flow; } return vElement; } function getNodeChildren(element, isSVG = false) { const childNodes = element.childNodes ? [...element.childNodes] : []; const children = []; childNodes.forEach((cnode) => { children.push(createVirtualDom(cnode, isSVG)); }); return children.filter(c => !!c); } function getNodeAttributes(element, isSVG = false) { const attributes = element.attributes ? [...element.attributes] : []; const attr = {}; let namespace; attributes.forEach(({ nodeName, nodeValue }) => { attr[nodeName] = nodeValue; if (XML_NAMESPACES.includes(nodeName)) { namespace = nodeValue; } else if (isSVG) { namespace = SVG_NAMESPACE; } }); return { attr, namespace }; } 通过以上代码,我们可以将整个documentElement转化为Virtual DOM,个中__flow用来记录一些参数,包罗标志ID等,Virtual Node记录了:type、attributes、children、namespace。 Virtual DOM还原为DOM 将Virtual DOM还原为DOM的时辰就较量简朴了,只必要递归建设DOM即可,个中nodeFilter是为了过滤script元素,由于我们不必要JS剧本的执行。 (编辑:湖南网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |