场景
demo 演示记录鼠标在一个区域内的动作,并记录下来。点击回放时,按时序回放动作,包括移动,点击,拖拽
源代码
<!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>鼠标事件记录并回放</title>
<style>
* {
margin: 0;
padding: 0;
}
.container {
display: flex;
height: 600px;
}
.ctrls {
display: flex;
}
.ctrls div {
flex: 1 1 auto;
}
.p-action,
.p-repay {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-wrap: wrap;
flex-direction: row;
}
.p-action {
background: #f0f0f0;
}
.p-repay {
background: #5aab94;
}
.mouse-img {
width: 30px;
position: absolute;
left: 0;
top: 0;
}
.btn {
width: 60px;
height: 30px;
line-height: 2em;
margin: 2em;
background-color: #eee;
display: flex;
justify-content: center;
align-items: center;
}
.btn.hover {
background-color: #888888;
}
.metas {
position: absolute;
/* bottom: 40px;
left: 40px; */
}
.metas img {
width: 40px;
left: 0px;
top: 480px;
position: absolute;
}
.metas .meta2 {
left: 60px;
}
.metas .meta3 {
left: 120px;
}
.metas .meta4 {
left: 180px;
}
.metas .meta5 {
left: 240px;
}
.metas .meta6 {
left: 300px;
}
</style>
</head>
<body>
<div class="ctrls">
<button class="btn" onclick="start()">开始记录</button>
<button class="btn" onclick="replay()">开始播放</button>
</div>
<div class="container">
<div class="p-action">
<button class="btn btn1" onclick="btnClicked(1)">button1</button>
<button class="btn btn2" onclick="btnClicked(2)">button2</button>
<button class="btn btn3" onclick="btnClicked(3)">button3</button>
<button class="btn btn4" onclick="btnClicked(4)">button4</button>
<div class="metas">
<img class="meta1" src="./square.png" alt="">
<img class="meta2" src="./circle.png" alt="">
<img class="meta3" src="./triangle.png" alt="">
<img class="meta4" src="./square.png" alt="">
<img class="meta5" src="./circle.png" alt="">
<img class="meta6" src="./triangle.png" alt="">
</div>
<img class="mouse-img" style="left:0;top:0;display: none;" src="./timg.png" alt="">
</div>
<!-- <div class="p-repay">
<button class="btn btn-r-1" onclick="btnClicked(1)">button1</button>
<button class="btn btn-r-2" onclick="btnClicked(2)">button2</button>
<button class="btn btn-r-3" onclick="btnClicked(3)">button3</button>
<button class="btn btn-r-4" onclick="btnClicked(4)">button4</button>
<div class="metas">
<img class="meta-1-r" src="./square.png" alt="">
<img class="meta-2-r" src="./circle.png" alt="">
<img class="meta-3-r" src="./triangle.png" alt="">
<img class="meta-4-r" src="./square.png" alt="">
<img class="meta-5-r" src="./circle.png" alt="">
<img class="meta-6-r" src="./triangle.png" alt="">
</div>
<img class="mouse-img" style="left:0;top:0;" src="./timg.png" alt="">
</div> -->
</div>
</body>
<script>
const eventL = [];
const rootEl = document.getElementsByClassName("p-action")[0];
const rootElTop = rootEl.getBoundingClientRect().y,
rootElLeft = rootEl.getBoundingClientRect().x;
const mouseImg = document.getElementsByClassName("mouse-img")[0];
let startTime;
let replayLastEvtTime = 0; // 上一个事件的时间(用于计算下一次事件需要等待时间)
let isMouseDown = false; // 鼠标是否按下去
// 页面上可操作的元素
const metaList = [
{
className: "meta1",
points: [[40, 480], [80, 480], [80, 520], [40, 520]]
},
{
className: "meta2",
points: [[60, 480], [100, 480], [100, 520], [60, 520]]
},
{
className: "meta3",
points: [[120, 480], [160, 480], [160, 520], [100, 520]]
},
{
className: "meta4",
points: [[180, 480], [220, 480], [220, 520], [180, 520]]
},
{
className: "meta5",
points: [[240, 480], [280, 480], [280, 520], [240, 520]]
},
{
className: "meta6",
points: [[280, 480], [320, 480], [320, 520], [280, 520]]
},
];
// 鼠标按下事件
function mouseDown() {
isMouseDown = true;
}
// 记录鼠标轨迹
function mouseMove(e) {
const x = e.clientX - rootElLeft,
y = e.clientY - rootElTop;
console.log("x,y:", x, y)
store({
evtType: isMouseDown ? "drag" : "mousemove",
data: {
x: x,
y: y
}
});
// 执行当前元素拖拽动作
// if (isMouseDown) {
// const meta = getDragMeta([x, y]);
// console.log(meta);
// if (meta) {
// meta.style.left = x + "px";
// meta.style.top = y + "px";
// }
// }
}
// 鼠标抬起事件
function mouseUp() {
isMouseDown = false;
}
// 记录按钮点击事件
function btnClicked(id) {
store({
evtType: "click",
data: {
metaId: id,
}
});
}
/***
* 记录当前事件
*/
function store(data) {
// 记录当前事件的时间(等待时间:即开始点击开始记录后,多少毫秒后执行这个动作)
const time = new Date().getTime();
eventL.push(Object.assign(data, {
time: time - startTime
}));
}
// 重现
function replay() {
console.log("事件总数:", eventL.length);
rootEl.removeEventListener("mousemove", mouseMove, false);
rootEl.removeEventListener("mousemove", mouseMove, false);
rootEl.removeEventListener("mouseup", mouseUp, false);
mouseImg.style.display = "inline";
replayMeta();
};
function replayMeta() {
let currentEvt = eventL.shift();
if (!currentEvt) {
replayLastEvtTime = 0;
console.log("end……")
return;
}
// 等待本次事件的时间到了才执行,完成后下一次事件进入等待
awateEvtTime(currentEvt.time - replayLastEvtTime)
.then(() => {
switch (currentEvt.evtType) {
case "mousemove":
console.log("do mousemove");
mouseImg.style.left = currentEvt.data.x + "px";
mouseImg.style.top = currentEvt.data.y + "px";
break;
case "drag":
const meta = getDragMeta([currentEvt.data.x, currentEvt.data.y]);
if (meta) {
meta.style.left = currentEvt.data.x + "px";
meta.style.top = currentEvt.data.y + "px";
}
// 拖拽同时鼠标也要跟着移动
mouseImg.style.left = currentEvt.data.x + "px";
mouseImg.style.top = currentEvt.data.y + "px";
break;
case "click":
console.log("do click");
let btn = document.getElementsByClassName("btn" + currentEvt.data.metaId)[0];
btn.classList.add("hover");
setTimeout(() => {
btn.classList.remove("hover");
}, 800);
break;
}
replayLastEvtTime = currentEvt.time;
replayMeta();
});
}
// 本次事件的需要等待的时间
function awateEvtTime(timeout) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, timeout);
})
}
function start() {
rootEl.addEventListener("mousedown", mouseDown, false);
rootEl.addEventListener("mousemove", mouseMove, false);
rootEl.addEventListener("mouseup", mouseUp, false);
mouseImg.style.display = "none";
startTime = new Date().getTime();
}
// 得到拖动的对象
function getDragMeta(point) {
for (let i = 0; i < metaList.length; i++) {
let meta = metaList[i];
if (pointInPolygon(point, meta.points)) {
return document.getElementsByClassName(meta.className)[0];
}
}
}
/**
* 判断点是否在另一平面图中
* {Array} point [x,y] 这个点
* {Array} vs [[x,y],[x,y]]平面点集合
*/
function pointInPolygon(point, vs) {
console.log(point, vs);
const x = point[0], y = point[1];
let inside = false;
for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {
const xi = vs[i][0], yi = vs[i][1];
const xj = vs[j][0], yj = vs[j][1];
const intersect = ((yi > y) !== (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) {
inside = !inside;
}
}
console.log(inside);
return inside;
}
</script>
</html>
效果图
没有gif -_-!!!
可能存在的问题
- 未测试最大可记录事件数量
- 如果一次事件未在1ms内完成,进入下个动作会怎么样
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。