## Chapter 14
# Handling Events
> [](http://eloquentjavascript.net/14_event.html#p_9vGtY0kynX)You have power over your mind—not outside events. Realize this, and you will find strength.
>
> Marcus Aurelius, Meditations
[](http://eloquentjavascript.net/14_event.html#p_/DC8VWovoV)Some programs work with direct user input, such as mouse and keyboard interaction. The timing and order of such input can’t be predicted in advance. This requires a different approach to control flow than the one we have used so far.
## [](http://eloquentjavascript.net/14_event.html#h_HQoLxG2r2l)Event handlers
[](http://eloquentjavascript.net/14_event.html#p_4crjoEGjCE)Imagine an interface where the only way to find out whether a key on the keyboard is being pressed is to read the current state of that key. To be able to react to keypresses, you would have to constantly read the key’s state so that you’d catch it before it’s released again. It would be dangerous to perform other time-intensive computations since you might miss a keypress.
[](http://eloquentjavascript.net/14_event.html#p_gL+2BzAZqa)That is how such input was handled on primitive machines. A step up would be for the hardware or operating system to notice the keypress and put it in a queue. A program can then periodically check the queue for new events and react to what it finds there.
[](http://eloquentjavascript.net/14_event.html#p_llZbht+m+p)Of course, it has to remember to look at the queue, and to do it often, because any time between the key being pressed and the program noticing the event will cause the software to feel unresponsive. This approach is called *polling*. Most programmers avoid it whenever possible.
[](http://eloquentjavascript.net/14_event.html#p_yR0Vf6qqc8)A better mechanism is for the underlying system to give our code a chance to react to events as they occur. Browsers do this by allowing us to register functions as *handlers* for specific events.
~~~
<p>Click this document to activate the handler.</p>
<script>
addEventListener("click", function() {
console.log("You clicked!");
});
</script>
~~~
[](http://eloquentjavascript.net/14_event.html#p_MOjheGCW5Y)The `addEventListener` function registers its second argument to be called whenever the event described by its first argument occurs.
## [](http://eloquentjavascript.net/14_event.html#h_Kx1VwAV7ei)Events and DOM nodes
[](http://eloquentjavascript.net/14_event.html#p_OFueY9yZeF)Each browser event handler is registered in a context. When you call`addEventListener` as shown previously, you are calling it as a method on the whole window because in the browser the global scope is equivalent to the`window` object. Every DOM element has its own `addEventListener` method, which allows you to listen specifically on that element.
~~~
<button>Click me</button>
<p>No handler here.</p>
<script>
var button = document.querySelector("button");
button.addEventListener("click", function() {
console.log("Button clicked.");
});
</script>
~~~
[](http://eloquentjavascript.net/14_event.html#p_dN0xA6sZi2)The example attaches a handler to the button node. Thus, clicks on the button cause that handler to run, whereas clicks on the rest of the document do not.
[](http://eloquentjavascript.net/14_event.html#p_baR6w+cVeA)Giving a node an `onclick` attribute has a similar effect. But a node has only one `onclick` attribute, so you can register only one handler per node that way. The `addEventListener` method allows you to add any number of handlers, so you can’t accidentally replace a handler that has already been registered.
[](http://eloquentjavascript.net/14_event.html#p_/QIdzEWfHg)The `removeEventListener` method, called with arguments similar to as`addEventListener`, removes a handler.
~~~
<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>
~~~
[](http://eloquentjavascript.net/14_event.html#p_Y/CadjfuHR)To be able to unregister a handler function, we give it a name (such as `once`) so that we can pass it to both `addEventListener` and `removeEventListener`.
## [](http://eloquentjavascript.net/14_event.html#h_0d6qd0WrDY)Event objects
[](http://eloquentjavascript.net/14_event.html#p_xMV45J9tPM)Though we have ignored it in the previous examples, event handler functions are passed an argument: the *event object*. This object gives us additional information about the event. For example, if we want to know *which* mouse button was pressed, we can look at the event object’s `which` property.
~~~
<button>Click me any way you want</button>
<script>
var button = document.querySelector("button");
button.addEventListener("mousedown", function(event) {
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>
~~~
[](http://eloquentjavascript.net/14_event.html#p_6Vk6va5Rnz)The information stored in an event object differs per type of event. We’ll discuss various types later in this chapter. The object’s `type` property always holds a string identifying the event (for example `"click"` or `"mousedown"`).
## [](http://eloquentjavascript.net/14_event.html#h_NEhx0cDpml)Propagation
[](http://eloquentjavascript.net/14_event.html#p_AMEe+Oabfh)Event handlers registered on nodes with children will also receive some events that happen in the children. If a button inside a paragraph is clicked, event handlers on the paragraph will also receive the click event.
[](http://eloquentjavascript.net/14_event.html#p_UBfEVzqY/E)But if both the paragraph and the button have a handler, the more specific handler—the one on the button—gets to go first. The event is said to *propagate*outward, from the node where it happened to that node’s parent node and on to the root of the document. Finally, after all handlers registered on a specific node have had their turn, handlers registered on the whole window get a chance to respond to the event.
[](http://eloquentjavascript.net/14_event.html#p_mgXPeV1qcP)At any point, an event handler can call the `stopPropagation` method on the event object to prevent handlers “further up” from receiving the event. This can be useful when, for example, you have a button inside another clickable element and you don’t want clicks on the button to activate the outer element’s click behavior.
[](http://eloquentjavascript.net/14_event.html#p_qKf3t2phih)The following example registers `"mousedown"` handlers on both a button and the paragraph around it. When clicked with the right mouse button, the handler for the button calls `stopPropagation`, which will prevent the handler on the paragraph from running. When the button is clicked with another mouse button, both handlers will run.
~~~
<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>
~~~
[](http://eloquentjavascript.net/14_event.html#p_NocTrrs2K+)Most event objects have a `target` property that refers to the node where they originated. You can use this property to ensure that you’re not accidentally handling something that propagated up from a node you do not want to handle.
[](http://eloquentjavascript.net/14_event.html#p_RrFjk/g1ly)It is also possible to use the `target` property to cast a wide net for a specific type of event. For example, if you have a node containing a long list of buttons, it may be more convenient to register a single click handler on the outer node and have it use the `target` property to figure out whether a button was clicked, rather than register individual handlers on all of the buttons.
~~~
<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>
~~~
## [](http://eloquentjavascript.net/14_event.html#h_GaHJsztrot)Default actions
[](http://eloquentjavascript.net/14_event.html#p_mNZd3hJWtB)Many events have a default action associated with them. If you click a link, you will be taken to the link’s target. If you press the down arrow, the browser will scroll the page down. If you right-click, you’ll get a context menu. And so on.
[](http://eloquentjavascript.net/14_event.html#p_QdllRyXgOw)For most types of events, the JavaScript event handlers are called *before* the default behavior is performed. If the handler doesn’t want the normal behavior to happen, typically because it has already taken care of handling the event, it can call the `preventDefault` method on the event object.
[](http://eloquentjavascript.net/14_event.html#p_qt5hvvY54m)This can be used to implement your own keyboard shortcuts or context menu. It can also be used to obnoxiously interfere with the behavior that users expect. For example, here is a link that cannot be followed:
~~~
<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>
~~~
[](http://eloquentjavascript.net/14_event.html#p_nc872RiHdX)Try not to do such things unless you have a really good reason to. For people using your page, it can be unpleasant when the behavior they expect is broken.
[](http://eloquentjavascript.net/14_event.html#p_KMgK6E70da)Depending on the browser, some events can’t be intercepted. On Chrome, for example, keyboard shortcuts to close the current tab (Ctrl-W or Command-W) cannot be handled by JavaScript.
## [](http://eloquentjavascript.net/14_event.html#h_974t15Z9oa)Key events
[](http://eloquentjavascript.net/14_event.html#p_dJaRso8RxV)When a key on the keyboard is pressed, your browser fires a `"keydown"` event. When it is released, a `"keyup"` event fires.
~~~
<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>
~~~
[](http://eloquentjavascript.net/14_event.html#p_gQnz+6p78i)Despite its name, `"keydown"` fires not only when the key is physically pushed down. When a key is pressed and held, the event fires again every time the key*repeats*. Sometimes—for example if you want to increase the acceleration of a game character when an arrow key is pressed and decrease it again when the key is released—you have to be careful not to increase it again every time the key repeats or you’d end up with unintentionally huge values.
[](http://eloquentjavascript.net/14_event.html#p_YN1AGK2x1E)The previous example looked at the `keyCode` property of the event object. This is how you can identify which key is being pressed or released. Unfortunately, it’s not always obvious how to translate the numeric key code to an actual key.
[](http://eloquentjavascript.net/14_event.html#p_y/Pr/lCtnH)For letter and number keys, the associated key code will be the Unicode character code associated with the (uppercase) letter or number printed on the key. The `charCodeAt` method on strings gives us a way to find this code.
~~~
console.log("Violet".charCodeAt(0));
// → 86
console.log("1".charCodeAt(0));
// → 49
~~~
[](http://eloquentjavascript.net/14_event.html#p_HVPFsxHIiv)Other keys have less predictable key codes. The best way to find the codes you need is usually by experimenting—register a key event handler that logs the key codes it gets and press the key you are interested in.
[](http://eloquentjavascript.net/14_event.html#p_4cdQPevWxW)Modifier keys such as Shift, Ctrl, Alt, and Meta (Command on Mac) generate key events just like normal keys. But when looking for key combinations, you can also find out whether these keys are held down by looking at the `shiftKey`,`ctrlKey`, `altKey`, and `metaKey` properties of keyboard and mouse events.
~~~
<p>Press Ctrl-Space to continue.</p>
<script>
addEventListener("keydown", function(event) {
if (event.keyCode == 32 && event.ctrlKey)
console.log("Continuing!");
});
</script>
~~~
[](http://eloquentjavascript.net/14_event.html#p_p/+N5nU+rr)The `"keydown"` and `"keyup"` events give you information about the physical key that is being pressed. But what if you are interested in the actual text being typed? Getting that text from key codes is awkward. Instead, there exists another event, `"keypress"`, which fires right after `"keydown"` (and repeated along with `"keydown"` when the key is held) but only for keys that produce character input. The `charCode` property in the event object contains a code that can be interpreted as a Unicode character code. We can use the`String.fromCharCode` function to turn this code into an actual single-character string.
~~~
<p>Focus this page and type something.</p>
<script>
addEventListener("keypress", function(event) {
console.log(String.fromCharCode(event.charCode));
});
</script>
~~~
[](http://eloquentjavascript.net/14_event.html#p_EHGULml9MQ)The DOM node where a key event originates depends on the element that has focus when the key is pressed. Normal nodes cannot have focus (unless you give them a `tabindex` attribute), but things such as links, buttons, and form fields can. We’ll come back to form fields in [Chapter 18](http://eloquentjavascript.net/18_forms.html#forms). When nothing in particular has focus, `document.body` acts as the target node of key events.
## [](http://eloquentjavascript.net/14_event.html#h_D5iwImkmyt)Mouse clicks
[](http://eloquentjavascript.net/14_event.html#p_zZHDVhEhYY)Pressing a mouse button also causes a number of events to fire. The`"mousedown"` and `"mouseup"` events are similar to `"keydown"` and `"keyup"`and fire when the button is pressed and released. These will happen on the DOM nodes that are immediately below the mouse pointer when the event occurs.
[](http://eloquentjavascript.net/14_event.html#p_vxFLT2fw8e)After the `"mouseup"` event, a `"click"` event fires on the most specific node that contained both the press and the release of the button. For example, if I press down the mouse button on one paragraph and then move the pointer to another paragraph and release the button, the `"click"` event will happen on the element that contains both those paragraphs.
[](http://eloquentjavascript.net/14_event.html#p_gzmmLlVcMF)If two clicks happen close together, a `"dblclick"` (double-click) event also fires, after the second click event.
[](http://eloquentjavascript.net/14_event.html#p_V3QuKrAk2Z)To get precise information about the place where a mouse event happened, you can look at its `pageX` and `pageY` properties, which contain the event’s coordinates (in pixels) relative to the top-left corner of the document.
[](http://eloquentjavascript.net/14_event.html#p_A7YDC3hfu1)The following implements a primitive drawing program. Every time you click the document, it adds a dot under your mouse pointer. See [Chapter 19](http://eloquentjavascript.net/19_paint.html#paint) for a less primitive drawing program.
~~~
<style>
body {
height: 200px;
background: beige;
}
.dot {
height: 8px; width: 8px;
border-radius: 4px; /* rounds corners */
background: blue;
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>
~~~
[](http://eloquentjavascript.net/14_event.html#p_qAqiJ6l3wp)The `clientX` and `clientY` properties are similar to `pageX` and `pageY` but relative to the part of the document that is currently scrolled into view. These can be useful when comparing mouse coordinates with the coordinates returned by `getBoundingClientRect`, which also returns viewport-relative coordinates.
## [](http://eloquentjavascript.net/14_event.html#h_XojjiOmg7v)Mouse motion
[](http://eloquentjavascript.net/14_event.html#p_Yjy/EtP24n)Every time the mouse pointer moves, a `"mousemove"` event fires. This event can be used to track the position of the mouse. A common situation in which this is useful is when implementing some form of mouse-dragging functionality.
[](http://eloquentjavascript.net/14_event.html#p_Z1h4BQYT0/)As an example, the following program displays a bar and sets up event handlers so that dragging to the left or right on this bar makes it narrower or wider:
~~~
<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>
~~~
[](http://eloquentjavascript.net/14_event.html#p_OkAFTS9SkH)Note that the `"mousemove"` handler is registered on the whole window. Even if the mouse goes outside of the bar during resizing, we still want to update its size and stop dragging when the mouse is released.
[](http://eloquentjavascript.net/14_event.html#p_fndkFYbayW)We must stop resizing the bar when the mouse button is released. Unfortunately, not all browsers give `"mousemove"` events a meaningful `which`property. There is a standard property called `buttons`, which provides similar information, but that is also not supported on all browsers. Fortunately, all major browsers support either `buttons` or `which`, so the `buttonPressed`function in the example first tries `buttons`, and falls back to `which` when that isn’t available.
[](http://eloquentjavascript.net/14_event.html#p_H543iFvHsm)Whenever the mouse pointer enters or leaves a node, a `"mouseover"` or`"mouseout"` event fires. These two events can be used, among other things, to create hover effects, showing or styling something when the mouse is over a given element.
[](http://eloquentjavascript.net/14_event.html#p_5IESlahWDS)Unfortunately, creating such an effect is not as simple as starting the effect on`"mouseover"` and ending it on `"mouseout"`. When the mouse moves from a node onto one of its children, `"mouseout"` fires on the parent node, though the mouse did not actually leave the node’s extent. To make things worse, these events propagate just like other events, and thus you will also receive`"mouseout"` events when the mouse leaves one of the child nodes of the node on which the handler is registered.
[](http://eloquentjavascript.net/14_event.html#p_WkAAgbH7zo)To work around this problem, we can use the `relatedTarget` property of the event objects created for these events. It tells us, in the case of `"mouseover"`, what element the pointer was over before and, in the case of `"mouseout"`, what element it is going to. We want to change our hover effect only when the`relatedTarget` is outside of our target node. Only in that case does this event actually represent a *crossing over* from outside to inside the node (or the other way around).
~~~
<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>
~~~
[](http://eloquentjavascript.net/14_event.html#p_QTE0dpHOFx)The `isInside` function follows the given node’s parent links until it either reaches the top of the document (when `node` becomes null) or finds the parent we are looking for.
[](http://eloquentjavascript.net/14_event.html#p_ujj+9rrIE3)I should add that a hover effect like this can be much more easily achieved using the CSS *pseudoselector* `:hover`, as the next example shows. But when your hover effect involves doing something more complicated than changing a style on the target node, you must use the trick with `"mouseover"` and`"mouseout"` events.
~~~
<style>
p:hover { color: red }
</style>
<p>Hover over this <strong>paragraph</strong>.</p>
~~~
## [](http://eloquentjavascript.net/14_event.html#h_xGSp7W5DAZ)Scroll events
[](http://eloquentjavascript.net/14_event.html#p_QhFs2TfUWV)Whenever an element is scrolled, a `"scroll"` event fires on it. This has various uses, such as knowing what the user is currently looking at (for disabling off-screen animations or sending spy reports to your evil headquarters) or showing some indication of progress (by highlighting part of a table of contents or showing a page number).
[](http://eloquentjavascript.net/14_event.html#p_koAfRfBrN2)The following example draws a progress bar in the top-right corner of the document and updates it to fill up as you scroll down:
~~~
<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>
~~~
[](http://eloquentjavascript.net/14_event.html#p_J92IjzNbC5)Giving an element a `position` of `fixed` acts much like an `absolute` position but also prevents it from scrolling along with the rest of the document. The effect is to make our progress bar stay in its corner. Inside it is another element, which is resized to indicate the current progress. We use `%`, rather than `px`, as a unit when setting the width so that the element is sized relative to the whole bar.
[](http://eloquentjavascript.net/14_event.html#p_Q5ZDL6Y/Rx)The global `innerHeight` variable gives us the height of the window, which we have to subtract from the total scrollable height—you can’t keep scrolling when you hit the bottom of the document. (There’s also an `innerWidth` to go along with `innerHeight`.) By dividing `pageYOffset`, the current scroll position, by the maximum scroll position and multiplying by 100, we get the percentage for the progress bar.
[](http://eloquentjavascript.net/14_event.html#p_0tTa0Fp6S+)Calling `preventDefault` on a scroll event does not prevent the scrolling from happening. In fact, the event handler is called only *after* the scrolling takes place.
## [](http://eloquentjavascript.net/14_event.html#h_NoKd+BgJRm)Focus events
[](http://eloquentjavascript.net/14_event.html#p_r6SF152VBY)When an element gains focus, the browser fires a `"focus"` event on it. When it loses focus, a `"blur"` event fires.
[](http://eloquentjavascript.net/14_event.html#p_rU6XEBvIwF)Unlike the events discussed earlier, these two events do not propagate. A handler on a parent element is not notified when a child element gains or loses focus.
[](http://eloquentjavascript.net/14_event.html#p_XbY3zpvUvn)The following example displays help text for the text field that currently has focus:
~~~
<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");
for (var i = 0; i < fields.length; i++) {
fields[i].addEventListener("focus", function(event) {
var text = event.target.getAttribute("data-help");
help.textContent = text;
});
fields[i].addEventListener("blur", function(event) {
help.textContent = "";
});
}
</script>
~~~
[](http://eloquentjavascript.net/14_event.html#p_5HyY0t9dpU)The window object will receive `"focus"` and `"blur"` events when the user moves from or to the browser tab or window in which the document is shown.
## [](http://eloquentjavascript.net/14_event.html#h_NmV8RP8lpt)Load event
[](http://eloquentjavascript.net/14_event.html#p_NarSNA3PKB)When a page finishes loading, the `"load"` event fires on the window and the document body objects. This is often used to schedule initialization actions that require the whole document to have been built. Remember that the content of`<script>` tags is run immediately when the tag is encountered. This is often too soon, such as when the script needs to do something with parts of the document that appear after the `<script>` tag.
[](http://eloquentjavascript.net/14_event.html#p_hCgodGiFEt)Elements such as images and script tags that load an external file also have a`"load"` event that indicates the files they reference were loaded. Like the focus-related events, loading events do not propagate.
[](http://eloquentjavascript.net/14_event.html#p_nu8/BUQa7r)When a page is closed or navigated away from (for example by following a link), a `"beforeunload"` event fires. The main use of this event is to prevent the user from accidentally losing work by closing a document. Preventing the page from unloading is not, as you might expect, done with the`preventDefault` method. Instead, it is done by returning a string from the handler. The string will be used in a dialog that asks the user if they want to stay on the page or leave it. This mechanism ensures that a user is able to leave the page, even if it is running a malicious script that would prefer to keep them there forever in order to force them to look at dodgy weight loss ads.
## [](http://eloquentjavascript.net/14_event.html#h_cj44kRfk/h)Script execution timeline
[](http://eloquentjavascript.net/14_event.html#p_m898wVhAww)There are various things that can cause a script to start executing. Reading a`<script>` tag is one such thing. An event firing is another. [Chapter 13](http://eloquentjavascript.net/13_dom.html#animationFrame)discussed the `requestAnimationFrame` function, which schedules a function to be called before the next page redraw. That is yet another way in which a script can start running.
[](http://eloquentjavascript.net/14_event.html#p_3Ya6RuV22D)It is important to understand that even though events can fire at any time, no two scripts in a single document ever run at the same moment. If a script is already running, event handlers and pieces of code scheduled in other ways have to wait for their turn. This is the reason why a document will freeze when a script runs for a long time. The browser cannot react to clicks and other events inside the document because it can’t run event handlers until the current script finishes running.
[](http://eloquentjavascript.net/14_event.html#p_qOcAjNuS8p)Some programming environments do allow multiple *threads of execution* to run at the same time. Doing multiple things at the same time can be used to make a program faster. But when you have multiple actors touching the same parts of the system at the same time, thinking about a program becomes at least an order of magnitude harder.
[](http://eloquentjavascript.net/14_event.html#p_omeCxjcdIQ)The fact that JavaScript programs do only one thing at a time makes our lives easier. For cases where you *really* do want to do some time-consuming thing in the background without freezing the page, browsers provide something called*web workers*. A worker is an isolated JavaScript environment that runs alongside the main program for a document and can communicate with it only by sending and receiving messages.
[](http://eloquentjavascript.net/14_event.html#p_dXLk5WXFTx)Assume we have the following code in a file called `code/squareworker.js`:
~~~
addEventListener("message", function(event) {
postMessage(event.data * event.data);
});
~~~
[](http://eloquentjavascript.net/14_event.html#p_3bETV2ub20)Imagine that squaring a number is a heavy, long-running computation that we want to perform in a background thread. This code spawns a worker, sends it a few messages, and outputs the responses.
~~~
var squareWorker = new Worker("code/squareworker.js");
squareWorker.addEventListener("message", function(event) {
console.log("The worker responded:", event.data);
});
squareWorker.postMessage(10);
squareWorker.postMessage(24);
~~~
[](http://eloquentjavascript.net/14_event.html#p_Stp8nkaZHV)The `postMessage` function sends a message, which will cause a `"message"`event to fire in the receiver. The script that created the worker sends and receives messages through the `Worker` object, whereas the worker talks to the script that created it by sending and listening directly on its global scope—which is a *new* global scope, not shared with the original script.
## [](http://eloquentjavascript.net/14_event.html#h_WCvdQCKgu8)Setting timers
[](http://eloquentjavascript.net/14_event.html#p_5YmF46Q06c)The `setTimeout` function is similar to `requestAnimationFrame`. It schedules another function to be called later. But instead of calling the function at the next redraw, it waits for a given amount of milliseconds. This page turns from blue to yellow after two seconds:
~~~
<script>
document.body.style.background = "blue";
setTimeout(function() {
document.body.style.background = "yellow";
}, 2000);
</script>
~~~
[](http://eloquentjavascript.net/14_event.html#p_de3VjAL+lV)Sometimes you need to cancel a function you have scheduled. This is done by storing the value returned by `setTimeout` and calling `clearTimeout` on it.
~~~
var bombTimer = setTimeout(function() {
console.log("BOOM!");
}, 500);
if (Math.random() < 0.5) { // 50% chance
console.log("Defused.");
clearTimeout(bombTimer);
}
~~~
[](http://eloquentjavascript.net/14_event.html#p_c/F+71/NPg)The `cancelAnimationFrame` function works in the same way as`clearTimeout`—calling it on a value returned by `requestAnimationFrame`will cancel that frame (assuming it hasn’t already been called).
[](http://eloquentjavascript.net/14_event.html#p_MTqUUpmFIN)A similar set of functions, `setInterval` and `clearInterval` are used to set timers that should repeat every *X* milliseconds.
~~~
var ticks = 0;
var clock = setInterval(function() {
console.log("tick", ticks++);
if (ticks == 10) {
clearInterval(clock);
console.log("stop.");
}
}, 200);
~~~
## [](http://eloquentjavascript.net/14_event.html#h_AOVmaqj10I)Debouncing
[](http://eloquentjavascript.net/14_event.html#p_I3Osg2AlyQ)Some types of events have the potential to fire rapidly, many times in a row (the `"mousemove"` and `"scroll"` events, for example). When handling such events, you must be careful not to do anything too time-consuming or your handler will take up so much time that interaction with the document starts to feel slow and choppy.
[](http://eloquentjavascript.net/14_event.html#p_CvRYtJPZwz)If you do need to do something nontrivial in such a handler, you can use`setTimeout` to make sure you are not doing it too often. This is usually called*debouncing* the event. There are several slightly different approaches to this.
[](http://eloquentjavascript.net/14_event.html#p_8qDwbjQg0l)In the first example, we want to do something when the user has typed something, but we don’t want to do it immediately for every key event. When they are typing quickly, we just want to wait until a pause occurs. Instead of immediately performing an action in the event handler, we set a timeout instead. We also clear the previous timeout (if any) so that when events occur close together (closer than our timeout delay), the timeout from the previous event will be canceled.
~~~
<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>
~~~
[](http://eloquentjavascript.net/14_event.html#p_55az3iLtsc)Giving an undefined value to `clearTimeout` or calling it on a timeout that has already fired has no effect. Thus, we don’t have to be careful about when to call it, and we simply do so for every event.
[](http://eloquentjavascript.net/14_event.html#p_GisH+i+4tv)We can use a slightly different pattern if we want to space responses so that they’re separated by at least a certain length of time but want to fire them*during* a series of events, not just afterward. For example, we might want to respond to `"mousemove"` events by showing the current coordinates of the mouse, but only every 250 milliseconds.
~~~
<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>
~~~
## [](http://eloquentjavascript.net/14_event.html#h_ErccPg/l98)Summary
[](http://eloquentjavascript.net/14_event.html#p_bKLVanKSm7)Event handlers make it possible to detect and react to events we have no direct control over. The `addEventListener` method is used to register such a handler.
[](http://eloquentjavascript.net/14_event.html#p_pT/A7mXxlh)Each event has a type (`"keydown"`, `"focus"`, and so on) that identifies it. Most events are called on a specific DOM element and then *propagate* to that element’s ancestors, allowing handlers associated with those elements to handle them.
[](http://eloquentjavascript.net/14_event.html#p_jK5bk5CuMw)When an event handler is called, it is passed an event object with additional information about the event. This object also has methods that allow us to stop further propagation (`stopPropagation`) and prevent the browser’s default handling of the event (`preventDefault`).
[](http://eloquentjavascript.net/14_event.html#p_8R4tiRRH3a)Pressing a key fires `"keydown"`, `"keypress"`, and `"keyup"` events. Pressing a mouse button fires `"mousedown"`, `"mouseup"`, and `"click"` events. Moving the mouse fires `"mousemove"` and possibly `"mouseenter"` and `"mouseout"`events.
[](http://eloquentjavascript.net/14_event.html#p_zwo0vgzm7O)Scrolling can be detected with the `"scroll"` event, and focus changes can be detected with the `"focus"` and `"blur"` events. When the document finishes loading, a `"load"` event fires on the window.
[](http://eloquentjavascript.net/14_event.html#p_0zJSMPD1MF)Only one piece of JavaScript program can run at a time. Thus, event handlers and other scheduled scripts have to wait until other scripts finish before they get their turn.
## [](http://eloquentjavascript.net/14_event.html#h_TcUD2vzyMe)Exercises
### [](http://eloquentjavascript.net/14_event.html#h_Lhbs8f7VGD)Censored keyboard
[](http://eloquentjavascript.net/14_event.html#p_EvVPidig6g)Between 1928 and 2013, Turkish law forbade the use of the letters *Q*, *W*, and *X*in official documents. This was part of a wider initiative to stifle Kurdish culture—those letters occur in the language used by Kurdish people but not in Istanbul Turkish.
[](http://eloquentjavascript.net/14_event.html#p_TZAfuJye+C)As an exercise in doing ridiculous things with technology, I’m asking you to program a text field (an `<input type="text">` tag) that these letters cannot be typed into.
[](http://eloquentjavascript.net/14_event.html#p_cs0DibmEsI)(Do not worry about copy and paste and other such loopholes.)
~~~
<input type="text">
<script>
var field = document.querySelector("input");
// Your code here.
</script>
~~~
### [](http://eloquentjavascript.net/14_event.html#h_NOgRH0Y9st)Mouse trail
[](http://eloquentjavascript.net/14_event.html#p_j4+NSbR+hs)In JavaScript’s early days, which was the high time of gaudy home pages with lots of animated images, people came up with some truly inspiring ways to use the language.
[](http://eloquentjavascript.net/14_event.html#p_ZseDCwl6/C)One of these was the “mouse trail”—a series of images that would follow the mouse pointer as you moved it across the page.
[](http://eloquentjavascript.net/14_event.html#p_0yy1fiF5Gm)In this exercise, I want you to implement a mouse trail. Use absolutely positioned `<div>` elements with a fixed size and background color (refer to the[code](http://eloquentjavascript.net/14_event.html#mouse_drawing) in the “Mouse Clicks” section for an example). Create a bunch of such elements and, when the mouse moves, display them in the wake of the mouse pointer.
[](http://eloquentjavascript.net/14_event.html#p_5jL8mAUnIQ)There are various possible approaches here. You can make your solution as simple or as complex as you want. A simple solution to start with is to keep a fixed number of trail elements and cycle through them, moving the next one to the mouse’s current position every time a `"mousemove"` event occurs.
~~~
<style>
.trail { /* className for the trail elements */
position: absolute;
height: 6px; width: 6px;
border-radius: 3px;
background: teal;
}
body {
height: 300px;
}
</style>
<script>
// Your code here.
</script>
~~~
### [](http://eloquentjavascript.net/14_event.html#h_Kk1WKx2anJ)Tabs
[](http://eloquentjavascript.net/14_event.html#p_Ktj5JZEPdu)A tabbed interface is a common design pattern. It allows you to select an interface panel by choosing from a number of tabs “sticking out” above an element.
[](http://eloquentjavascript.net/14_event.html#p_rh5X8kUE8g)In this exercise you’ll implement a simple tabbed interface. Write a function,`asTabs`, that takes a DOM node and creates a tabbed interface showing the child elements of that node. It should insert a list of `<button>` elements at the top of the node, one for each child element, containing text retrieved from the`data-tabname` attribute of the child. All but one of the original children should be hidden (given a `display` style of `none`), and the currently visible node can be selected by clicking the buttons.
[](http://eloquentjavascript.net/14_event.html#p_744xb2mKFr)When it works, extend it to also style the currently active button differently.
~~~
<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) {
// Your code here.
}
asTabs(document.querySelector("#wrapper"));
</script>
~~~
> One pitfall you’ll probably run into is that you can’t directly use the node’s childNodes property as a collection of tab nodes. For one thing, when you add the buttons, they will also become child nodes and end up in this object because it is live. For another, the text nodes created for the whitespace between the nodes are also in there and should not get their own tabs.
> To work around this, start by building up a real array of all the children in the wrapper that have a nodeType of 1.
> When registering event handlers on the buttons, the handler functions will need to know which tab element is associated with the button. If they are created in a normal loop, you can access the loop index variable from inside the function, but it won’t give you the correct number because that variable will have been further changed by the loop.
> A simple workaround is to use the forEach method and create the handler functions from inside the function passed to forEach. The loop index, which is passed as a second argument to that function, will be a normal local variable there and won’t be overwritten by further iterations.
- perface
- Introduction
- Part 1: Language
- 1 Values, Types, and Operators
- 2 Program Structure
- 3 Functions
- 4 Data Structures: Objects and Arrays
- 5 Higher-order Functions
- 6 The Secret Life of Objects
- 7 Project: Electronic Life
- 8 Bugs and Error Handling
- 9 Regular Expressions
- 10 Modules
- 11 Project: A Programming Language
- Part 2: Browser
- 12 JavaScript and the Browser
- 13 The Document Object Model
- 14 Handling Events
- 15 Project: A Platform Game
- 16 Drawing on Canvas
- 17 HTTP
- 18 Forms and Form Fields
- 19 Project: A Paint Program
- Part 3: Node
- 20 Node.js
- 21 Project: Skill-Sharing Website
- Other Pages