3

场景

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 -_-!!!

clipboard.png

可能存在的问题

  1. 未测试最大可记录事件数量
  2. 如果一次事件未在1ms内完成,进入下个动作会怎么样

纤细的一帆风顺
40 声望2 粉丝