索引
Notes
<button onclick="copyText()">Copy Text</button>
<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>
<button>Click me any way you want</button> <script> let button = document.querySelector("button"); button.addEventListener("mousedown", event => { if (event.button == 0) { console.log("Left button"); } else if (event.button == 1) { console.log("Middle button"); } else if (event.button == 2) { console.log("Right button"); } }); </script>
<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."); if(event.button == 2) event.stopPropagation(); }); </script>
<button>A</button> <button>B</button> <button>C</button> <script> document.body.addEventListener("click", event => { if(event.target.nodeName == "BUTTON") { console.log("Clicked", event.target.textContent); } }); </script>
<a href="https://developer.mozilla.org/">MDN</a> <script> let link = document.querySelector("a"); // 事件处理器在默认行为发生之前被调用 link.addEventListener("click", event => { console.log("Nope."); event.preventDefault(); // 例如ctrl+w是无法被prevent的 }); </script>
<p>This page turns violet when you hold the V key.</p> <script> window.addEventListener("keydown", event => { if(event.key == "v") { document.body.style.background = "violet"; console.log("repeat"); // 只要按住就会不断触发,而不是仅触发一次。 } }); window.addEventListener("keyup", event => { if(event.key == "v") { document.body.style.background = ""; } }); </script>
组合按键:
<p>Press Control-Space to continue.</p> <script> window.addEventListener("keydown", event => { if(event.key == " " && event.ctrlKey) { console.log("Continuing!"); } }); </script>
一个可以拖动的进度条:
<p>Drag the bar to change its width:</p> <div style="background: orange; width: 60px; height: 20px"> </div> <script> let lastX; // Tracks the last observed mouse X position let bar = document.querySelector("div"); bar.addEventListener("mousedown", event => { if(event.button == 0) { // 鼠标左键 lastX = event.clientX; window.addEventListener("mousemove", moved); event.preventDefault(); // Prevent selection } }); function moved(event) { if(event.buttons == 0) { // MouseEvent.buttons可指示任意鼠标事件中鼠标的按键情况 // 仅按下左键1 仅按下右键2 仅按下滚轮4 ,同时按下多个则取和 // 这个示例的现象在于,只有在进度条上按下左键才可以开启事件 // 之后快速换鼠标右键(或者n个键)拖动也可以。 window.removeEventListener("mousemove", moved); } else { let dist = event.clientX - lastX; let newWidth = Math.max(10, bar.offsetWidth + dist); // 10是最小宽度 bar.style.width = newWidth + "px"; lastX = event.clientX; } } </script>
触屏和鼠标点击是不同的,但是触屏仍然默认会触发一些鼠标事件。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style> dot { position: absolute; display: block; border: 2px solid red; border-radius: 50px; height: 100px; width: 100px; } </style> </head> <body> <p>Touch this page</p> <script> function update(event) { for(let dot; dot = document.querySelector("dot");) { dot.remove(); } for(let i = 0; i < event.touches.length; i++) { let { pageX, pageY } = event.touches[i]; let dot = document.createElement("dot"); dot.style.left = (pageX - 50) + "px"; dot.style.top = (pageY - 50) + "px"; document.body.appendChild(dot); } } window.addEventListener("touchstart", update); window.addEventListener("touchmove", update); window.addEventListener("touchend", update); </script> </body> </html>
监控滚动条的状况:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style> #progress { border-bottom: 2px solid blue; width: 0; position: fixed; top: 0; left: 0; } </style> </head> <body> <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> </body> </html>
采用preventDefault不会阻止默认事件(滚动)发生,因为滚动是在调用事件处理器之前执行的。
注意Focus事件是不会传播的。
<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>
Load事件同样是不传播的。
beforeunload - Event reference | MDN
事件处理器在事件发生的时候就已经被“安排”了,但是也必须等到别的脚本执行完才有机会执行。在有很多或者很耗时的脚本时,页面就有可能被“冻”住,为了解决这个问题,应该把繁重、长时间的计算放在一个单独的线程里。副脚本不会和主脚本共享作用域,只有可以表达为JSON的数据才可以在两者之间传递。
js/squareworker.js↓
addEventListener("message", event => { postMessage(event.data * event.data); });
index.html↓
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> let squareWorker = new Worker("js/squareworker.js"); squareWorker.addEventListener("message", event => { console.log("The worker responded:", event.data); }); squareWorker.postMessage(10); squareWorker.postMessage(24); </script> </body> </html>
取消setTimeout回调:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> let bombTimer = setTimeout(() => { console.log("BOOM!"); }, 500); if(Math.random() < 0.5) { // 50% chance console.log("Defused."); clearTimeout(bombTimer); } </script> </body> </html>
取消setInterval回调:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> let ticks = 0; let clock = setInterval(() => { console.log("tick", ticks++); if(ticks == 10) { clearInterval(clock); console.log("stop."); } }, 200); </script> </body> </html>
某些类型的事件有可能会被连续触发n次(例如“鼠标移动”和“滚动”事件)。处理此类事件时,必须注意不要做太费时间的操作,否则处理程序会占用很多时间,以至于与文档的交互开始变得缓慢。如果你确实需要在这样的处理程序中做一些非常重要的事情,你可以使用setTimeout来降低触发长时操作的频率。这通常被称为Debouncing(去抖动)。有几种略有不同的实现方式。
例子一,持续输入0.5秒输出一个Typed!:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <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> </body> </html>
例子二,间隔250ms响应一次鼠标移动:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> let scheduled = null; window.addEventListener("mousemove", event => { if(!scheduled) { setTimeout(() => { document.body.textContent = `Mouse at ${scheduled.pageX}, ${scheduled.pageY}`; scheduled = null; }, 250); } scheduled = event; }); </script> </body> </html>
Exercise
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <p>????</p> <script> // Your code here let balloon = document.body.querySelector("p"); const MIN_SIZE = 10; const MAX_SIZE = 130; balloon.style.fontSize = `${MIN_SIZE}px`; const balloonCantroller = (event) => { let size = /^\d+/.exec(balloon.style.fontSize)[0]; if(event.key == "ArrowUp") { if (size > MAX_SIZE) { window.removeEventListener("keydown", balloonCantroller); balloon.replaceChild(document.createTextNode("???? "), balloon.firstChild); } balloon.style.fontSize = `${Math.floor(size * 1.1)}px`; } if(event.key == "ArrowDown") { balloon.style.fontSize = `${Math.floor(Math.max(MIN_SIZE, size * 0.9))}px`; } event.preventDefault(); } window.addEventListener("keydown", balloonCantroller); </script> </body> </html>
————————-- -- --- - ———— -- - - - - - - - -
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style> .trail { /* className for the trail elements */ position: absolute; height: 6px; width: 6px; border-radius: 3px; background: teal; } body { height: 300px; } </style> </head> <body> <script> // Your code here. let dots = []; const NUM_DOTS = 10; for (let i = 0; i != NUM_DOTS; ++i) { let dot = document.createElement("div"); dot.className = "trail"; dots.push(dot); } let currentDot = 0; window.addEventListener("mousemove", event => { let dot = dots[currentDot]; currentDot = (currentDot + 1) % NUM_DOTS; dot.style.left = (event.pageX - 4) + "px"; dot.style.top = (event.pageY - 4) + "px"; document.body.appendChild(dot); }); </script> </body> </html>
————————-- -- --- - ———— -- - - - - - - - -
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <tab-panel> <div data-tabname="one">Tab one</div> <div data-tabname="two">Tab two</div> <div data-tabname="three">Tab three</div> </tab-panel> <script type="text/javascript"> function asTabs(node) { // Your code here. let contents = Array.from(node.children); let buttonBar = document.createElement("div"); let buttons = contents.map(content => { content.style.display = "none"; let resultButton = document.createElement("button"); resultButton.appendChild(document.createTextNode(content.getAttribute("data-tabname"))); resultButton.addEventListener("click", event => { selectTab(resultButton, content); }); buttonBar.appendChild(resultButton); return resultButton; }); let currentButton = buttons[0]; let currentContent = contents[0]; function selectTab(button, content) { currentButton.style.backgroundColor = ""; currentContent.style.display = "none"; button.style.backgroundColor = "pink"; content.style.display = ""; currentButton = button; currentContent = content; } node.insertBefore(buttonBar, contents[0]); selectTab(currentButton, currentContent); } asTabs(document.querySelector("tab-panel")); </script> </body> </html>