rrweb
https://github.com/rrweb-io/rrweb/blob/master/guide.zh_CN.md
html页面直接引入,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>rrweb demo web site</title>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.js"></script>
<link rel="stylesheet" crossorigin="anonymous"
href="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/style.css" />
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/index.js"></script>
</head>
<body>
<div style="float: left;">
<h1>这里模拟一些html代码:</h1>
<label>用户名</label><input type="text" style="height: 30px;width: 200px;">
<label>密码</label><input type="password" style="height: 30px;width: 200px;">
<label>邮箱</label><input type="email" style="height: 30px;width: 200px;">
<label>备注</label><textarea style="height: 80px;width: 200px;"></textarea>
</div>
<div style="float: left;">
<h1>模拟背景颜色,用来检验录屏画质</h1>
<div style="width: 100%;height: 50px;background-color: red;"></div>
<div style="width: 100%;height: 50px;background-color: green;"></div>
<div style="width: 100%;height: 50px;background-color: #ff50d0;"></div>
<div style="width: 100%;height: 50px;background-color: #7eff1f;"></div>
</div>
<p></p>
<div style="float: left;">
<h1>视频回放</h1>
<div id="playback" style="width: 1000px;height: 500px;background-color: #cccccc"></div>
</div>
<button onclick="get_start()">点击录制</button>
<button onclick="playback()">点击播放</button>
</body>
<script>
//1.存放DOM节点数据
let events = [];
//2.点击录制
function get_start() {
//rrwebMin 为启动对象
//record() 方法启动录制
//emit会监听所有的DOM的动作, 鼠标等,
rrwebMin.record({
emit(event) {
// 用任意方式存 储 event
console.log(event);
events.push(event);
},
});
alert('正在录制')
}
//3.这一步,应该把数据转为JSON ,然后发送到后台存储,可以写个循环,10S发送一次,再定义一个窗口关闭事件监听,最后关闭时发送一次数据
//SON.stringify({ events });
//4.点击回放
function playback() {
new rrwebPlayer({
target: document.getElementById('playback'), // 可以自定义 DOM 元素
data: {events,},
});
}
</script>
</html>
- 打开index.html,点击录制开始录制
- 点击播放开始回访
- 这里关键数据就是events,他可以将操作动作回放出来,并且可以将它保存在数据库中
代码效果
vue
<template>
<div class="main">
<div class="container">
<div class="row">
<div class="col-25">
<label for="fname">First Name</label>
</div>
<div class="col-75">
<input type="text" name="firstname" placeholder="Your name.." />
</div>
</div>
<div class="row">
<div class="col-25">
<label for="lname">Last Name</label>
</div>
<div class="col-75">
<input type="text" name="lastname" placeholder="Your last name.." />
</div>
</div>
<div class="row">
<div class="col-25">
<label for="country">Country</label>
</div>
<div class="col-75">
<select id="country" name="country">
<option value="australia">Australia</option>
<option value="canada">Canada</option>
<option value="usa">USA</option>
</select>
</div>
</div>
<div class="row">
<div class="col-25">
<label for="subject">Subject</label>
</div>
<div class="col-75">
<textarea
id="subject"
name="subject"
placeholder="Write something.."
style="height: 200px"
></textarea>
</div>
</div>
<div class="row">
<input type="submit" value="Submit" />
</div>
</div>
<div class="rr-block action">
<div class="record">
<button class="btn-record" @click="startRecord()">开始录屏</button>
<button
class="btn-replay"
:disabled="events.length < 2"
@click="playRecord()"
>
回放录屏
</button>
</div>
<div class="reset">
<button @click="resetRecord()">重置录屏</button>
<button @click="saveRecord()">保存录屏</button>
<!-- <button @click="replaySavedRecord()">重置保存录屏</button>-->
</div>
</div>
<div id="rrweb" class="replay"></div>
</div>
</template>
<script>
import RrwebReplay from 'rrweb-player'
import { record } from 'rrweb'
export default {
data() {
return {
events: [],
recordFlag: false,
}
},
methods: {
startRecord() {
this.recordFlag = true
const self = this
record({
emit(event) {
if (self.recordFlag) {
self.events.push(event)
}
},
})
},
playRecord() {
const rrwebEl = document.getElementById('rrweb')
// eslint-disable-next-line no-new
new RrwebReplay({
target: rrwebEl,
data: {
events: this.events,
autoPlay: true,
},
})
},
resetRecord() {
this.recordFlag = false
this.events = []
const replayEl = document.getElementsByClassName('rr-player')[0]
if (replayEl) replayEl.remove()
},
saveRecord() {
const data = JSON.stringify(this.events)
window.localStorage.setItem('rrweb', data)
},
replaySavedRecord() {
const replayEl = document.getElementsByClassName('rr-player')[0]
if (replayEl) replayEl.remove()
const rrwebEl = document.getElementById('rrweb')
const savedEvents = window.localStorage.getItem('rrweb')
// eslint-disable-next-line no-new
new RrwebReplay({
target: rrwebEl,
data: {
events: JSON.parse(savedEvents),
autoPlay: true,
},
})
},
},
}
</script>
<style>
.main {
position: relative;
}
.replay {
top: 0;
left: 200px;
position: absolute;
}
.action {
padding-top: 20px;
}
.reset {
padding-top: 20px;
}
.container {
max-width: 1000px;
}
* {
box-sizing: border-box;
}
input[type='text'],
select,
textarea {
width: 100%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 4px;
resize: vertical;
}
label {
padding: 12px 12px 12px 0;
display: inline-block;
}
input[type='submit'] {
background-color: #4caf50;
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
float: right;
}
input[type='submit']:hover {
background-color: #45a049;
}
.container {
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
}
.col-25 {
float: left;
width: 25%;
margin-top: 6px;
}
.col-75 {
float: left;
width: 75%;
margin-top: 6px;
}
/* Clear floats after the columns */
.row:after {
content: '';
display: table;
clear: both;
}
.rr-controller.svelte-dxnc1j.svelte-dxnc1j {
width: 100%;
height: 80px;
background: #fff;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
border-radius: 0 0 5px 5px;
}
.rr-timeline.svelte-dxnc1j.svelte-dxnc1j {
width: 80%;
display: flex;
align-items: center;
}
.rr-timeline__time.svelte-dxnc1j.svelte-dxnc1j {
display: inline-block;
width: 100px;
text-align: center;
color: #11103e;
}
.rr-progress.svelte-dxnc1j.svelte-dxnc1j {
flex: 1;
height: 12px;
background: #eee;
position: relative;
border-radius: 3px;
cursor: pointer;
box-sizing: border-box;
border-top: solid 4px #fff;
border-bottom: solid 4px #fff;
}
.rr-progress.disabled.svelte-dxnc1j.svelte-dxnc1j {
cursor: not-allowed;
}
.rr-progress__step.svelte-dxnc1j.svelte-dxnc1j {
height: 100%;
position: absolute;
left: 0;
top: 0;
background: #e0e1fe;
}
.rr-progress__handler.svelte-dxnc1j.svelte-dxnc1j {
width: 20px;
height: 20px;
border-radius: 10px;
position: absolute;
top: 2px;
transform: translate(-50%, -50%);
background: rgb(73, 80, 246);
}
.rr-controller__btns.svelte-dxnc1j.svelte-dxnc1j {
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
}
.rr-controller__btns.svelte-dxnc1j button.svelte-dxnc1j {
width: 32px;
height: 32px;
display: flex;
padding: 0;
align-items: center;
justify-content: center;
background: none;
border: none;
border-radius: 50%;
cursor: pointer;
}
.rr-controller__btns.svelte-dxnc1j button.svelte-dxnc1j:active {
background: #e0e1fe;
}
.rr-controller__btns.svelte-dxnc1j button.active.svelte-dxnc1j {
color: #fff;
background: rgb(73, 80, 246);
}
.rr-controller__btns.svelte-dxnc1j button.svelte-dxnc1j:disabled {
cursor: not-allowed;
}
.replayer-wrapper {
position: relative;
}
.replayer-mouse {
position: absolute;
width: 20px;
height: 20px;
transition: 0.05s linear;
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
background-image: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjMwMCIgd2lkdGg9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBkYXRhLW5hbWU9IkxheWVyIDEiIHZpZXdCb3g9IjAgMCA1MCA1MCI+PHBhdGggZD0iTTQ4LjcxIDQyLjkxTDM0LjA4IDI4LjI5IDQ0LjMzIDE4YTEgMSAwIDAwLS4zMy0xLjYxTDIuMzUgMS4wNmExIDEgMCAwMC0xLjI5IDEuMjlMMTYuMzkgNDRhMSAxIDAgMDAxLjY1LjM2bDEwLjI1LTEwLjI4IDE0LjYyIDE0LjYzYTEgMSAwIDAwMS40MSAwbDQuMzgtNC4zOGExIDEgMCAwMC4wMS0xLjQyem0tNS4wOSAzLjY3TDI5IDMyYTEgMSAwIDAwLTEuNDEgMGwtOS44NSA5Ljg1TDMuNjkgMy42OWwzOC4xMiAxNEwzMiAyNy41OEExIDEgMCAwMDMyIDI5bDE0LjU5IDE0LjYyeiIvPjwvc3ZnPg==');
}
.replayer-mouse:after {
content: '';
display: inline-block;
width: 20px;
height: 20px;
border-radius: 10px;
background: #4950f6;
transform: translate(-10px, -10px);
opacity: 0.3;
}
.replayer-mouse.active:after {
animation: click 0.2s ease-in-out 1;
}
.replayer-mouse-tail {
position: absolute;
pointer-events: none;
}
@keyframes click {
0% {
opacity: 0.3;
width: 20px;
height: 20px;
border-radius: 10px;
transform: translate(-10px, -10px);
}
50% {
opacity: 0.5;
width: 10px;
height: 10px;
border-radius: 5px;
transform: translate(-5px, -5px);
}
}
.rr-player {
position: relative;
background: white;
float: left;
border-radius: 5px;
box-shadow: 0 24px 48px rgba(17, 16, 62, 0.12);
}
.rr-player__frame {
overflow: hidden;
}
.replayer-wrapper {
float: left;
clear: both;
transform-origin: top left;
left: 50%;
top: 50%;
}
.replayer-wrapper > iframe {
border: none;
}
.switch.svelte-1mmdovf.svelte-1mmdovf {
height: 1em;
display: flex;
align-items: center;
}
.switch.disabled.svelte-1mmdovf.svelte-1mmdovf {
opacity: 0.5;
}
.label.svelte-1mmdovf.svelte-1mmdovf {
margin: 0 8px;
}
.switch.svelte-1mmdovf input[type='checkbox'].svelte-1mmdovf {
position: absolute;
opacity: 0;
}
.switch.svelte-1mmdovf label.svelte-1mmdovf {
width: 2em;
height: 1em;
position: relative;
cursor: pointer;
display: block;
}
.switch.disabled.svelte-1mmdovf label.svelte-1mmdovf {
cursor: not-allowed;
}
.switch.svelte-1mmdovf label.svelte-1mmdovf:before {
content: '';
position: absolute;
width: 2em;
height: 1em;
left: 0.1em;
transition: background 0.1s ease;
background: rgba(73, 80, 246, 0.5);
border-radius: 50px;
}
.switch.svelte-1mmdovf label.svelte-1mmdovf:after {
content: '';
position: absolute;
width: 1em;
height: 1em;
border-radius: 50px;
left: 0;
transition: all 0.2s ease;
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.3);
background: #fcfff4;
animation: switch-off 0.2s ease-out;
z-index: 2;
}
.switch
input[type='checkbox']:checked
+ label.svelte-1mmdovf.svelte-1mmdovf:before {
background: rgb(73, 80, 246);
}
.switch
input[type='checkbox']:checked
+ label.svelte-1mmdovf.svelte-1mmdovf:after {
animation: switch-on 0.2s ease-out;
left: 1.1em;
}
</style>
这里保存录像,可以采用setInterval,每个多少秒上传一次
代码效果