Depois de ler os comentários, de repente percebi que não expliquei com antecedência. Este artigo pode ser considerado um artigo de pesquisa e aprendizado. É um conjunto de soluções que considero viáveis. foram de código aberto para complementar meu próprio conhecimento. Faltam alguns detalhes, então você pode tratá-lo como um texto de aprendizagem e usá-lo com cautela em ambientes de produção.
Gravação de tela para reproduzir o cenário de erroSe o seu aplicativo estiver conectado ao sistema web apm, você deve saber que o sistema apm pode ajudá-lo a capturar erros não detectados que ocorrem na página, fornecer uma pilha de erros e ajudá-lo a localizar o BUG. Porém, às vezes, quando você não conhece a operação específica do usuário, não há como reproduzir o bug. Neste momento, se houver uma gravação de tela de operação, você poderá entender claramente o caminho de operação do usuário, reproduzindo assim o. ERRO. E reparar.
Ideias de implementação Ideia 1: use o Canvas para fazer capturas de telaEssa ideia é relativamente simples, que é usar o canvas para desenhar conteúdo da web. As bibliotecas mais famosas são: html2canvas O princípio simples desta biblioteca é:
Esta implementação é relativamente complicada, mas podemos usá-la diretamente, para que possamos obter a captura de tela da página web que desejamos.
Para tornar o vídeo gerado mais suave, precisamos gerar cerca de 25 quadros por segundo, o que significa que precisamos de 25 capturas de tela. O fluxograma da ideia é o seguinte:
No entanto, esta ideia tem a falha mais fatal: para tornar o vídeo suave, precisamos de 25 imagens por segundo, e uma imagem tem 300 KB. Quando precisamos de um vídeo de 30 segundos, o tamanho total das imagens é 220M. Uma sobrecarga de rede tão grande Obviamente não.
Ideia 2: Registrar a recorrência de todas as operaçõesPara reduzir a sobrecarga da rede, mudamos nosso pensamento. Registramos as próximas operações passo a passo com base na página inicial. Quando precisamos jogar, aplicamos essas operações em ordem, para que possamos ver as alterações na página inicial. página. Esta ideia separa as operações do mouse e as alterações do DOM:
Mudanças no mouse:
Claro, esta explicação é relativamente breve. A gravação do mouse é relativamente simples. Não entraremos em detalhes. Explicaremos principalmente as idéias de implementação do monitoramento do DOM.
O primeiro instantâneo completo da página Em primeiro lugar, você pode pensar que, para obter um instantâneo completo da página, pode usar diretamente outerHTML
const conteúdo = document.documentElement.outerHTML;
Isso simplesmente registra todo o DOM da página. Você só precisa primeiro adicionar o ID da tag ao DOM, depois obter o outerHTML e, em seguida, remover o script JS.
No entanto, há um problema aqui. O DOM gravado usando outerHTML
mesclará dois TextNodes adjacentes em um nó. Quando monitorarmos posteriormente as alterações do DOM, usaremos MutationObserver
. de TextNodes , caso contrário você não conseguirá localizar o nó de destino da operação durante a operação de restauração.
Então, há alguma maneira de mantermos a estrutura original da página DOM?
A resposta é sim. Aqui usamos o Virtual DOM para gravar a estrutura do DOM, transformar documentElement em Virtual DOM, gravá-lo e regenerar o DOM ao restaurar posteriormente.
Converter DOM em DOM virtual Só precisamos nos preocupar com dois tipos de Node aqui: Node.TEXT_NODE
e Node.ELEMENT_NODE
. Ao mesmo tempo, deve-se notar que a criação de subelementos SVG e SVG requer o uso da API: createElementNS Portanto, quando gravamos o Virtual DOM, precisamos prestar atenção ao registro do namespace O código acima:
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: retorno createVirtualText(element); case Node.ELEMENT_NODE: return createVirtualElement(element, isSVG || element.tagName.toLowerCase() === 'svg'); texto: element.nodeValue, tipo: 'VirtualText', } if (typeof element.__flow). !== 'indefinido') { vText.__flow = element.__flow } return vText;}function createVirtualElement(element, isSVG = false) { const tagName = element.tagName.toLowerCase(); ); const {attr, namespace } = getNodeAttributes(element, isSVG); tagName, tipo: 'VirtualElement', filhos, atributos: attr, namespace, }; if (typeof element.__flow !== 'indefinido') { vElement.__flow = element.__flow } return vElement;}function getNodeChildren(element, isSVG = false) { const childNodes = element.childNodes ? [...element.childNodes] : []; childNodes.forEach((cnode) => { children.push(createVirtualDom(cnode, isSVG)); }); return children.filter(c => !!c);}function getNodeAttributes(element, isSVG = false) { const atributos = elemento.atributos ? [...elemento.atributos]: []; atributos.forEach(({ nodeName, nodeValue }) => { attr[nodeName] = nodeValue; if (XML_NAMESPACES.includes(nodeName)) { namespace = nodeValue; } else if (isSVG) { namespace = SVG_NAMESPACE; } }); return {attr, namespace};}
Através do código acima, podemos converter todo o documentElement em Virtual DOM, no qual __flow é usado para registrar alguns parâmetros, incluindo ID de tag, etc. Registros de Node Virtual: tipo, atributos, filhos, namespace.
DOM virtual restaurado para DOMÉ relativamente simples restaurar o Virtual DOM para DOM. Você só precisa criar o DOM recursivamente. O nodeFilter é usado para filtrar elementos de script porque não precisamos da execução de scripts JS.
function createElement(vdom, nodeFilter = () => true) { let node; === 'indefinido' ? document.createElement(vdom.tagName) : document.createElementNS(vdom.namespace, vdom.tagName); vdom.attributes) { node.setAttribute(nome, vdom.attributes[nome]); vdom.children.forEach((cnode) => { const childNode = createElement(cnode, nodeFilter); if (childNode && nodeFilter(childNode) ) { node.appendChild(childNode); vdom.__flow; } nó de retorno;}Monitoramento de alterações na estrutura DOM
Aqui usamos a API: MutationObserver. O que é ainda mais gratificante é que essa API é compatível com todos os navegadores, então podemos usá-la com ousadia.
Usando MutationObserver:
const options = { childList: true, // Se devem ser observadas alterações nos nós filhos subárvore: true, // Se devem ser observadas alterações em todos os nós descendentes atributos: true, // Se devem ser observadas alterações nos atributos attributeOldValue: true, // Se para observar alterações nos atributos O valor antigo alterado characterData: true, // Se o conteúdo do nó ou o texto do nó muda characterDataOldValue: true, // Se o conteúdo do nó ou o texto do nó altera o valor antigo // attributeFilter: ['class', ' fonte'] As propriedades que não estão neste array serão ignoradas ao alterar};const observer = new MutationObserver((mutationList) => { //mutationList: array de mutação});observer.observe(document.documentElement, options);
É muito simples de usar. Você só precisa especificar um nó raiz e algumas opções que precisam ser monitoradas. Então, quando o DOM mudar, haverá umamutationList na função de retorno de chamada, que é uma lista de mudanças no DOM. da mutação é aproximadamente:
{ type: 'childList', // ou characterData, atributos target: <DOM>, // outros parâmetros}
Usamos um array para armazenar mutações. O retorno de chamada específico é:
const onMutationChange = (mutationsList) => { const getFlowId = (node) => { if (node) { // O DOM recém-inserido não tem marca, então precisa ser compatível aqui if (!node.__flow) node.__flow = { id: uuid() }; return node.__flow.id; mutação; atributoValue = target.getAttribute(attributeName); case 'childList': record.removedNodes = [...mutation.removedNodes].map(n => getFlowId(n)); record.addedNodes = [...mutation.addedNodes].map((n) => { const snapshot = this.takeSnapshot(n); return { ...instantâneo, nextSibling: getFlowId(n. próximoIrmão), irmão anterior: getFlowId (n. irmão anterior) } }); });}function takeSnapshot(node, options = {}) { this.markNodes(node); document.doctype.name; snapshot.clientWidth = document.body.clientWidth; snapshot.clientHeight = document.body.clientHeight } retornar instantâneo;}
Você só precisa observar aqui que quando você processa um novo DOM, você precisa de um instantâneo incremental. Aqui você ainda usa o Virtual DOM para gravá-lo mais tarde, você ainda gera o DOM e o insere no elemento pai, então aqui. Você precisa se referir ao DOM, que é o nó irmão.
Monitoramento de elemento de formulárioO MutationObserver acima não pode monitorar as alterações de valor de entrada e outros elementos, portanto, precisamos realizar um processamento especial nos valores dos elementos do formulário.
escuta de evento oninputDocumentação MDN: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/oninput
Objetos de evento: select, input, textarea
window.addEventListener('input', this.onFormInput, true);onFormInput = (event) => { const target = event.target; ].includes(target.tagName.toLowerCase()) ) { this.records.push({ type: 'input', target: alvo.__flow.id, valor: alvo.valor, } }}
Use a captura para capturar eventos na janela. Isso também é feito posteriormente. A razão para isso é que podemos e muitas vezes evitamos o borbulhamento durante o estágio de borbulhamento para implementar algumas funções, portanto, o uso da captura pode reduzir a perda de eventos, como eventos de rolagem. Não irá borbulhar e deve ser capturado.
escuta de evento onchangeDocumentação MDN: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/oninput
O evento de entrada não pode satisfazer o monitoramento do tipo checkbox e rádio, portanto é necessário utilizar o evento onchange para monitoramento.
window.addEventListener('change', this.onFormChange, true);onFormChange = (event) => { const target = event.target; if (target && target.__flow) { if ( target.tagName.toLowerCase() == = 'input' && ['caixa de seleção', 'radio'].includes(target.getAttribute('type')) ) { this.records.push({ tipo: 'verificado', alvo: alvo.__flow.id, verificado: alvo.verificado, });escuta de evento em foco
Documentação MDN: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onfocus
window.addEventListener('focus', this.onFormFocus, true);onFormFocus = (event) => { const target = event.target; if (target && target.__flow) { this.records.push({ type: 'focus ', alvo: alvo.__flow.id, });escuta de evento onblur
Documentação MDN: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onblur
window.addEventListener('blur', this.onFormBlur, true); onFormBlur = (event) => { const target = event.target; if (target && target.__flow) { this.records.push({ type: 'blur ', alvo: alvo.__flow.id, });Monitoramento de mudança de elemento de mídia
Isso se refere a áudio e vídeo. Semelhante aos elementos do formulário acima, você pode monitorar eventos onplay, onpause, timeupdate, volumechange e outros eventos e, em seguida, armazená-los em registros.
Monitoramento de alterações na telaNenhum evento é lançado quando o conteúdo da tela é alterado, então podemos:
Colete elementos de tela e atualize regularmente o conteúdo em tempo real. Hackeie algumas APIs de desenho para lançar eventos.
A investigação sobre a monitorização do canvas não é muito aprofundada e é necessária uma investigação mais aprofundada.
jogarA ideia é relativamente simples, basta obter algumas informações do backend:
Usando essas informações, você pode primeiro gerar o DOM da página, que inclui a filtragem de tags de script, depois criar um iframe e anexá-lo a um contêiner, que usa um mapa para armazenar o DOM
função play (opções = {}) { const {contêiner, registros = [], instantâneo ={} } = opções const { vdom, doctype, clientHeight, clientWidth } = instantâneo this.nodeCache = {}; registros; this.container = container; this.snapshot = snapshot; this.iframe = document.createElement('iframe'); (node) => { // Cache DOM const flowId = node.__flow && node.__flow.id; if (flowId) { this.nodeCache[flowId] = node; = Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'script' }); `${clientWidth}px`; this.iframe.style.height = `${clientHeight}px`; container.appendChild(iframe); doc.write(`<!doctype ${doctype}><html><head></head><body></body></html>`); doc.close(); doc.replaceChild(documentElement, doc.documentElement); this.execRecords();}
function execRecords(preDuration = 0) { const record = this.records.shift(); let node; , // Tratamento de 'atributos', 'input', 'checked', // 'focus', 'blur', 'play''pause' e outros eventos} this.execRecords(record.duration }); record.duration - preDuration) }}
A duração acima foi omitida no artigo acima. Você pode otimizar a suavidade da reprodução de acordo com sua própria otimização e ver se vários registros são apresentados como um quadro ou como estão.
O texto acima é todo o conteúdo deste artigo. Espero que seja útil para o estudo de todos. Também espero que todos apoiem a Rede VeVb Wulin.