I. DOM
what: document object model
who&why:
- the browser build the DOM
- used to draw the page on screen
- provide interface to JS to dynamically manipulate elements on the page
How to access it:
document
objectdocument.documentElement
=> ‘ …‘
moving throught the DOM tree
- traverse only element nodes
children
- traverse all node types: element node, text node, comment node, …
childNodes
parentNode
firstChild
lastChild
previousSibling
nextSibling
- traverse only element nodes
traverse tree e.g.:
recursive functions are often useful. The following function scans a document for text nodes containing a given string and returns
true
when it has found one:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function talksAbout(node, string) {
if (node.nodeType == Node.ELEMENT_NODE) {
for (let i = 0; i < node.childNodes.length; i++) {
if (talksAbout(node.childNodes[i], string)) {
return true;
}
}
return false;
} else if (node.nodeType == Node.TEXT_NODE) {
return node.nodeValue.indexOf(string) > -1;
}
}
console.log(talksAbout(document.body, "book"));
// → true
find element
document.querySelector("idName");
- first
Element
within the document that matches the specified selector, or group of selectors. If no matches are found,null
is returned. var el = document.querySelector(".myclass");
var el = document.querySelector("div.user-panel.main input[name='login']");
- Here, the first <input> element with the name “login” (
<input name="login"/>
) located inside a <div> whose class is “user-panel main” (<div class="user-panel main">
) in the document is returned.
- Here, the first <input> element with the name “login” (
- first
document.getElementById("idName");
document.getElementsByTagName("idName");
document.getElementsByClassName("idName");
document.getElementsByName()
when inside html we have<input name="down">
change document
1
2
3
4<script>
let paragraphs = document.body.getElementsByTagName("p");
document.body.insertBefore(paragraphs[2], paragraphs[0]);
</script>
create nodes
document.createTextNode(image.alt);
image.parentNode.replaceChild(text, image);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<p>The <img src="img/cat.png" alt="Cat"> in the
<img src="img/hat.png" alt="Hat">.</p>
<p><button onclick="replaceImages()">Replace</button></p>
<script>
function replaceImages() {
let images = document.body.getElementsByTagName("img");
for (let i = images.length - 1; i >= 0; i--) {
let image = images[i];
if (image.alt) {
let text = document.createTextNode(image.alt);
image.parentNode.replaceChild(text, image);
}
}
}
</script>
attributes
- HTML allows setting any attribute you want on nodes.
<p data="52">
setAttribute
&getAttribute
original attributes:
href
,class
,style
- custom attributes:
data
,myown
- HTML allows setting any attribute you want on nodes.
II. CSS
CSS specificity
inline_style=1000
id=100
class=10
element=1
e.g.
1
2
3
4
5
6
7
8
9A: h1
B: #content h1
C: <div id="content"><h1 style="color: #ffffff">Heading</h1></div>
The specificity of A is 1 (one element)
The specificity of B is 101 (one ID reference and one element)
The specificity of C is 1000 (inline styling)
Since 1 < 101 < 1000, the third rule (C) has a greater level of specificity, and therefore will be applied.
multiple class name for element:
<p class="leftAlign title"> content </p>
e.g.:
1
2
3
4/* p elements with id 'main' and with classes a and b */
p#main.a.b {
margin-bottom: 20px;
}p > a {…}
applies the given styles to all<a>
tags that are direct children of<p>
tags.p a {…}
applies to all<a>
tags inside<p>
tags, whether they are direct or indirect children.
position & animation
position:
transform
: used for rotation, move, skewtranslate(x, y)
: horizontally and vertically movetranslateX(x)
: horizontally movetranslateY(y)
: vertically movescale(x,y)
: zoom in & out according to x-axis/ y-axisrotate(90deg)
: num°: the angle is specified in the parameterskew(x-angle,y-angle)
: num°: the angle is specified in the parameter
animation:
animation-name: mymove;
,mymove
is created using@keyframes
rule specifies the animation codeanimation-duration
:animation-delay
:animation-iteration-count: 2
:e.g.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21div {
width: 100px;
height: 100px;
background: red;
position: relative;
-webkit-animation-name: mymove; /* Safari 4.0 - 8.0 */
-webkit-animation-duration: 5s; /* Safari 4.0 - 8.0 */
animation-name: mymove;
animation-duration: 5s;
}
/* Safari 4.0 - 8.0 */
@-webkit-keyframes mymove {
from {left: 0px;}
to {left: 200px;}
}
@keyframes mymove {
from {left: 0px;}
to {left: 200px;}
}
III. event handler
event object
- event handler functions are passed an argument: event object
event.target.innerHTML
orevent.target.value
two ways to handle event
onclick
attribute: But can only add one handler, since a node can have only oneonclick
attraddEventListener(event, callback)
: can add many handlersremoveEventListener(event, callback)
: will remove handler on this event1
2
3
4
5
6
7
8
9<button>Act-once button</button>
<script>
let button = document.querySelector("button");
function once() {
console.log("Done.");
button.removeEventListener("click", once);
}
button.addEventListener("click", once);
</script>
event loop
- Never put too much synchronous work inside asynchronous callback! It will make page freezing!
- If have to, use the web worker: is a JS process that runs alongside the main script, on its own timeline
propagation
the event will like ‘ bubble‘, propagation upward to elements who also registered for this event
1
2
3
4
5
6// when inner div clicked, both "fun_2", "fun_1" will be called!!
<div id='outer' onclick="fun_1()">
<div id='inner' onclick="fun_2()" >
<div>
</div>
stopPropagation
can prevent bubbling, prevent handlers further up from receiving the event1
2
3
4
5
6
7
8
9
10
11
12<p>A paragraph with a <button>button</button>.</p>
<script>
let para = document.querySelector("p");
let button = document.querySelector("button");
para.addEventListener("mousedown", () => {
console.log("Handler for paragraph.");
});
button.addEventListener("mousedown", event => {
console.log("Handler for button.");
event.stopPropagation();
});
</script>
Default action
[Important]: JavaScript event handlers are called before the default behavior takes place.
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.
1
2
3
4
5
6
7
8<a href="https://developer.mozilla.org/">MDN</a>
<script>
let link = document.querySelector("a");
link.addEventListener("click", event => {
console.log("Nope.");
event.preventDefault();
});
</script>
event type:
key
mouse
touchscreen
scroll
Whenever an element is scrolled, a
"scroll"
event is fired on ite.g.: draws a progress bar above the document and updates it to fill up as you scroll down
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<style>
#progress {
border-bottom: 2px solid blue;
width: 0;
position: fixed;
top: 0; left: 0;
}
</style>
<div id="progress"></div>
<script>
// Create some content
document.body.appendChild(document.createTextNode(
"supercalifragilisticexpialidocious ".repeat(1000)));
let bar = document.querySelector("#progress");
window.addEventListener("scroll", () => {
let max = document.body.scrollHeight - innerHeight;
bar.style.width = `${(pageYOffset / max) * 100}%`;
});
</script>
scrollHeight:
ENTIRE content & padding (visible or not)
offsetHeight:
VISIBLE content & padding
+ border + scrollbar
clientHeight:
VISIBLE content & padding
https://stackoverflow.com/questions/22675126/what-is-offsetheight-clientheight-scrollheight
outerHeight: height in pixels of the whole browser window, browser’s out-most height
innerHeight: Height in pixels of the browser window viewport including the horizontal scrollbar
https://developer.mozilla.org/en-US/docs/Web/API/Window/innerHeight
focus event
focus
&blur
e.g.: prompt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<p>Name: <input type="text" data-help="Your full name"></p>
<p>Age: <input type="text" data-help="Your age in years"></p>
<p id="help"></p>
<script>
let help = document.querySelector("#help");
let fields = document.querySelectorAll("input");
for (let field of Array.from(fields)) {
field.addEventListener("focus", event => {
let text = event.target.getAttribute("data-help");
help.textContent = text;
});
field.addEventListener("blur", event => {
help.textContent = "";
});
}
</script>
IV. Timers
handler = setTimeout(callback, time)
clearTimeout(handler)
handler = setInterval(callback, time)
clearInterval(handler)
e.g.:
make a bomb: cancel a function you have scheduled
1
2
3
4
5
6
7
8
9
10// 1. make it explods after 500ms
let bombTimer = setTimeout(() => {
console.log("BOOM!");
}, 500);
// 2. since synchronous, can clear timer before it explods
if (Math.random() < 0.5) {
console.log("Defused.");
clearTimeout(bombTimer);
}
make a ticker:
1
2
3
4
5
6
7
8let ticks = 0;
let clock = setInterval(() => {
console.log("tick", ticks++);
if (ticks == 10) {
clearInterval(clock); // can access outter var: clock
console.log("stop.");
}
}, 200);
V. Debounce / Throttle
why Debounce & Throttling?
- Debounce: For events like keydown, scroll, we don’t want to trigger event in the middle of it, but only want to trigger after user pause! (ie. we only care the final result)
- Throttling: For events like mouseover, we don’t want to trigger event every for every single move, but only want to trigger it every
200ms
if such event happens (ie. we only care sample results)
Debounce e.g. : debouce keydown [
setTimeout()
]1
2
3
4
5
6
7
8
9
10
11
12
13
14// 1. clear last timer for each keydown event,
// 2. execute callback function after setting time
// This way, if two keydown events happen less than 500ms,
// the first one will be cleard, won't execute the callback function
<textarea>Type something here...</textarea>
<script>
let textarea = document.querySelector("textarea");
let timeout;
textarea.addEventListener("input", () => {
clearTimeout(timeout);
timeout = setTimeout(() => {console.log("Typed!"); }, 500);
});
</script>
Throttling e.g.: throttle mouseover[
setTimeout()
]1
2
3
4
5
6
7
8
9
10
11
12
13
14// 1. scheduled first sampling
// 2. only when scheduled, we execute function;
// - set scheduled to be false
// - after 1000ms, set scheduled to be true
<script>
let scheduled = true;
window.addEventListener("mousemove", event => {
if (scheduled) {
document.body.textContent = `Mouse at ${event.pageX}, ${event.pageY}`;
scheduled = false;
setTimeout(() => { scheduled = true; }, 1000);
}
});
</script>
Debouncify a function
Given a function and a delay period, we want return a debouncified function:
- whenever called, will only be executed after the specified delay.
- Ignore calls in the middle of the delay, and reschedule the starting time of delay
- must receive variant number of arguments
code implementation
‘closure‘ is used here: the returned function have access to its outer function var:
timeout_handler
, and modify it by the returned functions1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32/* Each time the returned function is called,
* First clear the previous scheduled 'func' call
* Then, reschedule a new one
* <NOTE> we used 'closure' here: the returned function have
* access to its outer function var: timeout_handler, and modify
* it
*/
function debouncify( func, delay ){
let timeout_handler
return function(){ // Closure is used here
clearTimeout(timeout_handler)
timeout_handler = setTimeout(function(arguments){
func(arguments)
}, delay)
}
}
// test
function printTime(){
console.log(new Date().toLocaleTimeString());
}
let printTimeAfterFiveSecs = debouncify(printTime, 5000)
// need to call
console.log(new Date().toLocaleTimeString()) // get the start time
printTimeAfterFiveSecs()
printTimeAfterFiveSecs()
printTimeAfterFiveSecs()
printTimeAfterFiveSecs()
printTimeAfterFiveSecs()
printTimeAfterFiveSecs()
printTimeAfterFiveSecs() // should be 5sec after the start time