js記錄鼠標動做並按發生時間重現

場景

demo 演示記錄鼠標在一個區域內的動做,並記錄下來。點擊回放時,按時序回放動做,包括移動,點擊,拖拽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>鼠標事件記錄並回放</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 -_-!!!ide

clipboard.png

可能存在的問題

  1. 未測試最大可記錄事件數量
  2. 若是一次事件未在1ms內完成,進入下個動做會怎麼樣
相關文章
相關標籤/搜索