Nachdem ich die Kommentare gelesen hatte, wurde mir plötzlich klar, dass es sich bei diesem Artikel um einen Forschungs- und Lernartikel handelte, den ich für machbar halte Ich war Open Source, um mein eigenes Wissen zu ergänzen. Es fehlen einige Details, sodass Sie es als Lerntext betrachten und in Produktionsumgebungen mit Vorsicht verwenden können.
Bildschirmaufzeichnung zur Reproduktion des FehlerszenariosWenn Ihre Anwendung mit dem Web-APM-System verbunden ist, wissen Sie möglicherweise, dass das APM-System Ihnen dabei helfen kann, nicht erkannte Fehler, die auf der Seite auftreten, zu erfassen, einen Fehlerstapel bereitzustellen und Ihnen bei der Suche nach dem Fehler zu helfen. Wenn Sie jedoch den spezifischen Vorgang des Benutzers nicht kennen, gibt es zu diesem Zeitpunkt keine Möglichkeit, den Fehler zu reproduzieren. Wenn zu diesem Zeitpunkt eine Vorgangsbildschirmaufzeichnung vorliegt, können Sie den Vorgangspfad des Benutzers klar verstehen und so den Vorgang reproduzieren Fehler. Und reparieren.
Umsetzungsideen Idee 1: Verwenden Sie Canvas, um Screenshots zu machenDiese Idee ist relativ einfach, nämlich Canvas zum Zeichnen von Webinhalten zu verwenden. Die bekannteren Bibliotheken sind: html2canvas. Das einfache Prinzip dieser Bibliothek ist:
Diese Implementierung ist relativ kompliziert, aber wir können sie direkt verwenden, um den Screenshot der gewünschten Webseite zu erhalten.
Um das generierte Video flüssiger zu machen, müssen wir etwa 25 Bilder pro Sekunde generieren, was bedeutet, dass wir 25 Screenshots benötigen. Das Flussdiagramm der Idee sieht wie folgt aus:
Diese Idee weist jedoch den schwerwiegendsten Fehler auf: Um das Video flüssig zu machen, benötigen wir 25 Bilder in einer Sekunde, und ein Bild ist 300 KB groß. Wenn wir ein 30-Sekunden-Video benötigen, beträgt die Gesamtgröße der Bilder 220 MB. Ein solch großer Netzwerkaufwand ist offensichtlich nicht der Fall.
Idee 2: Zeichnen Sie die Wiederholung aller Vorgänge aufUm den Netzwerkaufwand zu reduzieren, zeichnen wir die nächsten Schritt-für-Schritt-Vorgänge basierend auf der ersten Seite auf. Wenn wir spielen müssen, wenden wir diese Vorgänge der Reihe nach an, damit wir die Änderungen sehen können Seite. Diese Idee trennt Mausoperationen und DOM-Änderungen:
Mausänderungen:
Natürlich ist diese Erklärung relativ einfach. Wir werden nicht auf Details eingehen. Wir werden hauptsächlich die Implementierungsideen der DOM-Überwachung erläutern.
Der erste vollständige Schnappschuss der Seite Zunächst denken Sie vielleicht, dass Sie outerHTML
direkt verwenden können, um einen vollständigen Schnappschuss der Seite zu erhalten
const content = document.documentElement.outerHTML;
Dadurch wird einfach das gesamte DOM der Seite aufgezeichnet. Sie müssen lediglich zuerst die Tag-ID zum DOM hinzufügen, dann das äußere HTML abrufen und dann das JS-Skript entfernen.
Hier gibt es jedoch ein Problem, bei dem zwei benachbarte TextNodes zu einem Knoten zusammengeführt werden. Wenn wir anschließend DOM outerHTML
Änderungen MutationObserver
, ist ein großer Verarbeitungsaufwand erforderlich, um mit dieser Zusammenführung kompatibel zu sein von TextNodes, andernfalls können Sie den Zielknoten des Vorgangs während des Wiederherstellungsvorgangs nicht finden.
Gibt es also eine Möglichkeit, die ursprüngliche Struktur des Seiten-DOM beizubehalten?
Die Antwort lautet: Ja. Hier verwenden wir Virtual DOM, um die DOM-Struktur aufzuzeichnen, documentElement in Virtual DOM umzuwandeln, es aufzuzeichnen und das DOM bei der späteren Wiederherstellung neu zu generieren.
Konvertieren Sie DOM in virtuelles DOM Wir müssen uns hier nur um zwei Knotentypen kümmern: Node.TEXT_NODE
und Node.ELEMENT_NODE
. Gleichzeitig ist zu beachten, dass die Erstellung von SVG- und SVG-Unterelementen die Verwendung der API erfordert: createElementNS. Daher müssen wir beim Aufzeichnen von Virtual DOM auf den Namespace-Datensatz achten.
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'); function createVirtualText(element) { const vText = { text: element.nodeValue, Typ: 'VirtualText', }; if (typeof element.__flow !== 'undefiniert') { 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); tagName, Typ: 'VirtualElement', Kinder, Attribute: attr, Namespace, }; if (typeof element.__flow !== 'undefiniert') { 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 attributes = element.attributes ? [...element.attributes] : []; 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 };}
Mit dem obigen Code können wir das gesamte documentElement in ein virtuelles DOM konvertieren, in dem __flow zum Aufzeichnen einiger Parameter verwendet wird, einschließlich Tag-ID usw. Virtuelle Knotendatensätze: Typ, Attribute, untergeordnete Elemente, Namespace.
Virtuelles DOM im DOM wiederhergestelltEs ist relativ einfach, das virtuelle DOM im DOM wiederherzustellen. Sie müssen das DOM nur rekursiv erstellen. Der nodeFilter wird zum Filtern von Skriptelementen verwendet, da wir keine Ausführung von JS-Skripten benötigen.
function createElement(vdom, nodeFilter = () => true) { let node; if (vdom.type === 'VirtualText') { node = document.createTextNode(vdom.text); === 'undefiniert' ? document.createElement(vdom.tagName) : document.createElementNS(vdom.namespace, vdom.tagName); for (let name in vdom.attributes) { node.setAttribute(name, vdom.attributes[name]); } vdom.children.forEach((cnode) => { const childNode = createElement(cnode, nodeFilter); if (childNode && nodeFilter(childNode) ) { node.appendChild(childNode); } } } if (vdom.__flow) { node.__flow = vdom.__flow; } return node;}Überwachung von DOM-Strukturänderungen
Hier verwenden wir die API: MutationObserver. Noch erfreulicher ist, dass diese API mit allen Browsern kompatibel ist, sodass wir sie problemlos verwenden können.
Verwendung von MutationObserver:
const options = { childList: true, // Ob Änderungen im Unterbaum der untergeordneten Knoten beobachtet werden sollen: true, // Ob Änderungen in allen untergeordneten Knoten beobachtet werden sollen. Attribute: true, // Ob Änderungen in Attributen beobachtet werden sollen. attributeOldValue: true, // Ob um Änderungen in Attributen zu beobachten. Der geänderte alte Wert. CharacterData: true, // Ob sich der Knoteninhalt oder der Knotentext ändert. CharacterDataOldValue: true, // Ob der Knoteninhalt oder der Knotentext den alten Wert ändert. // attributeFilter: ['class', ' src'] Eigenschaften, die nicht in diesem Array enthalten sind, werden beim Ändern ignoriert};const Observer = new MutationObserver((mutationList) => { // mutationList: array of mutation});observer.observe(document.documentElement, options);
Es ist sehr einfach zu verwenden. Sie müssen lediglich einen Stammknoten und einige zu überwachende Optionen angeben. Wenn sich das DOM ändert, gibt es eine Mutationsliste, die eine Liste der DOM-Änderungen darstellt der Mutation beträgt ungefähr:
{ Typ: 'childList', // oder Charakterdaten, Attribute Ziel: <DOM>, // andere Parameter}
Wir verwenden ein Array zum Speichern von Mutationen. Der spezifische Rückruf lautet:
const onMutationChange = (mutationsList) => { const getFlowId = (node) => { if (node) { // Das neu eingefügte DOM hat keine Markierung, daher muss es hier kompatibel sein if (!node.__flow) node.__flow = { id: uuid() }; return node.__flow.id; } }; mutation; const record = { type, target: getFlowId(target), }; switch (type) { case 'characterData': record.value = target.nodeValue; attributeValue = target.getAttribute(attributeName); break; case 'childList': record.removedNodes = [...mutation.removedNodes].map(n => getFlowId(n)); record.addedNodes = [...mutation.addedNodes].map((n) => { const snapshot = this.takeSnapshot(n); return { ...snapshot, nextSibling: getFlowId(n. nextSibling), previousSibling: getFlowId(n. previousSibling) }; });}function takeSnapshot(node, options = {}) { this.markNodes(node); { vdom: createVirtualDom(node), }; if (options.doctype === true) { snapshot.doctype = document.doctype.name; snapshot.clientWidth = document.body.clientHeight = document.body.clientHeight } return snapshot;}
Sie müssen hier nur beachten, dass Sie beim Verarbeiten eines neuen DOM einen inkrementellen Snapshot benötigen. Hier verwenden Sie immer noch Virtual DOM zum Aufzeichnen. Wenn Sie es später abspielen, generieren Sie immer noch das DOM und fügen es in das übergeordnete Element ein Sie müssen auf das DOM verweisen, das den Geschwisterknoten darstellt.
Überwachung von FormularelementenDer obige MutationObserver kann die Wertänderungen von Eingabe- und anderen Elementen nicht überwachen, daher müssen wir eine spezielle Verarbeitung der Werte von Formularelementen durchführen.
OnInput-EreignisüberwachungMDN-Dokumentation: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/oninput
Ereignisobjekte: Auswahl, Eingabe, Textbereich
window.addEventListener('input', this.onFormInput, true);onFormInput = (event) => { const target = event.target if ( target && target.__flow && ['select', 'textarea', 'input' ].includes(target.tagName.toLowerCase()) ) { this.records.push({ type: 'input', target: target.__flow.id, Wert: target.value, });
Verwenden Sie die Erfassung, um Ereignisse im Fenster zu erfassen. Der Grund dafür ist, dass wir das Bubbling während der Bubbling-Phase verhindern können, sodass die Verwendung der Capture-Funktion den Verlust von Ereignissen reduzieren kann Es wird keine Blasen bilden und muss eingefangen werden.
Onchange-EreignisüberwachungMDN-Dokumentation: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/oninput
Das Eingabeereignis kann die Überwachung des Typs Kontrollkästchen und Radio nicht erfüllen. Daher muss zur Überwachung das Ereignis onchange verwendet werden.
window.addEventListener('change', this.onFormChange, true);onFormChange = (event) => { const target = event.target; if (target && target.__flow) { if ( target.tagName.toLowerCase() == = 'input' && ['checkbox', 'radio'].includes(target.getAttribute('type')) ) { this.records.push({ type: 'geprüft', Ziel: target.__flow.id, geprüft: Ziel.checked, });Onfocus-Event-Listening
MDN-Dokumentation: 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 ', Ziel: target.__flow.id, });Onblur-Ereignisüberwachung
MDN-Dokumentation: 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 ', Ziel: target.__flow.id, });Überwachung der Änderung von Medienelementen
Dies bezieht sich auf Audio und Video. Ähnlich wie bei den oben genannten Formularelementen können Sie Onplay-, Onpause-Ereignisse, Zeitaktualisierungen, Lautstärkeänderungen und andere Ereignisse überwachen und diese dann in Datensätzen speichern.
Überwachung von Canvas-Canvas-ÄnderungenWenn sich der Canvas-Inhalt ändert, wird kein Ereignis ausgelöst, sodass wir Folgendes tun können:
Sammeln Sie Canvas-Elemente und aktualisieren Sie regelmäßig Echtzeitinhalte. Hacken Sie einige Zeichnungs-APIs, um Ereignisse auszulösen.
Die Forschung zur Canvas-Überwachung ist nicht sehr tiefgreifend und es bedarf weiterer eingehender Forschung.
spielenDie Idee ist relativ einfach, holen Sie sich einfach ein paar Informationen aus dem Backend:
Mit diesen Informationen können Sie zunächst das Seiten-DOM generieren, das Filterskript-Tags enthält, dann einen Iframe erstellen und ihn an einen Container anhängen, der eine Karte zum Speichern des DOM verwendet
function play(options = {}) { const { Container, Records = [], Snapshot ={} } = Options { vdom, doctype, clientHeight, clientWidth } = snapshot = {}; Records; this.container = container; this.iframe = document.createElement('iframe'); (node) => { // Cache DOM const flowId = node.__flow && node.__flow.id; if (flowId) { this.nodeCache[flowId] = node; } // Filterscript return !(node.nodeType == = Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'script' }); `${clientWidth}px`; this.iframe.style.height = `${clientHeight}px`; const doc = iframe.contentDocument = doc.open(); doc.write(`<!doctype ${doctype}><html><head></head><body></body></html>`); doc.close(); doc.replaceChild(documentElement, doc.documentElement);}
function execRecords(preDuration = 0) { const record = this.records.shift(); let node; if (record) { setTimeout(() => { switch (record.type) { // 'childList', 'characterData' , // Behandlung von 'attributes', 'input', 'checked', // 'focus', 'blur', 'play''pause' und anderen Ereignissen} this.execRecords(record.duration }, record.duration - preDuration) }}
Die obige Dauer wird im obigen Artikel weggelassen. Sie können die Wiedergabeglätte entsprechend Ihrer eigenen Optimierung optimieren und sehen, ob mehrere Datensätze als ein Frame oder so dargestellt werden.
Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass er für das Studium aller hilfreich ist. Ich hoffe auch, dass jeder das VeVb Wulin Network unterstützt.