錄屏實現方案:rrweb.js
官方文檔:https://github.com/rrweb-io/rrweb
截屏實現方案:html2canvas.js
官方文檔:http://html2canvas.hertzen.com/
rrweb錄屏實現的原理是記錄dom的變化,播放的時候重新執行dom操作,所以不支持生成mp4,只可以在web端重放
html2canvas截圖實現的原理是生成一個canvas畫布,畫布的內容是插件幫我們實現好的,我們需要做的是將canvas畫布保存成base64圖片,保存到本地或者上傳
服務端例子:express
需要引入的文件:
rrweb:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.css"/> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/style.css"/> <script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/index.js"></script>
html2canvas(從官網下載到本地的文件)
<script src="/javascripts/html2canvas.js"></script>
下面用一個例子演示一下跨頁面錄屏和截屏的效果
前端完整代碼如下:
頁面1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.css"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/style.css"
/>
</head>
<body>
<!-- 不支持 IE11 以下的瀏覽器 -->
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/index.js"></script>
<script src="/javascripts/html2canvas.js"></script>
<button class="btn">開始錄制</button>
<button class="btn2">結束並播放</button>
<button class="btn3" id="capture">截圖</button>
<button class="btn4">下一步</button>
<button class="btn5">清空服務器數據</button>
<br>
<br>
<div>this is page1</div>
<div>
<input type="text" placeholder="請輸入信息">
</div>
<script>
window.onload = function(){
let el = document.getElementsByClassName("btn")[0];
let el2 = document.getElementsByClassName("btn2")[0];
let el3 = document.getElementById("capture")
let el4 = document.getElementsByClassName("btn4")[0];
let el5 = document.getElementsByClassName("btn5")[0];
let events = []
let r
el.onclick = function(){
alert("開始錄制")
r = rrweb.record({
emit(event) {
// 將 event 存入 events 數組中
events.push(event);
console.log(events)
},
});
}
el2.onclick = function(){
alert("播放")
r()
new rrwebPlayer({
target: document.body, // 可以自定義 DOM 元素
data: {
events,
},
});
}
el3.onclick = function(){
html2canvas(document.querySelector("body")).then(canvas => {
// document.body.appendChild(canvas)
let pic = canvas.toDataURL("image/png");
downloadFileByBase64(pic,'金融測試下載')
// window.location.href = pic;
});
}
el4.onclick = function(){
// console.log(JSON.stringify(events))
// console.log(events.length)
uploadEvents(1,events)
return false
}
el5.onclick = function(){
// console.log(JSON.stringify(events))
// console.log(events.length)
ajax({
url:'/clearEvents',
type:'POST',
dataType:'json',
data:{},
// data:{name:1},
success:function(response,xml){
//請求成功后執行的代碼
alert(response.msg)
// console.log(events)
window.open("/page2.html")
},
error:function(status){
alert(response.msg)
}
});
return false
}
}
// 分批上傳
function uploadEvents(m,events){
console.log(JSON.stringify(events).length)
ajax({
url:'/saveEvents',
type:'POST',
dataType:'json',
data:{events:JSON.stringify(events),page:1},
// data:{name:1},
success:function(response,xml){
//請求成功后執行的代碼
response = JSON.parse(response);
// console.log(events)
window.open("/page2.html")
},
error:function(status){
alert(response.msg)
}
});
}
// base64生成圖片方法
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
function downloadFile(url,name='What\'s the fuvk'){
var a = document.createElement("a")
a.setAttribute("href",url)
a.setAttribute("download",name)
a.setAttribute("target","_blank")
let clickEvent = document.createEvent("MouseEvents");
clickEvent.initEvent("click", true, true);
a.dispatchEvent(clickEvent);
}
function downloadFileByBase64(base64,name){
var myBlob = dataURLtoBlob(base64)
var myUrl = URL.createObjectURL(myBlob)
downloadFile(myUrl,name)
}
// 原生ajax方法
function ajax(options){
var options=options||{};
options.type=(options.type||'GET').toUpperCase();
options.dataType=options.dataType||'json';
var params=formatParams(options.data);
//創建-第一步
var xhr;
//非IE6
if(window.XMLHttpRequest){
xhr=new XMLHttpRequest();
}else{
//ie6及其以下版本瀏覽器
xhr=ActiveXObject('Microsoft.XMLHTTP');
}
//接收-第三步
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
var status=xhr.status;
if(status>=200&&status<300){
options.success&&options.success(xhr.responseText,xhr.responseXML);
}else{
options.error&&options.error(status);
}
}
}
//連接和發送-第二步
if(options.type=='GET'){
xhr.open('GET',options.url+'?'+params,true);
xhr.send(null);
}else if(options.type=='POST'){
xhr.open('POST',options.url,true);
//設置表單提交時的內容類型
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);
}
}
//格式化參數
function formatParams(data){
var arr=[];
for(var name in data){
arr.push(encodeURIComponent(name)+'='+encodeURIComponent(data[name]));
}
// arr.push(('v='+Math.random()).replace('.',''));
return arr.join('&');
}
// save 函數用於將 events 發送至后端存入,並重置 events 數組
// function save() {
// const body = JSON.stringify({ events });
// events = [];
// fetch('http://YOUR_BACKEND_API', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body,
// });
// 每 10 秒調用一次 save 方法,避免請求過多
// setInterval(save, 10 * 1000);
</script>
</body>
</html>
頁面2:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.css"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/style.css"
/>
</head>
<body>
<!-- 不支持 IE11 以下的瀏覽器 -->
<script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/rrweb.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/rrweb-player@latest/dist/index.js"></script>
<script src="/javascripts/html2canvas.js"></script>
<button class="btn">開始錄制2</button>
<button class="btn2">結束並播放1和2</button>
<button class="btn3" id="capture">截圖2</button>
<br>
<br>
<div>this is page2</div>
<div>
<input type="text" placeholder="請輸入信息">
</div>
<script>
window.onload = function(){
let el = document.getElementsByClassName("btn")[0];
let el2 = document.getElementsByClassName("btn2")[0];
let el3 = document.getElementById("capture")
console.log(el)
let events = []
let r
el.onclick = function(){
alert("開始錄制")
r = rrweb.record({
emit(event) {
// 將 event 存入 events 數組中
events.push(event);
console.log(events)
},
});
}
el2.onclick = function(){
alert("播放")
if(r){
r()
}
ajax({
url:'/saveEvents',
type:'POST',
dataType:'json',
data:{events:JSON.stringify(events),finished:false},
// data:{name:1},
success:function(response,xml){
//請求成功后執行的代碼
// console.log(events)
ajax({
url:'/getEvents',
type:'POST',
dataType:'json',
data:{},
success:function(response,xml){
//請求成功后執行的代碼
response = JSON.parse(response);
events = response;
console.log(response)
new rrwebPlayer({
target: document.body, // 可以自定義 DOM 元素
data: {
events,
},
});
},
error:function(status){
alert(response.msg)
}
});
},
error:function(status){
alert(response.msg)
}
});
}
el3.onclick = function(){
html2canvas(document.querySelector("body")).then(canvas => {
// document.body.appendChild(canvas)
let pic = canvas.toDataURL("image/png");
downloadFileByBase64(pic,'金融測試下載')
// window.location.href = pic;
});
}
}
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
function downloadFile(url,name='What\'s the fuvk'){
var a = document.createElement("a")
a.setAttribute("href",url)
a.setAttribute("download",name)
a.setAttribute("target","_blank")
let clickEvent = document.createEvent("MouseEvents");
clickEvent.initEvent("click", true, true);
a.dispatchEvent(clickEvent);
}
function downloadFileByBase64(base64,name){
var myBlob = dataURLtoBlob(base64)
var myUrl = URL.createObjectURL(myBlob)
downloadFile(myUrl,name)
}
// 原生ajax方法
function ajax(options){
var options=options||{};
options.type=(options.type||'GET').toUpperCase();
options.dataType=options.dataType||'json';
var params=formatParams(options.data);
//創建-第一步
var xhr;
//非IE6
if(window.XMLHttpRequest){
xhr=new XMLHttpRequest();
}else{
//ie6及其以下版本瀏覽器
xhr=ActiveXObject('Microsoft.XMLHTTP');
}
//接收-第三步
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
var status=xhr.status;
if(status>=200&&status<300){
options.success&&options.success(xhr.responseText,xhr.responseXML);
}else{
options.error&&options.error(status);
}
}
}
//連接和發送-第二步
if(options.type=='GET'){
xhr.open('GET',options.url+'?'+params,true);
xhr.send(null);
}else if(options.type=='POST'){
xhr.open('POST',options.url,true);
//設置表單提交時的內容類型
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);
}
}
//格式化參數
function formatParams(data){
var arr=[];
for(var name in data){
arr.push(encodeURIComponent(name)+'='+encodeURIComponent(data[name]));
}
// arr.push(('v='+Math.random()).replace('.',''));
return arr.join('&');
}
// save 函數用於將 events 發送至后端存入,並重置 events 數組
// function save() {
// const body = JSON.stringify({ events });
// events = [];
// fetch('http://YOUR_BACKEND_API', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body,
// });
// 每 10 秒調用一次 save 方法,避免請求過多
// setInterval(save, 10 * 1000);
</script>
</body>
</html>
服務端代碼:
/* * @Author: your name * @Date: 2020-07-18 19:09:47 * @LastEditTime: 2020-07-19 12:24:16 * @LastEditors: Please set LastEditors * @Description: In User Settings Edit * @FilePath: /record-2/app.js */ var createError = require('http-errors'); var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); var fs = require('fs'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); var bodyParser = require('body-parser'); var app = express(); app.use(bodyParser.urlencoded({ extended:true })); let events = []; // view engine setup app.use(express.static(path.join(__dirname, 'public'))); // 服務端頁面 app.get('/', function(req, res, next) { // res.writeHead(200,{'Content-Type':'text/html'}) fs.readFile('./html/index.html','utf-8',function(err,data){ if(err){ throw err ; } res.send(data); }); }); app.get('/page2.html', function(req, res, next) { // res.writeHead(200,{'Content-Type':'text/html'}) fs.readFile('./html/page2.html','utf-8',function(err,data){ if(err){ throw err ; } res.send(data); }); }); app.post('/clearEvents',function(req, res, next) { // res.writeHead(200,{'Content-Type':'text/html'}) console.log(req.body) events = []; res.send({msg:"操作成功"}) // if(req.query.events.length>0){ // res.send({state:1,msg:"上傳成功"}) // }else{ // res.send({state:0,msg:"參數錯誤,未獲取到錄屏信息"}) // } }); // 存 app.post('/saveEvents',function(req, res, next) { // res.writeHead(200,{'Content-Type':'text/html'}) console.log(req.body) res.send({state:1,msg:"上傳成功"}) // res.send(req.body.events) if(req.body.page==1){ events = [] } events = events.concat(JSON.parse(req.body.events)) fs.writeFile("./data.txt",JSON.stringify(req.body.events),'utf-8',function(){ }) // if(req.query.events.length>0){ // res.send({state:1,msg:"上傳成功"}) // }else{ // res.send({state:0,msg:"參數錯誤,未獲取到錄屏信息"}) // } }); // 取 app.post('/getEvents',function(req, res, next) { res.send(events); // fs.readFile('./data.txt','utf-8',function(err,data){ // if(err){ // throw err ; // } // console.log(JSON.parse(data)) // res.send(data); // }); // if(req.query.events.length>0){ // res.send({state:1,msg:"上傳成功"}) // }else{ // res.send({state:0,msg:"參數錯誤,未獲取到錄屏信息"}) // } }); module.exports = app;
上面的例子如果只想在前端看效果的話,只運行第一個頁面的前端代碼也能查看。
