Eloquent JavaScript 笔记 十四:Handling Event

1. Event Handlers

<p>Click this document to activate the handler.</p>
<script>
  addEventListener("click", function() {
    console.log("You clicked!");
  });
</script>

相当于调用 window.addEventListener( ),因为,window对象是所有全局变量/函数的容器。

在浏览器窗口中点击鼠标,控制台输出:You clicked!

2. Events and DOM nodes

每一个DOM node都有自己的addEventListener

<button>Click me</button>
<p>No handler here.</p>
<script>
  var button = document.querySelector("button");
  button.addEventListener("click", function() {
    console.log("Button clicked.");
  });
</script>

onclick 属性,和addEventListener 的作用相同

<button οnclick="onBtnClicked()">Click me</button>
<script>
   function onBtnClicked() {
      console.log("Button clicked.");
   }
</script>

addEventListener 可以给一个事件添加多个handler,而,onclick 属性不行

删除事件句柄: removeEventListener()

<button>Act-once button</button>
<script>
  var button = document.querySelector("button");
  function once() {
    console.log("Done.");
    button.removeEventListener("click", once);
  }
  button.addEventListener("click", once);
</script>

3. Event Objects

<button οnmοusedοwn="onMouseDown(event)">Click me any way you want</button>
<script>
    function onMouseDown(event) {
        console.log(event.type);
        if (event.which == 1)
            console.log("Left button");
        else if (event.which == 2)
            console.log("Middle button");
        else if (event.which == 3)
            console.log("Right button");
    }
</script>

通过event.which识别鼠标左、右键。

event.type 存储了event的名字,例如:mousedown, click。

4. Propagation

propagation

如果一个子节点收到click事件,它的父节点也会收到,父节点的父节点也会收到,直到window,都会收到click事件。

如果一个节点和它的父节点都有ckick事件的handler,那么,子节点的handler先执行,然后执行父节点的。 如果在子节点的handler中调用了 stopPropagtion( ) ,则父节点的handler不再执行。

stopPropagation()

<p>A paragraph with a <button>button</button>.</p>
<script>
    var para = document.querySelector("p");
    var button = document.querySelector("button");
    para.addEventListener("mousedown", function() {
        console.log("Handler for paragraph.");
    });
    button.addEventListener("mousedown", function(event) {
        console.log("Handler for button.");
        if (event.which == 3)
            event.stopPropagation();
    });
</script>

<button> 是 <p> 的子节点,二者都添加了 mousedown handler 。 

用鼠标左键点击button,二者的handler都会执行。

用鼠标右键点击button,只有button的handler执行。因为,调用了event.stopPropagation( )

event.target

event 的接收目标,拥有该handler的对象。

<button>A</button>
<button>B</button>
<button>C</button>
<script>
  document.body.addEventListener("click", function(event) {
    if (event.target.nodeName == "BUTTON")
      console.log("Clicked", event.target.textContent);
  });
</script>

5. Default Actions

很多event有默认的action,例如:

1. <a> ,点击一个link,就会跳转到它的url。

2. 在浏览器中点击右键,会弹出上下文菜单。

3. 在浏览器中按键盘上的 “下” 键,会使网页上下滚动。

对于大部分类型的event,“自定义的handler” 会优先于 “默认action” 执行。

preventDefault() 禁止默认action执行。例如:

<a href="https://developer.mozilla.org/">MDN</a>
<script>
  var link = document.querySelector("a");
  link.addEventListener("click", function(event) {
    console.log("Nope.");
    event.preventDefault();
  });
</script>

尽量不要这么做,这会导致用户的困惑,影响网站的用户体验。

6. Key Events

6.1. keydown, keyup

<p>This page turns violet when you hold the V key.</p>
<script>
  addEventListener("keydown", function(event) {
    if (event.keyCode == 86)
      document.body.style.background = "violet";
  });
  addEventListener("keyup", function(event) {
    if (event.keyCode == 86)
      document.body.style.background = "";
  });
</script>

当按住一个键不松开,keydown 事件会被出发多次。

6.2. event.keyCode

对于字母和数字,keyCode就是它的Unicode编码,该编码和 string.charCodeAt( ) 结果相同。

console.log("Violet".charCodeAt(0));
// → 86
console.log("1".charCodeAt(0));
// → 49

对于其他特殊字符,没有一个标准的编码,需要自己去尝试。

6.3. Modifier Keys

Shift, Ctrl, Alt, Meta(Cmd on Mac)

单独按下这些键,也会产生正常的event,和其他键一样。

当使用组合键时,可以检查这些属性 shiftKey, ctrlKey, altKey, metaKey,例如:

<p>Press Ctrl-Space to continue.</p>
<script>
    addEventListener("keydown", function(event) {
        if (event.keyCode == 32 && event.ctrlKey)
            console.log("Continuing!");
    });
</script>
6.4. keypress
<p>Focus this page and type something.</p>
<script>
  addEventListener("keypress", function(event) {
    console.log(String.fromCharCode(event.charCode));
  });
</script>

可见字符被按下才会触发这个event。

用String.fromCharCode(event.charCode) 可以得到被按下的字符。

和keydown一样,如果长时间按住一个键,会触发多次event。

6.5. focus

普通的node不能接收到键盘event,除非给它加上 tabindex 属性。

link, button, form field 默认就可以获得键盘焦点。

如果网页中没有键盘焦点,document.body 会接收到键盘事件。

7. Mouse Clicks

7.1. mousedown, mouseup

7.2. click

既收到mousedown,又收到mouseup的node,会收到一个click event。

例如: 在一个<p> node 按下鼠标,鼠标保持按下,指针移动到另外一个<p>,抬起鼠标按键。那么,这两个<p> 都不会收到click event,而是,它们共同的父节点收到click event。

7.3.  dblclick

双击

7.4. 点击坐标

pageX, pageY 是以document左上角为原点的坐标。

7.5. 在网页上点蓝色圆点

    <style>
        body {
            height: 200px;
            background: beige;
        }
        .dot {
            width: 8px; height: 8px;
            background: blue;
            border-radius: 4px;
            position: absolute;
        }
    </style>
    <script>
        addEventListener('click', function (event) {
            var dot = document.createElement("div");
            dot.className = "dot";
            dot.style.left = (event.pageX - 4) + "px";
            dot.style.top = (event.pageY - 4) + "px";
            document.body.appendChild(dot);
        });
    </script>

event.clientX 和 event.clientY 也是鼠标点击位置坐标,它们不是相对于document左上角的,而是浏览器的视窗左上角,当视窗向下滚动后,它们和pageX、pageY就不同了。


8.  Mouse Motion
8.1. 
mousemove

鼠标移动时,触发此事件。

例如:拉动一个矩形,改变它的宽度

<p>Drag the bar to change its width:</p>
<div style="background: orange; width: 60px; height: 20px">
</div>
<script>
    var lastX; // Tracks the last observed mouse X position
    var rect = document.querySelector("div");
    rect.addEventListener("mousedown", function(event) {
        if (event.which == 1) {
            lastX = event.pageX;
            addEventListener("mousemove", moved);
            event.preventDefault(); // Prevent selection
        }
    });

    function buttonPressed(event) {
        if (event.buttons == null)
            return event.which != 0;
        else
            return event.buttons != 0;
    }
    function moved(event) {
        if (!buttonPressed(event)) {
            removeEventListener("mousemove", moved);
        } else {
            var dist = event.pageX - lastX;
            var newWidth = Math.max(10, rect.offsetWidth + dist);
            rect.style.width = newWidth + "px";
            lastX = event.pageX;
        }
    }
</script>

注意:mousedown事件handler 加在了rect上,而mousemove 事件handler加在了window上。为什么这么做呢?在鼠标移动过程中,即使移动到了rect外部,也应该处理mousemove事件。

注意:buttonPressed 函数中,在判断鼠标左键是否按下时,不仅用到了event.which, 还用到了event.buttons。这是为了兼容所有浏览器。

8.2. mouseover, mouseout

鼠标 "进入" 或 "离开" node。

propagation:父节点也会收到子节点的mouseover和mouseout 。

event.relatedTarget 是哪一个element:

  对于mouseover事件: what element the pointer was over before.

  对于mouseout事件: what element it is going to.

例子:

<p>Hover over this <strong>paragraph</strong>.</p>
<script>
  var para = document.querySelector("p");
  function isInside(node, target) {
    for (; node != null; node = node.parentNode)
      if (node == target) return true;
  }
  para.addEventListener("mouseover", function(event) {
    if (!isInside(event.relatedTarget, para))
      para.style.color = "red";
  });
  para.addEventListener("mouseout", function(event) {
    if (!isInside(event.relatedTarget, para))
      para.style.color = "";
  });
</script>

9. Scroll Events

scroll,例子:自制进度条,显示当前窗口滚动的百分比。

<style>
  .progress {
    border: 1px solid blue;
    width: 100px;
    position: fixed;
    top: 10px; right: 10px;
  }
  .progress > div {
    height: 12px;
    background: blue;
    width: 0%;
  }
  body {
    height: 2000px;
  }
</style>
<div class="progress"><div></div></div>
<p>Scroll me...</p>
<script>
  var bar = document.querySelector(".progress div");
  addEventListener("scroll", function() {
    var max = document.body.scrollHeight - innerHeight;
    var percent = (pageYOffset / max) * 100;
    bar.style.width = percent + "%";
  });
</script>

10. Focus Events

10.1. events

blur: 失去焦点

focus: 获得焦点

这两个事件不会向 parentNode 传播。

10.2. 例子

显示当前获得焦点的 input 的 data-help 属性。

<p>Name: <input type="text" data-help="Your full name"></p>
<p>Age: <input type="text" data-help="Age in years"></p>
<p id="help"></p>
<script>
    var help = document.querySelector("#help");
    var fields = document.querySelectorAll("input");
    fields.forEach(function (afield) {
        afield.addEventListener("focus", function (event) {
            var text = event.target.getAttribute("data-help");
            help.textContent = text;
        });
        afield.addEventListener("blur", function (event) {
            help.textContent = "";
        });
    });
</script>

11. Load Event

11.1. 页面加载完毕事件: 

window.onload

document.body.onload

document.onload

三者的区别是什么?DOM加载完毕?还是整个页面所有资源加载完毕? 在不同版本的浏览器上定义不太一致,趋势是window.onload越来越流行。如果用jQuery的话,就不用太关心这个事情,直接用:

$(document).ready(function() { /* code here */ });

$(function() { /* code here */ });

11.2. external file loaded

外部文件加载完毕,也会收到 onload 事件。

11.3. beforeunload

关闭网页之时被调用

onbeforeunload = function (e) {
    var dialogText = 'Dialog text here';
    e.returnValue = dialogText;
    return dialogText;
}

Starting with Firefox 4, Chrome 51, Opera 38 and Safari 9.1, a generic string not under the control of the webpage will be shown instead of the returned string. 

12. Script Execution Timeline

12.1. start executing

js 代码何时开始执行?

在<body> 中遇到<script> 标签中的代码,立即开始执行。

requestAnimationFrame(myFunc) ,在浏览器刷新页面时,执行myFunc。

event触发时执行 event handler。

js 代码顺序执行,默认是不会并发的,即使是event触发的代码,也是顺次执行,执行完一个handler,再执行其他代码。

12.2. web worker

Worker 给js提供了一种异步并发机制。举个简单的例子:

// worker.html
<p>Count numbers: <output id="result"></output></p>
<button οnclick="startWorker()">Start Worker</button>
<button οnclick="stopWorker()">Stop Worker</button>
<br /><br />

<script>
    var w;
    function startWorker() {
        if (typeof(Worker) != "undefined") {
            if (typeof(w) == "undefined") {
                w = new Worker("js/demo_worker.js");
            }

            w.onmessage = function (event) {
                document.getElementById("result").innerHTML = event.data;
            };
        }
        else {
            document.getElementById("result").innerHTML = "Sorry, your browser does not support Web Worker...";
        }
    }

    function stopWorker() {
        w.terminate();
    }
</script>
// js/demo_worker.js
var i=0;
function timedCount() {
    i = i+1;
    postMessage(i);
    setTimeout("timedCount()", 500);
}
timedCount();
用一个js文件构造出Worker对象。这个Worker对象的代码会和其他 js 代码并发执行。

js/demo_worker.js 中,用 postMessage(xx) 与Worker对象通信: w.onmessage = function(event) { ... }

这个例子貌似没什么用处,只是演示了如何使用Worker。 当有比较繁重的任务需要并发时,可以写在 js/demo_worker.js 中。


13. Setting Timers

13.1. setTimeOut( ) / clearTimeOut( )

var bombTimer = setTimeout(function() {
  console.log("BOOM!");
}, 500);

if (Math.random() < 0.5) { // 50% chance
  console.log("Defused.");
  clearTimeout(bombTimer);
}
13.2.  requestAnimationFrame() / cancelAnimationFrame()
13.3. 
setInterval() / clearInterval()
var ticks = 0;
var clock = setInterval(function() {
  console.log("tick", ticks++);
  if (ticks == 10) {
    clearInterval(clock);
    console.log("stop.");
  }
}, 200);

14. Debouncing

目标:防止频繁多次响应一个event

用timeOut,如果间隔小于500ms,只处理最后一个event,前面的都cancel。

<textarea>Type something here...</textarea>
<script>
  var textarea = document.querySelector("textarea");
  var timeout;
  textarea.addEventListener("keydown", function() {
    clearTimeout(timeout);
    timeout = setTimeout(function() {
      console.log("You stopped typing.");
    }, 500);
  });
</script>

用timeOut,每隔固定时间响应一次。

<script>
  function displayCoords(event) {
    document.body.textContent =
      "Mouse at " + event.pageX + ", " + event.pageY;
  }

  var scheduled = false, lastEvent;
  addEventListener("mousemove", function(event) {
    lastEvent = event;
    if (!scheduled) {
      scheduled = true;
      setTimeout(function() {
        scheduled = false;
        displayCoords(lastEvent);
      }, 250);
    }
  });
</script>

15. Exercise: Censored Keyboard

阻止用户输入Q,W,X:

<input type="text">
<script>
    var field = document.querySelector("input");
    field.addEventListener("keypress", function (event) {
        if (/[qwx]/i.test(String.fromCharCode(event.charCode))) {
            event.preventDefault();
        }
    });
</script>

16. Exercise: Mouse Trail

    <style>
        .trail { /* className for the trail elements */
            position: absolute;
            height: 6px; width: 6px;
            border-radius: 3px;
            background: teal;
        }
        body {
            height: 300px;
        }
    </style>

<script>
    var dots = [];
    for (var i = 0; i < 12; i++) {
        var node = document.createElement("div");
        node.className = "trail";
        document.body.appendChild(node);
        dots.push(node);
    }

    var currentDot = 0;

    addEventListener("mousemove", function(event) {
        var dot = dots[currentDot];
        dot.style.left = (event.pageX - 3) + "px";
        dot.style.top = (event.pageY - 3) + "px";
        currentDot = (currentDot + 1) % dots.length;
    });

</script>
用十二个圆点跟随鼠标移动。

17. Exercise: Tabs

<div id="wrapper">
    <div data-tabname="one">Tab one</div>
    <div data-tabname="two">Tab two</div>
    <div data-tabname="three">Tab three</div>
</div>
<script>
    function asTabs(node) {
        var tabs = [];
        for (var i = 0; i < node.childNodes.length; i++) {
            var child = node.childNodes[i];
            if (child.nodeType == document.ELEMENT_NODE)
                tabs.push(child);
        }

        var tabList = document.createElement("div");
        tabs.forEach(function(tab, i) {
            var button = document.createElement("button");
            button.textContent = tab.getAttribute("data-tabname");
            button.addEventListener("click", function() { selectTab(i); });
            tabList.appendChild(button);
        });
        node.insertBefore(tabList, node.firstChild);

        function selectTab(n) {
            tabs.forEach(function(tab, i) {
                if (i == n)
                    tab.style.display = "";
                else
                    tab.style.display = "none";
            });
            for (var i = 0; i < tabList.childNodes.length; i++) {
                if (i == n)
                    tabList.childNodes[i].style.background = "violet";
                else
                    tabList.childNodes[i].style.background = "";
            }
        }
        selectTab(0);
    }
    asTabs(document.querySelector("#wrapper"));
</script>


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值