بعد قراءة التعليقات، أدركت فجأة أنني لم أشرحها مسبقًا. يمكن القول أن هذه المقالة عبارة عن مقالة بحثية وتعليمية، وأعتقد أنها قابلة للتطبيق لقد كانت مفتوحة المصدر لتكملة معرفتي الخاصة. هناك بعض التفاصيل المفقودة، لذا يمكنك التعامل معها كنص تعليمي واستخدامها بحذر في بيئات الإنتاج.
تسجيل الشاشة لإعادة إنتاج سيناريو الخطأإذا كان تطبيقك متصلاً بنظام web apm، فقد تعلم أن نظام apm يمكن أن يساعدك في التقاط الأخطاء التي لم يتم اكتشافها والتي تحدث في الصفحة، وتوفير مكدس أخطاء، ومساعدتك في تحديد موقع الخطأ. ومع ذلك، في بعض الأحيان، عندما لا تعرف العملية المحددة للمستخدم، لا توجد طريقة لإعادة إنتاج الخطأ. في هذا الوقت، إذا كان هناك تسجيل لشاشة العملية، فيمكنك فهم مسار تشغيل المستخدم بوضوح، وبالتالي إعادة إنتاج الخطأ. علة.
أفكار التنفيذ الفكرة 1: استخدم Canvas لالتقاط لقطات الشاشةهذه الفكرة بسيطة نسبيًا، وهي استخدام Canvas لرسم محتوى الويب، والمكتبات الأكثر شهرة هي: html2canvas.
هذا التنفيذ معقد نسبيًا، ولكن يمكننا استخدامه مباشرة حتى نتمكن من الحصول على لقطة شاشة لصفحة الويب التي نريدها.
لكي نجعل الفيديو الذي تم إنشاؤه أكثر سلاسة، نحتاج إلى إنشاء حوالي 25 إطارًا في الثانية، مما يعني أننا بحاجة إلى 25 لقطة شاشة. المخطط الانسيابي للفكرة هو كما يلي:
ومع ذلك، فإن هذه الفكرة بها العيب الأكثر خطورة: لكي نجعل الفيديو سلسًا، نحتاج إلى 25 صورة في ثانية واحدة، وصورة واحدة بحجم 300 كيلو بايت. عندما نحتاج إلى فيديو مدته 30 ثانية، يكون الحجم الإجمالي للصور 220 ميجا بايت. من الواضح أن مثل هذه الحملات الكبيرة على الشبكة لا.
الفكرة 2: تسجيل تكرار كافة العملياتمن أجل تقليل حمل الشبكة، نقوم بتغيير تفكيرنا ونسجل العمليات التالية خطوة بخطوة بناءً على الصفحة الأولية. عندما نحتاج إلى اللعب، نقوم بتطبيق هذه العمليات بالترتيب، حتى نتمكن من رؤية التغييرات في صفحة. تفصل هذه الفكرة بين عمليات الماوس وتغييرات DOM:
تغييرات الماوس:
بالطبع، هذا الشرح موجز نسبيًا، ولن نخوض في التفاصيل بشكل أساسي.
أول لقطة كاملة للصفحة في البداية، قد تعتقد أنه للحصول على لقطة كاملة للصفحة، يمكنك استخدام outerHTML
مباشرة
محتوى ثابت = document.documentElement.outerHTML؛
يؤدي هذا ببساطة إلى تسجيل كل عناصر DOM الخاصة بالصفحة، ما عليك سوى إضافة معرف العلامة إلى DOM أولاً، ثم الحصول على HTML الخارجي، ثم إزالة البرنامج النصي JS.
ومع ذلك، هناك مشكلة هنا. سوف يقوم DOM المسجل باستخدام outerHTML
بدمج عقدتين نصيتين متجاورتين في عقدة واحدة. عندما نراقب تغييرات DOM لاحقًا، سنستخدم MutationObserver
. في هذا الوقت، تحتاج إلى الكثير من المعالجة لتكون متوافقة مع هذا الدمج TextNodes، وإلا فلن تتمكن من تحديد موقع العقدة المستهدفة للعملية أثناء عملية الاستعادة.
إذًا، هل هناك أي طريقة يمكننا من خلالها الحفاظ على البنية الأصلية لصفحة DOM؟
الإجابة هي نعم. هنا نستخدم Virtual DOM لتسجيل بنية DOM، وتحويل documentElement إلى Virtual DOM، وتسجيله، وإعادة إنشاء DOM عند استعادته لاحقًا.
تحويل DOM إلى DOM الظاهري نحتاج فقط إلى الاهتمام بنوعين من العقد هنا: Node.TEXT_NODE
و Node.ELEMENT_NODE
. في الوقت نفسه، تجدر الإشارة إلى أن إنشاء العناصر الفرعية SVG وSVG يتطلب استخدام API: createElementNS، لذلك عندما نسجل Virtual DOM، نحتاج إلى الانتباه إلى سجل مساحة الاسم:
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';const XML_NAMESPACES = ['xmlns', 'xmlns:svg', 'xmlns:xlink'];وظيفة createVirtualDom(element, isSVG = false) { التبديل (element.nodeType) { الحالة Node.TEXT_NODE: العودة createVirtualText(element); case Node.ELEMENT_NODE: return createVirtualElement(element, isSVG || element.tagName.toLowerCase() === 'svg'); النص: element.nodeValue، النوع: 'VirtualText'، }؛ !== 'undef') { vText.__flow = element.__flow; } return vText;}function createVirtualElement(element, isSVG = false) { const tagName = element.tagName.toLowerCase(); const { attr, namespace } = getNodeAttributes(element, isSVG); tagName، اكتب: 'VirtualElement'، الأطفال، السمات: attr، namespace، }؛ 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 السمات = element.attributes [...element.attributes] : []; attributes.forEach(({nodeName,NodeValue }) => { attr[nodeName] =NodeValue;if (XML_NAMESPACES.includes(nodeName)) { namespace =NodeValue; } else if (isSVG) { namespace = SVG_NAMESPACE; } }); العودة {attr، مساحة الاسم }؛}
من خلال الكود أعلاه، يمكننا تحويل عنصر المستند بأكمله إلى Virtual DOM، حيث يتم استخدام __flow لتسجيل بعض المعلمات، بما في ذلك معرف العلامة، وما إلى ذلك. سجلات العقدة الافتراضية: النوع، والسمات، والأطفال، ومساحة الاسم.
تمت استعادة DOM الظاهري إلى DOMمن السهل نسبيًا استعادة Virtual DOM إلى DOM، ما عليك سوى إنشاء DOM بشكل متكرر. يتم استخدام NodeFilter لتصفية عناصر البرنامج النصي لأننا لا نحتاج إلى تنفيذ البرامج النصية JS.
function createElement(vdom,nodeFilter = () => true) { Let العقدة؛ === 'غير محدد'؟ document.createElement(vdom.tagName) : document.createElementNS(vdom.namespace, vdom.tagName); 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 } عقدة الإرجاع؛}مراقبة تغيير هيكل DOM
هنا، نستخدم واجهة برمجة التطبيقات: MutationObserver، والأمر الأكثر إرضاءً هو أن واجهة برمجة التطبيقات هذه متوافقة مع جميع المتصفحات، حتى نتمكن من استخدامها بجرأة.
باستخدام MutationObserver:
const options = { ChildList: true, // ما إذا كان سيتم ملاحظة التغييرات في الشجرة الفرعية للعقد الفرعية: صحيح، // ما إذا كان سيتم ملاحظة التغييرات في جميع سمات العقد التابعة: صحيح، // ما إذا كان سيتم ملاحظة التغييرات في السمات attributeOldValue: true، // سواء لمراقبة التغييرات في السمات القيمة القديمة المتغيرة CharacterData: true، // ما إذا كان محتوى العقدة أو نص العقدة يتغير CharacterDataOldValue: true، // ما إذا كان محتوى العقدة أو نص العقدة يغير القيمة القديمة // attributeFilter: ['class', ' سرك'] سيتم تجاهل الخصائص غير الموجودة في هذه المصفوفة عند التغيير};const Observer = new MutationObserver((mutationList) => { // MutationList: مجموعة من الطفرات});observer.observe(document.documentElement, options);
إنه سهل الاستخدام للغاية. ما عليك سوى تحديد العقدة الجذرية وبعض الخيارات التي تحتاج إلى مراقبتها. ثم عندما يتغير DOM، ستكون هناك قائمة طفرة في وظيفة رد الاتصال، وهي قائمة بتغييرات DOM الطفرة هي تقريبًا:
{ النوع: 'childList'، // أو CharacterData، السمات المستهدفة: <DOM>، // معلمات أخرى}
نستخدم مصفوفة لتخزين الطفرات. رد الاتصال المحدد هو:
const onMutationChange = (mutationsList) => { const getFlowId = (node) => { if (node) { // لا يحتوي DOM المدرج حديثًا على علامة، لذلك يجب أن يكون متوافقًا هنا if (!node.__flow) العقدة.__flow = { id: uuid() }; return العقدة.__flow.id } }; سجل ثابت = { النوع، الهدف: getFlowId(target)، }؛ التبديل (النوع) { 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)، PrevSibling: getFlowId(n.previousSibling) });break; } this.records.push(record); });} function takeSnapshot(node, options = {}) { this.markNodes(node); const snapshot = { vdom: createVirtualDom(node), }; if (options.doctype === true) { snapshot.doctype = document.doctype.name; snapshot.clientWidth = document.body.clientWidth; snapshot.clientHeight = document.body.clientHeight } لقطة الإرجاع;
كل ما عليك فعله هو ملاحظة أنه عند معالجة DOM جديد، فأنت بحاجة إلى لقطة تزايدية. هنا لا تزال تستخدم Virtual DOM للتسجيل، وعندما تقوم بتشغيله لاحقًا، ستستمر في إنشاء DOM وإدراجه في العنصر الأصلي، لذا هنا تحتاج إلى الرجوع إلى DOM، وهي العقدة الشقيقة.
مراقبة عناصر النموذجلا يمكن لـ MutationObserver أعلاه مراقبة تغيرات قيمة المدخلات والعناصر الأخرى، لذلك نحتاج إلى إجراء معالجة خاصة لقيم عناصر النموذج.
الاستماع إلى حدث oninputوثائق MDN: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/oninput
كائنات الحدث: التحديد والإدخال ومنطقة النص
window.addEventListener('input', this.onFormInput, true);onFormInput = (event) => { const target = events.target; .includes(target.tagName.toLowerCase()) ) { this.records.push({ type: 'input', target: target.__flow.id, value: target.value, });
استخدم الالتقاط لالتقاط الأحداث على النافذة، ويتم ذلك أيضًا لاحقًا والسبب في ذلك هو أننا قد نمنع الفقاعات في كثير من الأحيان أثناء مرحلة الفقاعات لتنفيذ بعض الوظائف، لذا فإن استخدام الالتقاط يمكن أن يقلل من فقدان الأحداث، مثل أحداث التمرير لن يحدث فقاعات ويجب التقاطها.
الاستماع إلى حدث onchangeوثائق MDN: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/oninput
لا يمكن لحدث الإدخال تلبية مراقبة مربع اختيار النوع والراديو، لذلك من الضروري استخدام حدث onchange للمراقبة.
window.addEventListener('change', this.onFormChange, true);onFormChange = (event) => { const target = events.target; if (target && target.__flow) { if ( target.tagName.toLowerCase() == = 'input' && ['checkbox', 'radio'].includes(target.getAttribute('type')) ) { this.records.push({ type: "محدد"، الهدف: target.__flow.id، محدد: target.checked، } } }});الاستماع إلى حدث التركيز
وثائق MDN: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onfocus
window.addEventListener('focus', this.onFormFocus, true);onFormFocus = (event) => { const target = events.target; if (target && target.__flow) { this.records.push({ type: 'focus '، الهدف: الهدف.__flow.id، }});الاستماع إلى الحدث onblur
وثائق MDN: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onblur
window.addEventListener('blur', this.onFormBlur, true); onFormBlur = (event) => { const target = events.target; '، الهدف: target.__flow.id، }});مراقبة تغيير عنصر الوسائط
يشير هذا إلى الصوت والفيديو، كما هو الحال مع عناصر النموذج أعلاه، يمكنك مراقبة أحداث التشغيل والإيقاف المؤقت وتحديث الوقت وتغيير الحجم والأحداث الأخرى، ثم تخزينها في السجلات.
مراقبة تغيير قماش القماشلا يتم طرح أي حدث عندما يتغير محتوى اللوحة، لذا يمكننا:
اجمع عناصر اللوحة وقم بتحديث المحتوى في الوقت الفعلي بانتظام. اخترق بعض واجهات برمجة التطبيقات للرسم لرمي الأحداث.
إن البحث حول مراقبة القماش ليس متعمقًا للغاية وهناك حاجة إلى مزيد من البحث المتعمق.
يلعبالفكرة بسيطة نسبيًا، ما عليك سوى الحصول على بعض المعلومات من الواجهة الخلفية:
باستخدام هذه المعلومات، يمكنك أولاً إنشاء صفحة DOM، والتي تتضمن تصفية علامات البرنامج النصي، ثم إنشاء إطار iframe وإلحاقه بحاوية تستخدم خريطة لتخزين DOM
function play(options = {}) { const { Container, Records = [], snapshot ={} } = options; const { vdom, doctype,clientHeight,clientWidth } = snapshot; this.nodeCache = {}; Records; this.container = Container; this.snapshot = snapshot; this.iframe = document.createElement('iframe'); (node) => { // ذاكرة التخزين المؤقت DOM constflowId =node.__flow &&node.__flow.id; = Node.ELEMENT_NODE &&node.tagName.toLowerCase() === 'script' }); this.iframe.style.width = `${clientWidth}px`; this.iframe.style.height = `${clientHeight}px`; doc.write(`<!doctype ${doctype}><html><head></head><body></body></html>`); doc. Close(); doc.replaceChild(documentElement, doc.documentElement); this.execRecords();}
دالة execRecords(preDuration = 0) { const Record = this.records.shift(); , // التعامل مع "السمات"، و"الإدخال"، و"المحدد"، و// "التركيز"، و"التمويه"، و"تشغيل" و"إيقاف مؤقت" والأحداث الأخرى} this.execRecords(record.duration }); مدة التسجيل - مدة ما قبل) }}
تم حذف المدة المذكورة أعلاه في المقالة أعلاه. يمكنك تحسين سلاسة التشغيل وفقًا للتحسين الخاص بك، ومعرفة ما إذا كان يتم تقديم سجلات متعددة كإطار واحد أو كما هي.
ما ورد أعلاه هو المحتوى الكامل لهذه المقالة وآمل أن يكون مفيدًا لدراسة الجميع وآمل أيضًا أن يدعم الجميع شبكة VeVb Wulin.