录制和回放 Javascript
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8093961/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Record and replay Javascript
提问by Hakan
I know it is possible to record mouse movements, scrolling and keystrokes. But what about changes to the document? How can I record changes to the document?
我知道可以记录鼠标移动、滚动和击键。但是对文档的更改呢?如何记录对文档的更改?
Here is my try out. There must be a better more simple way to store all events?
这是我的尝试。一定有更好更简单的方法来存储所有事件吗?
I am thankful for all tips I can get!
我很感谢我能得到的所有提示!
<!DOCTYPE html>
<html>
<head>
<title>Record And replay javascript</title>
</head>
<body id="if_no_other_id_exist">
<div style="height:100px;background:#0F0" id="test1">click me</div>
<div style="height:100px;background:#9F9" class="test2">click me</div>
<div style="height:100px;background:#3F9" id="test3">click me</div>
<div style="height:100px;background:#F96" id="test4">click me</div>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script>
$(document).ready(function() {
var the_time_document_is_redy = new Date().getTime();
var the_replay = '';
$('div').live("click", function (){
var the_length_of_visit = new Date().getTime() - the_time_document_is_redy;
// check if the element that is clicked has an id
if (this.id)
{
the_replay =
the_replay
+
"setTimeout(\"$('#"
+
this.id
+
"').trigger('click')\","
+
the_length_of_visit
+
");"
;
alert (
"The following javascript will be included in the file in the replay version:\n\n"
+
the_replay
) // end alert
} // end if
// if it does not have an id, check if the element that is clicked has an class
else if (this.className)
{
// find the closest id to better target the element (needed in my application)
var closest_div_with_id = $(this).closest('[id]').attr('id');
the_replay =
the_replay
+
"setTimeout(\"$('#"
+
closest_div_with_id
+
" ."
+
this.className
+
"').trigger('click')\","
+
the_length_of_visit
+
");"
;
alert (
"The following javascript will be included in the file in the replay version:\n\n"
+
the_replay
) // end alert
} // end if
});
// fall back if there are no other id's
$('body').attr('id','if_no_other_id_exist');
// example of how it will work in the replay version
setTimeout("$('#test1').trigger('click')",10000);
});
</script>
</body>
</html>
采纳答案by WTK
Replaying user actions with just Javascript is a complex problem.
仅使用 Javascript 重放用户操作是一个复杂的问题。
First of all, you can't move mouse cursor, you can't emulate mouseover/hovers also. So there goes away a big part of user interactions with a page.
首先,您不能移动鼠标光标,也不能模拟鼠标悬停/悬停。因此,用户与页面的大部分交互都消失了。
Second of all, actions, once recorded, for most of the time they have to be replayed in different environment than they were recorded in the first place. I mean you can replay the actions on screen with smaller resolutions, different client browser, different content served based on replaying browser cookies etc.
其次,动作一旦记录下来,大部分时间都必须在与最初记录的环境不同的环境中重放。我的意思是您可以使用较小的分辨率、不同的客户端浏览器、基于重放浏览器 cookie 提供的不同内容等在屏幕上重放操作。
If you take a time to study available services that enable you to record website visitors actions ( http://clicktale.com, http://userfly.com/to name a few), you'll see that none of them are capable of fully replaying users actions, especially when it comes to mouseovers, ajax, complex JS widgets.
如果您花时间研究可以记录网站访问者操作的可用服务(http: //clicktale.com、http://userfly.com/仅举几例),您会发现它们都没有能力完全重播用户操作,尤其是在涉及鼠标悬停、ajax、复杂的 JS 小部件时。
As to your question for detecting changes made to the DOM - as Chris Biscardi stated in his answer, there are mutation events, that are designed for that. However, keep in mind, that they are not implemented in every browser. Namely, the IE doesn't support them (they will be supported as of IE 9, according to this blog entry on msdn http://blogs.msdn.com/b/ie/archive/2010/03/26/dom-level-3-events-support-in-ie9.aspx).
至于您检测对 DOM 所做更改的问题 - 正如 Chris Biscardi 在他的回答中所说,有突变事件,专为此而设计。但是,请记住,并非在每个浏览器中都实现了它们。也就是说,IE 不支持它们(根据 msdn http://blogs.msdn.com/b/ie/archive/2010/03/26/dom- level-3-events-support-in-ie9.aspx)。
Relying on those events may be suitable for you, depending on use case.
依赖于这些事件可能适合您,具体取决于用例。
As to "better more simple way to store all events". There are other ways (syntax wise), of listening for events of your choice, however handling (= storing) them can't be handled in simple way, unless you want to serialize whole event objects which wouldn't be a good idea, if you were to send information about those event to some server to store them. You have to be aware of the fact, that there are massive amount of events popping around while using website, hence massive amount of potential data to be stored/send to the server.
至于“更好更简单的方法来存储所有事件”。还有其他方法(语法明智)可以侦听您选择的事件,但是处理(= 存储)它们不能以简单的方式处理,除非您想序列化整个事件对象,这不是一个好主意,如果您要将有关这些事件的信息发送到某个服务器以存储它们。您必须意识到这样一个事实,即在使用网站时会出现大量事件,因此有大量潜在数据要存储/发送到服务器。
I hope I made myself clear and you find some of those information useful. I myself have been involved in project that aimed to do what you're trying to achive, so I know how complicated can it get once you start digging into the subject.
我希望我说清楚了,你会发现其中一些信息很有用。我本人参与了旨在完成您想要实现的目标的项目,所以我知道一旦您开始深入研究该主题,它会变得多么复杂。
回答by david_adler
I became curious by this question and implemented a proof of concept here
我对这个问题感到好奇并在此处实施了概念证明
https://codesandbox.io/s/jquery-playground-y46pv?fontsize=14&hidenavigation=1&theme=dark
https://codesandbox.io/s/jquery-playground-y46pv?fontsize=14&hidenavigation=1&theme=dark
Using the demo
使用演示
- Try pressing record, clicking around, press record again and then click play.
- Play creates an
<iframe>
, injects the original HTML and replays the user events. - To change zoom change the
REPLAY_SCALE
variable in the source code. - To change the playback speed change the
SPEED
variable in the source code. - NB I only tested the jsbin on chrome 56.
- 尝试按记录,单击周围,再次按记录,然后单击播放。
- Play 创建一个
<iframe>
,注入原始 HTML 并重播用户事件。 - 要更改缩放,请更改
REPLAY_SCALE
源代码中的变量。 - 要更改播放速度,请更改
SPEED
源代码中的变量。 - 注意我只在 chrome 56 上测试了 jsbin。
Implementation details:
实施细则:
- It supports mousemove, click and typing. I omitted others (scroll, window resizing, hover, focus etc etc) but should be easily extensible.
- The event listeners bypass any
event.stopPropagation()
by using capture when listening for events on the document. - Displaying playback in a different resolution is done using
zoom
CSS3. - A transparent canvas could be overlaid to draw the trace lines of the mouse. I use just a simple div so no trace lines.
- 它支持鼠标移动、点击和打字。我省略了其他的(滚动、窗口大小调整、悬停、焦点等),但应该很容易扩展。
- 事件侦听器
event.stopPropagation()
在侦听文档上的事件时通过使用 capture 来绕过 any 。 - 使用
zoom
CSS3以不同的分辨率显示播放。 - 可以覆盖一个透明的画布来绘制鼠标的轨迹线。我只使用一个简单的 div,所以没有跟踪线。
Considerations:
注意事项:
Imagining we are capturing user events on a real website. Since the page served could change between now and the playback we can't rely on the client's server when replaying the recording in the iframe. Instead we have to snapshot the html, all ajax requests and resource requests made during the recording. In the demo I only snapshot the HTML for simplicity. However in practice, all extra requests would have to be stored on the server in realtime as they are downloaded on the client page. Furthermore, during playback it is important that the requests are played back with the same timeline that they were perceived by the user. To simulate the request timeline, the offset and duration of each request must also be stored. Uploading all page requests as they are downloaded on the client will slow down the client page. One way to optimize this uploading could be to md5 hash the contents of the request before they are uploaded, if the md5 hash is already present on the server, the request data need not be reuploaded. Furthermore, the session of one user can leverage the request data uploaded by another user using this hashing method.
Careful consideration will be needed when uploading all the events. Since lots of events will be generated, this means lots of data. Perhaps some compression of the events could be made e.g. losing some of the less important mousemove events. An upload request should not be made per event to minimize number of requests. The events should be buffered until a buffer size or timeout is reached before each batch of events is uploaded. A timeout should be used as the user could close the page at any point thus losing some events.
During playback outgoing POST requests should be mocked to prevent duplicating events elsewhere.
During playback the user agent should be spoofed but this may be unreliable in rendering the original display.
The custom recording code could conflict with client code. e.g. jquery. Namespacing will be required to avoid this.
There might be some edge cases where typing and clicking may not reproduce the same resulting HTML as seen in the original e.g. random numbers, date times. Mutation observers may be required to observe HTML changes, although not supported in all browsers. Screenshots could come in useful here but might prove OTT.
想象一下,我们正在一个真实的网站上捕捉用户事件。由于所提供的页面可能会在现在和播放之间发生变化,因此在 iframe 中重播录制内容时,我们不能依赖客户端的服务器。相反,我们必须对 html、录制期间发出的所有 ajax 请求和资源请求进行快照。在演示中,为了简单起见,我只对 HTML 进行了快照。然而在实践中,所有额外的请求都必须实时存储在服务器上,因为它们被下载到客户端页面上。此外,在回放期间,重要的是使用与用户感知到的时间线相同的时间线回放请求。为了模拟请求时间线,还必须存储每个请求的偏移量和持续时间。在客户端下载所有页面请求时上传它们会减慢客户端页面的速度。优化此上传的一种方法可能是在上传请求内容之前对其进行 md5 散列,如果服务器上已经存在 md5 散列,则不需要重新上传请求数据。此外,一个用户的会话可以利用另一个用户使用这种散列方法上传的请求数据。
上传所有事件时需要仔细考虑。由于将生成大量事件,这意味着大量数据。也许可以对事件进行一些压缩,例如丢失一些不太重要的 mousemove 事件。不应针对每个事件发出上传请求,以尽量减少请求数量。在上传每批事件之前,应缓冲事件,直到达到缓冲区大小或超时。应该使用超时,因为用户可以在任何时候关闭页面从而丢失一些事件。
在播放输出 POST 请求期间应该被模拟以防止在别处复制事件。
在播放期间,用户代理应该被欺骗,但这在呈现原始显示时可能不可靠。
自定义录制代码可能与客户端代码冲突。例如jQuery。需要命名空间来避免这种情况。
在某些极端情况下,键入和单击可能无法重现与原始 HTML 相同的结果 HTML,例如随机数、日期时间。变异观察者可能需要观察 HTML 变化,尽管并非所有浏览器都支持。屏幕截图在这里可能有用,但可能会证明 OTT。
回答by Chris Biscardi
I believe you are looking for Mutation Events.
我相信您正在寻找突变事件。
Here are some resources for you:
这里有一些资源供您使用:
http://forum.jquery.com/topic/mutation-events-12-1-2010
http://forum.jquery.com/topic/mutation-events-12-1-2010
https://github.com/jollytoad/jquery.mutation-events
https://github.com/jollytoad/jquery.mutation-events
Update:
更新:
In Response to comment, a very, very basic implementation:
回应评论,一个非常非常基本的实现:
//callback function
function onNodeInserted(){alert('inserted')}
//add listener to dom(in this case the body tag)
document.body.addEventListener ('DOMNodeInserted', onNodeInserted, false);
//Add element to dom
$('<div>test</div>').appendTo('body')
Like WTK said, you are getting yourself into complex territory.
就像 WTK 所说的,您正在进入复杂的领域。
回答by Elheni Mokhles
Record
记录
Save the initial DOM of the page, remove the scripts from it and also you need to change all relative URLs to absolute ones.
保存页面的初始 DOM,从中删除脚本,您还需要将所有相对 URL 更改为绝对 URL。
Then, record DOM mutations and Keyboard/Mouse event.
然后,记录 DOM 变化和键盘/鼠标事件。
Replay
重播
Start with initial saved DOM, apply mutations and events using timestamp order.
从初始保存的 DOM 开始,使用时间戳顺序应用更改和事件。
In fact, clicks will not do anything because we have removed any scripts. but because we have saved the DOM changes we can replay the effect after the click.
事实上,点击不会做任何事情,因为我们已经删除了任何脚本。但是因为我们已经保存了 DOM 更改,所以我们可以在点击后重播效果。
回答by Aftab Naveed
I found these two solutions on github which allows your to capture the events and then replay that on a remote server.
我在 github 上找到了这两个解决方案,它允许您捕获事件,然后在远程服务器上重播。
https://github.com/ElliotNB/js-replay
https://github.com/ElliotNB/js-replay
and a more comprehensive solution
和更全面的解决方案
https://github.com/rrweb-io/rrweb
https://github.com/rrweb-io/rrweb
Both has demos which you can try.
两者都有您可以尝试的演示。
回答by NVRM
Lately, we can now use MutationObserver
最近,我们现在可以使用MutationObserver
MutationObserver provides developers with a way to react to changes in a DOM. It is designed as a replacement for Mutation Events defined in the DOM3 Events specification.
MutationObserver 为开发人员提供了一种对 DOM 中的变化做出反应的方法。它旨在替代 DOM3 事件规范中定义的突变事件。
Slow demo, because the console.log message is huge.
缓慢的演示,因为 console.log 消息很大。
var mutationObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation)
})
})
mutationObserver.observe(watchme, {
attributes: true,
characterData: true,
childList: true,
subtree: true,
attributeOldValue: true,
characterDataOldValue: true
})
<div id="watchme" contenteditable>
Hello world!
</div>