Why is requestAnimationFrame generating "Uncaught TypeError: Cannot read property 'id' of undefined"?

Why is requestAnimationFrame generating "Uncaught TypeError: Cannot read property 'id' of undefined"?

我是 JavaScript 初学者,我有一些代码在功能方面工作得很好,但它是如此 not DRY 和 "dirty"...光是看那段代码就让我恶心。 所以,我决定做一个非常漂亮、现代、干净和干燥的版本。 但是现在我完全被困住了,尽管到处搜索都找不到任何东西来理解我到底做错了什么? 最重要的是:为什么错了?

因此,这是当前代码:

        document.addEventListener("DOMContentLoaded", function() {
            // parent div that holds the navigation wheel: 
            var navDiv = document.querySelector("#wrapper > section > div");
            navDiv.addEventListener("mousedown", moveEye, false);
            navDiv.addEventListener("mouseup", stopEye, false);
            
            var eye = document.querySelector("#eyeball"),
                iris = document.querySelector("#iris"),
                reqID;
            
            function moveEye(e) {
                var btnClicked = e.target.id;
//                alert("Button clicked: " + btnClicked);
                if (btnClicked === "right") {
                    eye.style.left = (eye.offsetLeft += 3) + "px";
                } else if (btnClicked === "left") {
                    eye.style.left = (eye.offsetLeft -= 3) + "px";
                } else if (btnClicked === "top") {
                    eye.style.top = (eye.offsetTop -= 3) + "px";
                } else if (btnClicked === "bottom") {
                    eye.style.top = (eye.offsetTop += 3) + "px";
                }

                reqID = window.requestAnimationFrame(moveEye);
            }
            
            function stopEye(e) {
                cancelAnimationFrame(reqID);
            }
            
        }, false);
    <div id="wrapper">
        <p>Description comes here.</p>
        <section>
            <p>Navigation Wheel:</p>
            <div>
                <span id="top"></span>
                <span id="bottom"></span>
                <span id="left"></span>
                <span id="right"></span>
                <span id="hub"></span>
            </div>
        </section>
        <section id="stage">
            <figure id="eyeball">
                <span id="shadow"></span>
                <span id="iris" class="irisMove"></span>
            </figure>
        </section>
    </div>

请注意,我特别想保留这个:

var navDiv = document.querySelector("#wrapper > section > div");

因为我想尽可能保持代码干爽和干净。 这就是为什么我选择包含 4 个按钮的 parent div 而不是 div 中的 4 个按钮中的每一个。 所以,我想听父 div 告诉我当前正在单击里面的 4 个按钮中的哪一个(即 4 个跨度元素),而用户将鼠标按住 button/span 我希望 requestAnimationFrame 完成它该死的工作! 而已!没问太多吧?

但据我所知,目前是该死的 requestAnimationFrame 导致了这个错误: "Uncaught TypeError: Cannot read property 'id' of undefined"

最大的问题是:为什么??? 为什么 requestAnimationFrame 说它是 "undefined"? 无需在此处使用 requestAnimationFrame 即可正常工作。

那么,请解释一下: 我到底做错了什么? 最重要的是: 为什么错了?

此代码:

window.requestAnimationFrame(moveEye);

...将导致 moveEye 被调用,有点像这样:

moveEye(timestamp);

请注意,没有传递任何事件,而是传递了一个时间戳。所以函数中的 e.target 将是未定义的,这就是 e.target.id 失败的原因。

如果您的目的是循环播放动画,则需要"capture" 相关数据。关闭将使这成为可能:

function moveEye(e) {
  var btnClicked = e.target.id;
  function animate() {
    if (btnClicked === "right") {
      eye.style.left = (eye.offsetLeft += 3) + "px";
    } else if (btnClicked === "left") {
      eye.style.left = (eye.offsetLeft -= 3) + "px";
    } else if (btnClicked === "top") {
      eye.style.top = (eye.offsetTop -= 3) + "px";
    } else if (btnClicked === "bottom") {
      eye.style.top = (eye.offsetTop += 3) + "px";
    }
    reqID = window.requestAnimationFrame(animate);
  }
  animate();
}

当调用 moveEye 以响应鼠标单击时,它会提供一个事件对象作为第一个参数。事件对象有一个 target 属性.

moveEye 作为 requestAnimationFrame 的回调被调用时,它被传递一个 DOMHighResTimeStamp 双精度数。 JavaScript 允许尝试通过自动将数字转换为数字对象来访问数字的属性。

但是,Number 对象没有 target 属性,因此 属性 查找返回的值为 undefined。现在尝试访问未定义的 target 属性 的 id 属性 会生成错误。


更新
自然地,修复需要为单击事件处理程序和动画回调使用单独的函数。在处理完其他问题后,这里有一个移动#eyeball 的工作示例:

document.addEventListener("DOMContentLoaded", function() {
    // parent div that holds the navigation wheel: 
    var navDiv = document.querySelector("#wrapper > section > div");
    navDiv.addEventListener("mousedown", startEye, false);
    navDiv.addEventListener("mouseup", stopEye, false);

    var eye = document.querySelector("#eyeball"),
        iris = document.querySelector("#iris"),
        reqID,
        btnClicked = "";

    function startEye(e) {
        btnClicked = e.target.id;
        moveEye();
    }
    function moveEye() {
        var left = parseFloat( eye.style.left);
        var top = parseFloat( eye.style.top);

        if ( btnClicked === "right") {
          eye.style.left = (left + 3) + "px";
        } 
        else if ( btnClicked === "left") {
          eye.style.left = (left -3) + 'px';
        }
        else if ( btnClicked === "top") {
            eye.style.top = (top - 3) + "px";
        }
        else if ( btnClicked === "bottom") {
            eye.style.top = (top + 3) + "px";
        }
        reqID = window.requestAnimationFrame(moveEye);
    }
    
    function stopEye(e) {
        cancelAnimationFrame(reqID);
    }
    
},false);
<div id="wrapper">
<p>Description comes here.</p>
<section>
    <p>Navigation Wheel:</p>
    <div>
        <span id="top">top</span>
        <span id="bottom">bottom</span>
        <span id="left">left</span>
        <span id="right">right</span>
        <span id="hub"></span>
    </div>
</section>
<section id="stage">
    <figure id="eyeball" style="position: relative; top:0px; left: 0px">
        eyeball
        <span id="shadow"></span>
        <span id="iris" class="irisMove"></span>
    </figure>
</section>
</div>

TLDR;

请注意,.offsetLeftoffsetTop 是只读元素属性。 (eye.offsetTop += 3)(eye.offsetLeft -= 3) 等表达式不会更新 属性 的值,具有严重的误导性,不应保留在代码中。

CSS 属性 left/top 和元素属性 offSetLeft/offsetTop 不使用相同的参考原点。最初这导致单击 "left" 也向右移动元素。采用的解决方案是使用 CSS lefttop 属性设置要移动的元素 (#eyeball) 的样式,并使用它们来读取和更新位置。这就是示例中未使用 offSetLeft.offsetTop 的原因。