Notes

1、onclick

<button onclick="copyText()">Copy Text</button>

 

2、removeEventListener

<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>

 

3、Event objects

<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>

 

4、stopPropagation

        <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>

 

5、event.target

        <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>

 

6、Default actions

        <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>

 

7、Key events

        <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>

 

8、Mouse motion

一个可以拖动的进度条:

        <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>

 

9、Touch events

触屏和鼠标点击是不同的,但是触屏仍然默认会触发一些鼠标事件。

<!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>

 

10、Scroll events

监控滚动条的状况:

<!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不会阻止默认事件(滚动)发生,因为滚动是在调用事件处理器之前执行的。

 

11、Focus events

注意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>

 

12、Load event

Load事件同样是不传播的。

beforeunload - Event reference | MDN

load - Event reference | MDN

 

13、Events and the event loop

事件处理器在事件发生的时候就已经被“安排”了,但是也必须等到别的脚本执行完才有机会执行。在有很多或者很耗时的脚本时,页面就有可能被“冻”住,为了解决这个问题,应该把繁重、长时间的计算放在一个单独的线程里。副脚本不会和主脚本共享作用域,只有可以表达为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>

 

14、Timers

取消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>

 

15、Debouncing

某些类型的事件有可能会被连续触发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

① Balloon

<!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>

————————-- --                 --- - ———— -- - - - - - - - -

② Mouse trail

<!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>

————————-- --                 --- - ———— -- - - - - - - - -

③ Tabs

<!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>