1.同步和异步 #

1.1 同步 #

console.log('1');
console.log('2');
console.log('3');

1.2 异步 #

1.2.1 setTimeout #

console.log('1');
setTimeout(() => {
  console.log('2');
}, 1000);
console.log('3');

1.2.2 Ajax #

<body>
    <script>
        console.log(1);
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
            if (this.readyState === 4 && this.status === 200) {
                console.log('responseText', this.responseText);
            }
        };
        xhr.open("GET", "data.txt", false);
        xhr.send();
        console.log(2);
    </script>
</body>

2.事件绑定 #

2.1 DOM0级事件 #

2.1.1 内联模型 #

<body>
    <button onclick="alert('Hello, World!')">Click me!</button>
</body>

2.1.2 脚本模型 #

<body>
    <button onclick="alert('Hello')">Click me!</button>
    <script>
        const button = document.querySelector("button");
        button.onclick = function () {
            console.log(this,"World");
        };
    </script>
</body>

2.2 DOM2级事件 #

2.3 事件生命周期 #

<body>
    <div id="parent">
        <div id="child">
            click
        </div>
    </div>
    <script>
        const parent = document.querySelector("#parent");
        const child = document.querySelector("#child");
        function captureClick(event) {
            //event.stopPropagation();
            console.log('captureClick', 'target=', event.target.id, 'currentTarget=', event.currentTarget.id);
        }
        function bubbleClick(event) {
            if(event.stopPropagation){
                event.stopPropagation();
            }else{
                event.cancelBubble = true;
            }
            //event.stopPropagation();
            //event.stopImmediatePropagation();
            console.log('bubbleClick', 'target=', event.target.id, 'currentTarget=', event.currentTarget.id);
        }
        function bubbleClick2(event) {
            console.log('bubbleClick2')
        }
        parent.addEventListener("click", captureClick, true);
        child.addEventListener("click", captureClick, true);
        parent.addEventListener("click", bubbleClick, false);
        child.addEventListener("click", bubbleClick, false);
        child.addEventListener("click", bubbleClick2, false);
    </script>
</body>

2.3.1 捕获阶段(Capturing Phase) #

element.addEventListener(eventType, eventHandler, useCapture);

2.3.2 目标阶段(Target Phase) #

2.3.3 冒泡阶段(Bubbling Phase) #

3.事件类型 #

3.1 window vs document #

3.2 鼠标事件 #

事件名 介绍 是否可冒泡
mousedown 当按下鼠标按钮时触发该事件
mouseup 当释放鼠标按钮时触发该事件
click 当鼠标单击元素时触发该事件(mousedownmouseup 事件顺序发生在同一元素上
dblclick 当鼠标在元素上双击时触发该事件
mouseover 当鼠标指针移动到元素上时触发该事件
mousemove 当鼠标在元素上移动时触发该事件
mouseout 当鼠标指针移出元素时触发该事件
mouseenter 当鼠标指针进入元素时触发该事件(不冒泡)
mouseleave 当鼠标指针离开元素时触发该事件(不冒泡)
contextmenu 当在元素上单击鼠标右键(或在 Mac 上按 control + 点击)时触发该事件,通常用于显示自定义上下文菜单

3.2.1 mousedown #

<body>
    <button id="button">按钮</button>
    <script>
        const button = document.getElementById("button");
        button.addEventListener("mousedown", function (event) {
            console.log("mousedown");
        });
    </script>
</body>

3.2.2 mouseup #

<body>
    <button id="button">按钮</button>
    <script>
        const button = document.getElementById("button");
        button.addEventListener("mousedown", function (event) {
            console.log("mousedown");
        });
        button.addEventListener("mouseup", function (event) {
            console.log("mouseup");
        });
    </script>
</body>

3.2.3 click #

<body>
    <button id="button">按钮</button>
    <script>
        const button = document.getElementById("button");
        button.addEventListener("mousedown", function (event) {
            console.log("mousedown");
        });
        button.addEventListener("mouseup", function (event) {
            console.log("mouseup");
        });
        button.addEventListener("click", function (event) {
            console.log("click");
        });
    </script>
</body>

3.2.4 dblclick #

<body>
    <button id="button">按钮</button>
    <script>
        const button = document.getElementById("button");
        button.addEventListener("mousedown", function (event) {
            console.log("mousedown");
        });
        button.addEventListener("mouseup", function (event) {
            console.log("mouseup");
        });
        button.addEventListener("click", function (event) {
            console.log("click");
        });
    </script>
</body>

3.2.5 mouseover #

<head>
    <style>
        #container {
            width: 100px;
            height: 100px;
            background-color: lightgreen;
        }
    </style>
</head>

<body>
    <div id="container"></div>
    <script>
        const container = document.getElementById("container");
        container.addEventListener("mouseover", function (event) {
            console.log("mouseover");
        });
    </script>
</body>

3.2.6 mousemove #

<head>
    <style>
        #container {
            width: 100px;
            height: 100px;
            background-color: lightgreen;
        }
    </style>
</head>

<body>
    <div id="container"></div>
    <script>
        const container = document.getElementById("container");
        container.addEventListener("mouseover", function (event) {
            console.log("mouseover");
        });
        container.addEventListener("mousemove", function (event) {
            console.log("mousemove");
        });
    </script>
</body>

3.2.6 mouseout #

<head>
    <style>
        #container {
            width: 100px;
            height: 100px;
            background-color: lightgreen;
        }
    </style>
</head>

<body>
    <div id="container"></div>
    <script>
        const container = document.getElementById("container");
        container.addEventListener("mouseover", function (event) {
            console.log("mouseover");
        });
        container.addEventListener("mousemove", function (event) {
            console.log("mousemove");
        });
        container.addEventListener("mouseout", function (event) {
            console.log("mouseout");
        });
    </script>
</body>

3.2.7 mouseenter #

<head>
    <style>
        #container {
            width: 100px;
            height: 100px;
            background-color: lightgreen;
        }
    </style>
</head>

<body>
    <div id="container"></div>
    <script>
        const container = document.getElementById("container");
        container.addEventListener("mouseover", function (event) {
            console.log("mouseover");
        });
        container.addEventListener("mousemove", function (event) {
            console.log("mousemove");
        });
        container.addEventListener("mouseout", function (event) {
            console.log("mouseout");
        });
        container.addEventListener("mouseenter", function (event) {
            console.log("mouseenter");
        });
    </script>
</body>

3.2.8 mouseleave #

<head>
    <style>
        #container {
            width: 100px;
            height: 100px;
            background-color: lightgreen;
        }
    </style>
</head>

<body>
    <div id="container"></div>
    <script>
        const container = document.getElementById("container");
        container.addEventListener("mouseover", function (event) {
            console.log("mouseover");
        });
        container.addEventListener("mousemove", function (event) {
            console.log("mousemove");
        });
        container.addEventListener("mouseout", function (event) {
            console.log("mouseout");
        });
        container.addEventListener("mouseenter", function (event) {
            console.log("mouseenter");
        });
        container.addEventListener("mouseleave", function (event) {
            console.log("mouseleave");
        });
    </script>
</body>
mouseover
mouseenter
mousemove
mouseout
mouseleave

3.2.9 比较 #

head>
    <style>
        #parent {
            width: 200px;
            height: 200px;
            background-color: lightgreen;
        }

        #child { 
            width: 100px;
            height: 100px;
            background-color: lightpink;
        }
    </style>
</head>

<body>
    <div id="parent">
        <div id="child"></div>
    </div>
    <script>
        const parent = document.getElementById("parent");
        parent.addEventListener("mouseover", function (event) {
            console.log("parent mouseover");
        });
        parent.addEventListener("mousemove", function (event) {
            //console.log("parent mousemove");
        });
        parent.addEventListener("mouseout", function (event) {
            console.log("parent mouseout");
        });
        parent.addEventListener("mouseenter", function (event) {
            console.log("parent mouseenter");
        });
        parent.addEventListener("mouseleave", function (event) {
            console.log("parent mouseleave");
        });
        const child = document.getElementById("child");
        child.addEventListener("mouseover", function (event) {
            console.log("child mouseover");
        });
        child.addEventListener("mousemove", function (event) {
            //console.log("child mousemove");
        });
        child.addEventListener("mouseout", function (event) {
            console.log("child mouseout");
        });
        child.addEventListener("mouseenter", function (event) {
            console.log("child mouseenter");
        });
        child.addEventListener("mouseleave", function (event) {
            console.log("child mouseleave");
        });
    </script>
</body>
parent mouseover
                parent mouseenter
parent mouseout
child mouseover
parent mouseover
               child mouseenter
child mouseout
parent mouseout
               child mouseleave
parent mouseover
parent mouseout
              parent mouseleave

3.2.10 contextmenu #

<body>
    <div id="customElement">右键点击</div>
    <script>
        const customElement = document.getElementById("customElement");
        customElement.addEventListener("contextmenu", function (event) {
            console.log('contextmenu');
        });
    </script>
</body>

3.3 键盘事件 #

// 绑定到 window 对象
window.addEventListener('keydown', function(event) {
  // 处理键盘事件
});
// 绑定到 document 对象
document.addEventListener('keydown', function(event) {
  // 处理键盘事件
});
// 绑定到具体的 HTML 元素
const inputElement = document.getElementById('my-input');
inputElement.addEventListener('keydown', function(event) {
  // 处理键盘事件
});

3.3.1 keydown #

document.addEventListener('keydown', function(event) {
  if (event.ctrlKey && event.key === 's') {
    event.preventDefault(); // 阻止浏览器默认行为
    console.log('执行自定义保存操作');
  }
});

3.3.2 keypress #

<body>
    <input type="text" id="phone" placeholder="请输入电话号码" />
    <script>
        const phoneInput = document.getElementById('phone');
        phoneInput.addEventListener('keypress', function (event) {
            const key = event.key;
            // 如果按下的不是数字,则阻止输入
            if (!isFinite(key)) {
                event.preventDefault();
                return;
            }
            // 获取当前输入框中的值
            const currentValue = phoneInput.value;
            // 根据电话号码格式,添加分隔符
            if (currentValue.length === 3) {
                phoneInput.value += '-';
            }
        });
    </script>
</body>

3.3.3 keyup #

let startTime;
document.addEventListener('keydown', function(event) {
  if (event.key === 'a') {
    startTime = new Date().getTime();
  }
});
document.addEventListener('keyup', function(event) {
  if (event.key === 'a') {
    let endTime = new Date().getTime();
    let duration = endTime - startTime;
    console.log('按键持续时间:', duration, '毫秒');
  }
});
const inputBox = document.querySelector('#inputBox');
inputBox.addEventListener('keyup', function(event) {
  let searchText = inputBox.value;
  console.log('执行实时搜索,关键词:', searchText);
});

3.4 表单事件 #

事件名 介绍 是否可冒泡
input 当元素的值发生变化时触发该事件
change 当元素的值发生变化时触发该事件
focus 当元素获得焦点时触发该事件
blur 当元素失去焦点时触发该事件
submit 当提交表单时触发该事件
reset 当重置表单时触发该事件
select 当用户选中输入框中的文本时触发该事件

3.4.1 input #

<body>
    <input />
    <script>
        var input = document.querySelector('input');
        input.addEventListener('input', function (event) {
            console.log('input');
        });
    </script>
</body>

3.4.2 change #

<body>
    <input />
    <script>
        var input = document.querySelector('input');
        input.addEventListener('input', function (event) {
            console.log('input');
        });
        input.addEventListener('change', function (event) {
            console.log('change');
        });
    </script>
</body>

3.4.3 focus #

<body>
    <input />
    <script>
        var input = document.querySelector('input');
        input.addEventListener('input', function (event) {
            console.log('input');
        });
        input.addEventListener('change', function (event) {
            console.log('change');
        });
        input.addEventListener('focus', function (event) {
            console.log('focus');
        });
    </script>
</body>

3.4.4 blur #

<body>
    <input />
    <script>
        var input = document.querySelector('input');
        input.addEventListener('input', function (event) {
            console.log('input');
        });
        input.addEventListener('change', function (event) {
            console.log('change');
        });
        input.addEventListener('focus', function (event) {
            console.log('focus');
        });
        input.addEventListener('blur', function (event) {
            console.log('blur');
        });
    </script>
</body>

3.4.5 submit #

<body>
     <form>
        <label for="name">Name:</label><br>
        <input type="text" id="name" name="name"><br>
        <input type="submit" value="Submit">
    </form>
    <script>
        // 获取表单元素
        const form = document.querySelector('form');
        // 添加 submit 事件监听器
        form.addEventListener('submit', function(event) {
        // 阻止表单的默认提交行为
        event.preventDefault();
        // 这里可以添加你自己的表单处理代码
        console.log('Form submitted!');
        });
    </script>
</body>

3.5 系统事件 #

事件名 介绍 是否可冒泡
scroll 当元素或窗口滚动时触发
load 当页面加载完成时触发
resize 当窗口或框架被重新调整大小时触发
DOMContentLoaded 当HTML文档被完全加载和解析时触发
unload 当页面完全卸载时触发
beforeunload 当窗口或标签页关闭前触发
abort 当加载资源过程被用户终止时触发
error 当加载资源发生错误时触发
online 当设备重新连接到互联网时触发
offline 当设备失去互联网连接时触发

3.5.1 scroll #

<body>
    <div id="scrollable-element" style="height:500px;overflow: auto;">
        <div style="height: 1000px; background-color: lightgreen;"></div>
    </div>
    <script>
        window.addEventListener('scroll', function (event) {
            console.log('页面滚动了');
        });
        const scrollableElement = document.querySelector('#scrollable-element');
        scrollableElement.addEventListener('scroll', function (event) {
            console.log('元素滚动了');
        });
    </script>
</body>

3.5.2 resize #

<body>
    <script>
        window.addEventListener('resize', function (event) {
            console.log('窗口大小发生了变化');
        });
    </script>
</body>

3.5.3 load #

<body>
    <img src="https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png" />
    <script>
        window.addEventListener('load', function (event) {
            console.log('页面加载完成');
        });
        const imageElement = document.querySelector('img');
        imageElement?.addEventListener('load', function (event) {
            console.log('图片加载完成');
        });
        const scriptElement = document.createElement('script');
        scriptElement.src = 'script.js';
        scriptElement.addEventListener('load', function (event) {
            console.log('脚本加载完成');
        });
        document.head.appendChild(scriptElement);
    </script>
</body>

3.5.4 DOMContentLoaded #

<body>
    <img src="https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png" />
    <script>
        window.addEventListener('load', function (event) {
            console.log('页面加载完成');
        });
        const imageElement = document.querySelector('img');
        imageElement?.addEventListener('load', function (event) {
            console.log('图片加载完成');
        });
        const scriptElement = document.createElement('script');
        scriptElement.src = 'script.js';
        scriptElement.addEventListener('load', function (event) {
            console.log('脚本加载完成');
        });
        document.head.appendChild(scriptElement);
        document.addEventListener('DOMContentLoaded', function (event) {
            console.log('HTML 文档已加载和解析');
        });
    </script>
</body>

3.6 移动端事件 #

事件名 介绍 是否可冒泡
touchstart 触摸屏幕时触发的事件
touchmove 触摸点在屏幕上移动时触发的事件
touchend 触摸点离开屏幕时触发的事件
touchcancel 触摸事件被取消时触发的事件(如来电)
gesturestart 当两个触摸点开始接触屏幕时触发的事件
gesturechange 当两个触摸点在屏幕上移动时触发的事件
gestureend 当两个触摸点离开屏幕时触发的事件

3.6.1 touchstart #

<body>
    <div id="container" style="width:200px;height:200px;background-color: lightgreen;"></div>
    <script>
        const container = document.getElementById('container');
        container.addEventListener("touchstart", function (event) {
            console.log('touchstart');
        });
    </script>
</body>

3.6.2 touchmove #

<body>
    <div id="container" style="width:200px;height:200px;background-color: lightgreen;"></div>
    <script>
        const container = document.getElementById('container');
        container.addEventListener("touchstart", function (event) {
            console.log('touchstart');
        });
        container.addEventListener("touchmove", function (event) {
            console.log('touchmove');
        });
    </script>
</body>

3.6.3 touchend #

<body>
    <div id="container" style="width:200px;height:200px;background-color: lightgreen;"></div>
    <script>
        const container = document.getElementById('container');
        container.addEventListener("touchstart", function (event) {
            console.log('touchstart');
        });
        container.addEventListener("touchmove", function (event) {
            console.log('touchmove');
        });
        container.addEventListener("touchend", function (event) {
            console.log('touchend');
        });
    </script>
</body>

3.7 取消默认行为 #

3.7.1 自定义菜单 #

<body>
    <div id="customElement">右键点击我查看自定义菜单</div>
    <ul id="contextMenu" style="display:none; position:absolute;">
        <li>选项1</li>
        <li>选项2</li>
        <li>选项3</li>
    </ul>
    <script>
        const customElement = document.getElementById("customElement");
        const contextMenu = document.getElementById("contextMenu");
        customElement.addEventListener("contextmenu", function (event) {
            event.preventDefault();
            contextMenu.style.display = "block";
            contextMenu.style.left = event.pageX + "px";
            contextMenu.style.top = event.pageY + "px";
        });
        document.addEventListener("click", function () {
            contextMenu.style.display = "none";
        });
    </script>
</body>

3.7.2 点击a #

<body>
    <a id="nav" href="http://www.baidu.com">百度</a>
    <script>
        const nav = document.getElementById('nav');
        nav.addEventListener("click", function (event) {
            if(event.preventDefault){
                event.preventDefault();
            }else{
            event.returnValue = false;
            }
            alert('click');
        });
    </script>
</body>

4.事件对象 #

4.1 事件坐标 #

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Event Coordinates</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        body {
            height: 2000px;
        }

        #container {
            width: 200px;
            height: 200px;
            background-color: lightblue;
            display: flex;
            justify-content: center;
            align-items: center;
            position: absolute;
            top: 300px;
        }
    </style>
</head>

<body>
    <div id="container"></div>
    <script>
        //定义一个函数,用于将数字四舍五入到最接近的特定位数(默认为100)
        function roundToPlace(num, roundingPlace = 100) {
            return Math.round(num / roundingPlace) * roundingPlace;
        }
        //找到HTML中ID为'container'的元素
        const container = document.getElementById('container');
        //为该元素添加一个点击事件监听器
        container.addEventListener('click', (event) => {
            //在点击事件触发时,输出点击点的x坐标相对于触发事件元素的偏移量,并四舍五入到最近的100
            console.log('offsetX:', roundToPlace(event.offsetX));
            //输出点击点的x坐标相对于浏览器可视区的位置,并四舍五入到最近的100
            console.log('clientX:', roundToPlace(event.clientX));
            //输出点击点的x坐标相对于完整页面的位置,并四舍五入到最近的100
            console.log('pageX:', roundToPlace(event.pageX));
            //输出点击点的x坐标相对于用户电脑屏幕的位置,并四舍五入到最近的10
            console.log('screenX:', roundToPlace(event.screenX, 10));
            //以下四行与上面类似,但是输出的是y坐标而非x坐标
            console.log('offsetY:', roundToPlace(event.offsetY));
            console.log('clientY:', roundToPlace(event.clientY));
            console.log('pageY:', roundToPlace(event.pageY));
            console.log('screenY:', roundToPlace(event.screenY, 10));
        });
        //设置一个定时器,在1秒后将页面滚动到距离顶部100像素的位置
        setTimeout(() => {
            document.documentElement.scrollTop = 100;
        }, 1000);

    </script>
</body>

4.2 key和keyCode #

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Moving Square</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        #box {
            position: absolute;
            width: 100px;
            height: 100px;
            background-color: lightblue;
        }
    </style>
</head>

<body>
    <div id="box"></div>
    <script>
      //获取ID为'box'的元素
      const box = document.getElementById('box');
      //获取该元素距离页面顶部的距离
      let offsetTop = box.offsetTop;
      //获取该元素距离页面左边的距离
      let offsetLeft = box.offsetLeft;
      //定义移动步长为10px
      const step = 10;
      //定义一个函数,用于根据键盘的方向键移动box元素
      function moveBox(event) {
          switch (event.key) {
              //如果按下的是向上的方向键,则box元素向上移动
              case 'ArrowUp':
                  offsetTop -= step;
                  break;
              //如果按下的是向下的方向键,则box元素向下移动
              case 'ArrowDown':
                  offsetTop += step;
                  break;
              //如果按下的是向左的方向键,则box元素向左移动
              case 'ArrowLeft':
                  offsetLeft -= step;
                  break;
              //如果按下的是向右的方向键,则box元素向右移动
              case 'ArrowRight':
                  offsetLeft += step;
                  break;
          }
          //更新box元素的top样式,使其移动到新的位置
          box.style.top = `${offsetTop}px`;
          //更新box元素的left样式,使其移动到新的位置
          box.style.left = `${offsetLeft}px`;
      }
      //为整个文档添加一个键盘按下的事件监听器,当按下键盘时,调用moveBox函数
      document.addEventListener('keydown', moveBox);

    </script>
</body>
</html>

4.3 事件代理委托 #

<body>
    <ul id="list">
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>

    <script>
      //获取ID为'list'的元素
      const list = document.getElementById("list");
      //为该元素添加一个点击事件监听器
      list.addEventListener("click", function (event) {
          //检查点击的目标元素是否为列表项('LI')
          if (event.target.tagName === "LI") {
              //如果是,弹出一个警告框,显示点击的列表项的内容
              alert("You clicked on: " + event.target.textContent);
          }
      });
    </script>
</body>

4.4 拖拽 #

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple Drag and Drop Example</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
    <style>
        .draggable {
            width: 100px;
            height: 100px;
            background-color: red;
            position: absolute;
            cursor: move;
        }
    </style>
</head>

<body>
    <div class="draggable" id="draggable"></div>
    <script>
        //获取ID为'draggable'的元素
        const draggable = document.getElementById("draggable");
        //定义两个变量用于保存鼠标与拖动元素的偏移量
        let offsetX = 0;
        let offsetY = 0;
        //定义一个标志位,表示当前是否正在拖动
        let dragging = false;
        //为拖动元素添加一个鼠标按下的事件监听器
        draggable.addEventListener("mousedown", (event) => {
            //当鼠标按下时,开始拖动
            dragging = true;
            //计算鼠标点击点与拖动元素左边界和上边界的偏移量
            offsetX = event.clientX - draggable.offsetLeft;
            offsetY = event.clientY - draggable.offsetTop;
            //以下两行代码是另一种获取偏移量的方法,但这里被注释掉了
            //offsetX = event.offsetX;
            //offsetY = event.offsetY;
        });
        //为整个文档添加一个鼠标移动的事件监听器
        document.addEventListener("mousemove", function (event) {
            //如果正在拖动,更新拖动元素的位置
            if (dragging) {
                draggable.style.left = event.clientX - offsetX + "px";
                draggable.style.top = event.clientY - offsetY + "px";
            }
        });
        //为整个文档添加一个鼠标抬起的事件监听器
        document.addEventListener("mouseup", function () {
            //当鼠标抬起时,停止拖动
            dragging = false;
        });
    </script>
</body>

4.5 拖拽边缘检测 #

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple Drag and Drop Example</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
    <style>
        .draggable {
            width: 100px;
            height: 100px;
            background-color: red;
            position: absolute;
            cursor: move;
        }
    </style>
</head>

<body>
    <div class="draggable" id="draggable"></div>
    <script>
      //获取ID为'draggable'的元素
      const draggable = document.getElementById("draggable");
      //定义两个变量用于保存鼠标与拖动元素的偏移量
      let offsetX = 0;
      let offsetY = 0;
      //定义一个标志位,表示当前是否正在拖动
      let dragging = false;
      //为拖动元素添加一个鼠标按下的事件监听器
      draggable.addEventListener("mousedown", (event) => {
          //当鼠标按下时,开始拖动
          dragging = true;
          //计算鼠标点击点与拖动元素左边界和上边界的偏移量
          offsetX = event.clientX - draggable.offsetLeft;
          offsetY = event.clientY - draggable.offsetTop;
          //以下两行代码是另一种获取偏移量的方法,但这里被注释掉了
          //offsetX = event.offsetX;
          //offsetY = event.offsetY;
      });
      //为整个文档添加一个鼠标移动的事件监听器
      document.addEventListener("mousemove", function (event) {
          //如果正在拖动,更新拖动元素的位置
          if (dragging) {
              //计算新的位置
              const newX = event.clientX - offsetX;
              const newY = event.clientY - offsetY;
              //计算窗口的右边界和底边界
              const maxLeft = document.documentElement.clientWidth - draggable.offsetWidth;
              const maxTop = document.documentElement.clientHeight - draggable.offsetHeight;
              //确保新的位置不超过窗口的边界
              const clampedX = Math.max(0, Math.min(newX, maxLeft));
              const clampedY = Math.max(0, Math.min(newY, maxTop));
              //更新元素的位置
              draggable.style.left = clampedX + "px";
              draggable.style.top = clampedY + "px";
          }
      });
      //为整个文档添加一个鼠标抬起的事件监听器
      document.addEventListener("mouseup", function () {
          //当鼠标抬起时,停止拖动
          dragging = false;
      });
    </script>
</body>

4.6 多元素拖拽 #

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        .box {
            position: absolute;
            width: 100px;
            height: 100px;
            background-color: lightblue;
            cursor: move;
        }
    </style>
</head>

<body>
    <div class="box" style="left: 50px; top: 50px;"></div>
    <div class="box" style="left: 50px; top: 200px;"></div>
    <div class="box" style="left: 50px; top: 350px;"></div>
    <script>
      //定义一个变量用于保存当前正在拖动的元素
      let currentDragging = null;
      //获取所有class为'box'的元素
      const boxes = document.querySelectorAll('.box');
      //为每个元素添加一个鼠标按下的事件监听器
      boxes.forEach(box => {
          box.addEventListener('mousedown', (e) => {
              //当鼠标按下时,设置当前正在拖动的元素为该元素,并计算鼠标相对于元素的偏移量
              currentDragging = box;
              currentDragging.dataset.offsetX = e.offsetX;
              currentDragging.dataset.offsetY = e.offsetY;
          });
      });
      //为整个文档添加一个鼠标移动的事件监听器
      document.addEventListener('mousemove', (e) => {
          //如果当前没有正在拖动的元素,则直接返回
          if (!currentDragging) return;
          //如果有正在拖动的元素,则更新元素的位置
          currentDragging.style.left = (e.clientX - currentDragging.dataset.offsetX) + 'px';
          currentDragging.style.top = (e.clientY - currentDragging.dataset.offsetY) + 'px';
      });
      //为整个文档添加一个鼠标抬起的事件监听器
      document.addEventListener('mouseup', () => {
          //当鼠标抬起时,清空当前正在拖动的元素
          currentDragging = null;
      });
    </script>
</body>

</html>

4.7 碰撞检测 #

如果上述所有条件都为真,那么这两个元素在水平和垂直方向上都有重叠,因此我们可以判断它们发生了碰撞。如果任何一个条件为假,那么这两个元素就没有碰撞

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        .box {
            position: absolute;
            width: 100px;
            height: 100px;
            cursor: move;
        }
    </style>
</head>

<body>
    <div class="box" style="left: 50px; top: 50px;background-color: lightcoral;"></div>
    <div class="box" style="left: 50px; top: 200px;background-color: lightblue;"></div>
    <div class="box" style="left: 50px; top: 350px;background-color: lightseagreen;"></div>
    <script>
        // 声明一个变量,用于保存当前正在拖动的元素
        let currentDragging = null;
        // 获取页面上所有 class 为 'box' 的元素
        const boxes = document.querySelectorAll('.box');
        // 为每个 box 元素添加鼠标按下事件监听器
        boxes.forEach(box => {
            box.addEventListener('mousedown', (e) => {
                // 当鼠标按下时,将当前元素设置为正在拖动的元素
                // 并且计算鼠标相对于元素的 x 和 y 坐标的偏移
                currentDragging = box;
                currentDragging.dataset.offsetX = e.offsetX;
                currentDragging.dataset.offsetY = e.offsetY;
            });
        });
        // 为整个文档添加鼠标移动事件监听器
        document.addEventListener('mousemove', (e) => {
            // 如果当前没有元素正在被拖动,直接返回
            if (!currentDragging) return;
            // 计算预期的新位置
            const proposedX = e.clientX - currentDragging.dataset.offsetX;
            const proposedY = e.clientY - currentDragging.dataset.offsetY;
            // 遍历所有的 box 元素
            for (const box of boxes) {
                // 如果是当前正在拖动的元素,跳过
                if (box === currentDragging) continue;
                // 获取元素的位置和尺寸
                const boxRect = box.getBoundingClientRect();
                // 如果预期的新位置和其他元素发生了碰撞,直接返回
                if (
                    proposedX < boxRect.right &&
                    proposedX + currentDragging.offsetWidth > boxRect.left &&
                    proposedY < boxRect.bottom &&
                    proposedY + currentDragging.offsetHeight > boxRect.top
                ) {
                    return;
                }
            }
            // 如果没有碰撞,更新元素的位置
            currentDragging.style.left = proposedX + 'px';
            currentDragging.style.top = proposedY + 'px';
        });

        // 为整个文档添加鼠标抬起事件监听器
        document.addEventListener('mouseup', () => {
            // 当鼠标抬起时,清除当前正在拖动的元素
            currentDragging = null;
        });
    </script>
</body>
</html>

4.8 放大镜 #

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
    <style>
        body {
            display: flex;
            padding: 100px;
        }

        .container {
            position: relative;
            width: 300px;
            height: 300px;
            display: inline-block;
        }

        .container img {
            width: 100%;
            height: 100%;
        }

        .magnifier {
            width: 100px;
            background: rgba(0, 0, 0, 0.2);
            position: absolute;
            left: 0;
            top: 0;
        }

        .preview {
            width: 300px;
            position: relative;
            display: inline-block;
            overflow: hidden;
        }

        .preview img {
            position: absolute;
            left: 0;
            top: 0;
        }
    </style>
    <title>magnifier</title>
</head>

<body>
    <!-- HTML结构部分 -->
    <div class="container">
        <img src="images/small.jpg" />
        <div class="magnifier"></div>
    </div>
    <div class="preview">
        <img src="images/big.jpg" />
    </div>
    <script>
        // 获取相关的DOM元素
        const container = document.querySelector('.container');
        const magnifier = document.querySelector('.magnifier');
        const preview = document.querySelector('.preview');
        const largeImg = document.querySelector('.preview img');
        // 计算容器的宽高比 clientHeight=2,clientWidth=1,containerRatio=2
        const containerRatio = container.clientHeight / container.clientWidth;
        // 设置放大镜的高度,以保持宽高比一致 
        magnifier.style.height = magnifier.clientWidth * containerRatio + 'px';
        // 设置预览框的宽高
        preview.style.height = preview.clientWidth * containerRatio + 'px';
        // 计算预览框放大的比例
        const scale = preview.clientWidth / magnifier.clientWidth;
        // 设置放大图像的宽度和高度
        largeImg.style.width = container.clientWidth * scale + 'px';
        largeImg.style.height = container.clientHeight * scale + 'px';
        // 监听容器的鼠标移动事件
        container.addEventListener('mousemove', (e) => {
            const rect = container.getBoundingClientRect();
            let x = e.clientX - rect.left - magnifier.clientWidth / 2;
            let y = e.clientY - rect.top - magnifier.clientHeight / 2;
            // 限制放大镜的位置在容器范围内
            x = Math.max(0, Math.min(x, container.clientWidth - magnifier.clientWidth));
            y = Math.max(0, Math.min(y, container.clientHeight - magnifier.clientHeight));
            // 设置放大镜的位置和显示
            magnifier.style.left = x + 'px';
            magnifier.style.top = y + 'px';
            magnifier.style.display = 'block';
            // 设置放大图像的位置和显示
            largeImg.style.left = -x * scale + 'px';
            largeImg.style.top = -y * scale + 'px';
            largeImg.style.display = 'block';
        });
        // 监听容器的鼠标离开事件
        container.addEventListener('mouseleave', () => {
            // 隐藏放大镜和放大图像
            magnifier.style.display = 'none';
            largeImg.style.display = 'none';
        });
    </script>
</body>
</html>

4.9 树型菜单 #

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>树形菜单</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
    <style>
        .tree-menu {
            display: none;
        }

        .tree-menu.top-level {
            display: block;
        }

        .tree-menu li {
            position: relative;
            font-size: 16px;
            padding-left: 16px;
            line-height: 32px;
        }

        .tree-menu .toggle-icon {
            position: absolute;
            left: 0;
            top: 10px;
            width: 12px;
            height: 12px;
            font-size: 12px;
            line-height: 12px;
            text-align: center;
            border: 1px solid #AAA;
            cursor: pointer;
        }
    </style>
</head>

<body>
    <ul class="tree-menu top-level" id="treeMenu"></ul>
    <script>
        //获取ID为'treeMenu'的元素
        const treeMenu = document.getElementById("treeMenu");
        //为该元素添加一个点击事件监听器
        treeMenu.onclick = function (e) {
            //获取点击的目标元素和它的下一个兄弟元素
            let target = e.target;
            let submenu = target.nextElementSibling;
            //如果点击的目标元素有'toggle-icon'类,并且存在下一个兄弟元素,则切换目标元素的'open'类,并切换下一个兄弟元素的显示状        态
            if (target.classList.contains("toggle-icon") && submenu) {
                target.classList.toggle("open");
                submenu.style.display = submenu.style.display === "block" ? "none" : "block";
            }
        };
        //定义一个函数,根据数据渲染树形菜单的HTML
        function renderTree(data) {
            let html = "";
            data.forEach(item => {
                let { name, children, open } = item;
                html += `
                    <li>
                        <span>${name}</span>
                        ${children ? `<em class="toggle-icon${open ? ' open' : ''}">${open ? '-' : '+'}</em>` : ``}
                        ${children ?
                        `<ul class="tree-menu" style="display: ${open ? 'block' : 'none'};">
                            ${renderTree(children)}
                        </ul>`: ``
                    }
                    </li>`;
            });
            return html;
        }
        let xhr = new XMLHttpRequest();
        xhr.open("get", "https://static.zhufengpeixun.com/data_1684067863819.json");
        xhr.onreadystatechange = function () {
            //当请求成功完成时,解析响应数据并调用resolve函数
            if (xhr.readyState === 4 && xhr.status === 200) {
                let data = JSON.parse(xhr.response);
                let treeHTML = renderTree(data);
                treeMenu.innerHTML = treeHTML;
            }
        };
        xhr.send();
    </script>
</body>
</html>

5.参考 #

5.1 reset #

5.2 window和document #

5.3 innerWidth和innerHeight #

<body>
    <button onclick="displayViewportSize()">Show Viewport Size</button>
    <script>
        function displayViewportSize() {
            const innerWidth = window.innerWidth;
            const innerHeight = window.innerHeight;
            let outerWidth = window.outerWidth;
            let outerHeight = window.outerHeight;
            console.log('innerWidth',innerWidth);
            console.log('innerHeight',innerHeight);
            console.log('outerWidth',outerWidth);
            console.log('outerHeight',outerHeight);
        }
    </script>
</body>

5.4 DOM元素 #

5.5 Event属性 #

5.6 getBoundingClientRect #

5.7 getComputedStyle #

const element = document.getElementById('myElement');
const styles = getComputedStyle(element);
// 获取元素的背景颜色
const backgroundColor = styles.backgroundColor;
// 获取元素的字体大小
const fontSize = styles.fontSize;
// 获取元素的宽度和高度
const width = styles.width;
const height = styles.height;

5.8 DOM节点的属性 #

属性 作用 关注的节点类型
nextElementSibling 获取当前元素紧邻的下一个兄弟元素 元素节点
previousElementSibling 获取当前元素紧邻的上一个兄弟元素 元素节点
firstElementChild 获取当前元素的第一个子元素 元素节点
lastElementChild 获取当前元素的最后一个子元素 元素节点
nextSibling 获取当前节点紧邻的下一个兄弟节点,包括文本节点和注释节点 所有节点类型
previousSibling 获取当前节点紧邻的上一个兄弟节点,包括文本节点和注释节点 所有节点类型
firstChild 获取当前节点的第一个子节点,包括文本节点和注释节点 所有节点类型
lastChild 获取当前节点的最后一个子节点,包括文本节点和注释节点 所有节点类型

5.9 classList #