JavaScript 事件

一、事件基础概念

  • 事件触发机制:特效通常由事件触发
  • 事件源:任何 HTML 元素(节点)都可作为事件源,一个事件源可添加多个事件
  • 事件分类
    • 键盘事件:keypress(按键)、keyup(抬起)、keydown(按下)
    • 鼠标事件:click(单击)、dblclick(双击)、mouseover(悬浮)、mouseout(离开)等
    • 文档事件:load(加载)
    • 表单事件:focus(获得焦点)、blur(失去焦点)、submit(提交)、change(改变)
    • 其他事件:scroll(滚动)、select(选择)

二、事件触发方式

方法一:元素。事件 = 匿名函数

直接获取元素后,将事件与匿名函数绑定,适合简单的事件处理。

html

预览

<div class="box1" style="width:100px;height:100px;border:1px solid #000;"></div>

<script>
// 获取元素
var oBox = document.querySelector('.box1');

// 绑定点击事件
oBox.onclick = function() {
    // 事件处理逻辑
    console.log('匿名函数方式:盒子被点击了');
    this.style.backgroundColor = 'lightblue';
};
</script>

方法二:元素。事件 = 函数名称

将事件绑定到命名函数,适合代码量较大或需要复用的事件处理逻辑。

html

预览

<div class="box2" style="width:100px;height:100px;border:1px solid #000;margin-top:10px;"></div>

<script>
// 获取元素
var oBox = document.querySelector('.box2');

// 绑定事件到命名函数
oBox.onclick = changeColor;

// 定义事件处理函数
function changeColor() {
    console.log('命名函数方式:盒子被点击了');
    this.style.backgroundColor = 'lightgreen';
}
</script>

方法三:HTML 属性直接绑定

在 HTML 标签中直接通过事件属性绑定,可通过 this 传递当前元素。

html

预览

<div class="box3" style="width:100px;height:100px;border:1px solid #000;margin-top:10px;" 
     onclick="changeBox(this, 'lightcoral')"></div>

<script>
// 定义事件处理函数
function changeBox(element, color) {
    console.log('HTML属性方式:盒子被点击了');
    console.log('当前元素:', element);
    element.style.backgroundColor = color;
}
</script>
  • 可通过this传递当前标签作为参数

三、常用事件详解

1. 鼠标事件

  • 常用鼠标事件

    • onclick:单击事件
    • ondblclick:双击事件
    • onmouseover:鼠标悬浮(支持冒泡)
    • onmouseout:鼠标离开(支持冒泡)
    • onmouseenter:鼠标进入(不支持冒泡)
    • onmouseleave:鼠标离开(不支持冒泡)
    • onmousemove:鼠标移动
  • onmouseover 与 onmouseenter 的区别

    • onmouseover/onmouseout:鼠标经过自身或子元素时都触发(支持冒泡)
    • onmouseenter/onmouseleave:仅鼠标经过自身时触发,经过子元素不触发(不支持冒泡)

2. 键盘事件

  • 常用键盘事件

    • onkeydown:按键按下(持续触发)
    • onkeyup:按键抬起(仅一次触发)
  • 键盘按键识别

    • 通过event.keyCode获取按键编码值来判断按下的是哪个键
    • 应用场景:表单提交、游戏控制等
  • 案例:键盘控制元素移动

javascript

运行

<div class="box" style=" margin-left: 0px;margin-top: 0px;"></div>
<script>
    var oBox = document.querySelector('.box');
    document.onkeyup = function(){
        if(event.keyCode==39){
            oBox.style.marginLeft = parseFloat(oBox.style.marginLeft)+5 + 'px';
            console.log(parseFloat(oBox.style.marginLeft)+5);
        }else if(event.keyCode==37){
            oBox.style.marginLeft = parseFloat(oBox.style.marginLeft)-5 + 'px';
        }else if(event.keyCode==83){
            oBox.style.marginTop = parseFloat(oBox.style.marginTop)+5 + 'px';
        }
    }
</script>

3. 光标事件

  • 常用光标事件

    • onfocus:元素获得焦点
    • onblur:元素失去焦点
  • 应用场景

    • 输入框提示文字显示 / 隐藏
    • 搜索框下拉菜单显示 / 隐藏

javascript

运行

// 获取焦点,让value消失;失去焦点,显示提示信息
<input type="radio" name="" id="inp" value="请输入用户名">
<script>
  var oInp = document.getElementById('inp');
  oInp.onfocus = function(){
    oInp.value = '';
  }
  oInp.onblur = function(){
    oInp.value = '请输入用户名';
  }
</script>

4. 表单事件

  • 常用表单事件

    • onsubmit:表单提交
    • onreset:表单重置
    • onselect:文本被选中
    • onchange:内容改变(失去焦点时触发)
    • oninput:内容改变(立即触发)
  • onchange 与 oninput 区别

    • oninput:值改变时立即触发
    • onchange:值改变且失去焦点时触发,适用于<select>等元素

5. 单选框与复选框事件

  • 通过checked属性判断是否选中(true 为选中,false 为未选中)
  • 应用场景:购物车商品选择与价格计算

javascript

运行

<table border='1' width="500">
    <tr>
        <td>选择</td>
        <td>商品名称</td>
        <td>商品价格</td>
    </tr>
    <tr>
        <td>
            <input type="checkbox" class="sel"/>
        </td>
        <td>上衣</td>
        <td class="price">100</td>
    </tr>
    <tr>
        <td><input type="checkbox" class="sel"/></td>
        <td>裤子</td>
        <td class="price">200</td>
    </tr>
    <tr>
        <td><input type="checkbox" class="sel"/></td>
        <td>包包</td>
        <td class="price">300</td>
    </tr>
    <tr>
        <td>全选:<input type="checkbox"/></td>
        <td colspan="2" id='totlePrice'>总价:0</td>
    </tr>
</table>
<script>
    // 1. 单选 - 给复选框添加一个事件
    // 2. 判断复选框的状态
    // 3. checked=true计算 / 否则不用计算
    // 4. 0-checked-price  1-checked-price  2-checked-price
    var oSel = document.querySelectorAll('.sel');
    var oPrice = document.querySelectorAll('.price');
    var totlePrice = document.querySelector('#totlePrice');
    var sum = 0;
    for(var i=0;i<oSel.length;i++){
        oSel[i].index = i;
        oSel[i].onclick = function(){
            if(this.checked){
                // 计算总价
                sum += parseFloat(oPrice[this.index].innerHTML);
            }else{
                sum -= parseFloat(oPrice[this.index].innerHTML);
            }
            totlePrice.innerHTML = "总价:"+sum;
        }
    }
</script>

6. 加载事件

加载事件主要用于控制页面资源加载完成后的代码执行时机,解决 JS 代码因 DOM 未加载完成而无法获取元素的问题。

核心概念

  • window.onload:页面中所有资源(HTML 结构、图片、样式表等)完全加载完成后触发,确保 DOM 元素已存在,避免获取元素时返回null
  • window.onbeforeunload:页面刷新或关闭前触发,可用于执行清理操作(如重置滚动条位置)。

基础案例:解决 DOM 未加载问题

当 JS 代码写在<head>标签中或 DOM 结构之前时,直接获取元素会报错,使用window.onload可避免此问题。

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>加载事件基础案例</title>
    <script>
        // 若不包裹在onload中,此时div#box尚未加载,会报错“无法读取null的innerHTML属性”
        window.onload = function() {
            // 页面加载完成后再获取元素
            var oBox = document.getElementById('box');
            console.log('盒子内容:', oBox.innerHTML); // 正常输出:hello world
        }
    </script>
</head>
<body>
    <div id="box">hello world</div>
</body>
</html>

扩展案例:页面刷新时重置滚动条

页面刷新后默认保留上次滚动位置,使用onbeforeunload可在刷新前将滚动条重置到顶部。

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>重置滚动条案例</title>
    <style>
        body {
            height: 2000px; /* 制造滚动空间 */
        }
    </style>
    <script>
        // 页面刷新/关闭前触发
        window.onbeforeunload = function() {
            // 滚动到页面左上角(x=0,y=0)
            window.scrollTo(0, 0);
        }
    </script>
</head>
<body>
    <h1>向下滚动后刷新页面,滚动条会重置到顶部</h1>
    <div style="margin-top: 1000px;">滚动到这里后刷新页面</div>
</body>
</html>

7. 滚动事件

滚动事件(onscroll)响应页面或元素的滚动行为,常见于 “返回顶部”“楼层导航”“滚动显示元素” 等交互场景。

核心概念与方法

  • window.onscroll:页面滚动时持续触发(滚动过程中多次执行)。
  • 滚动控制方法
    1. window.scrollTo(x, y):直接滚动到指定坐标(x:水平坐标,y:垂直坐标)。
    2. window.scrollTo(options):支持平滑滚动,参数为对象(top:垂直目标位置,left:水平目标位置,behavior:滚动方式,smooth为平滑,instant为瞬间)。
  • 滚动距离获取
    • 标准浏览器:document.documentElement.scrollTop(垂直滚动距离)、document.documentElement.scrollLeft(水平滚动距离)。
    • 兼容 IE:document.body.scrollTop / document.body.scrollLeft
    • 通用写法:var scrollTop = document.documentElement.scrollTop || document.body.scrollTop

基础案例 1:监听页面滚动

页面滚动时触发事件,记录滚动次数。

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>监听滚动案例</title>
    <style>
        body {
            height: 10000px; /* 足够长的页面,确保可滚动 */
        }
        .scroll-count {
            position: fixed;
            top: 20px;
            right: 20px;
            background: #fff;
            padding: 10px;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <div class="scroll-count">滚动次数:<span id="count">0</span></div>

    <script>
        var count = 0;
        var countEl = document.getElementById('count');

        // 页面滚动时触发
        window.onscroll = function() {
            count++;
            countEl.textContent = count;
        }
    </script>
</body>
</html>

案例 2:楼层导航(平滑切换)

点击导航栏,平滑滚动到对应 “楼层”,并高亮当前导航项(类似电商商品详情页的楼层切换)。

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>楼层导航案例</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        ul, li {
            list-style: none;
        }
        /* 导航栏样式 */
        .header {
            position: fixed;
            left: 0;
            top: 0;
            width: 100%;
            height: 40px;
            background-color: #ffe169;
            line-height: 40px;
            z-index: 100;
        }
        .header ul {
            display: flex;
            justify-content: space-around;
        }
        .header ul li {
            cursor: pointer;
            transition: color 0.3s;
        }
        .header ul li.active {
            color: #ff0000;
            font-weight: bold;
        }
        /* 占位元素(避免内容被导航栏遮挡) */
        .empty {
            height: 40px;
        }
        /* 楼层样式 */
        .floor {
            height: 500px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 30px;
        }
        .product {
            background-color: #b2ccee;
        }
        .ratings {
            background-color: #b2eebb;
        }
        .detail {
            background-color: #e7eebb;
        }
        .recommend {
            background-color: #f1cadc;
        }
    </style>
</head>
<body>
    <!-- 导航栏 -->
    <div class="header">
        <ul>
            <li class="active">商品</li>
            <li>评价</li>
            <li>详情</li>
            <li>推荐</li>
        </ul>
    </div>
    <!-- 占位 -->
    <div class="empty"></div>
    <!-- 楼层 -->
    <div class="product floor">商品楼层</div>
    <div class="ratings floor">评价楼层</div>
    <div class="detail floor">详情楼层</div>
    <div class="recommend floor">推荐楼层</div>

    <script>
        // 获取导航项和楼层元素
        var navList = document.querySelector('.header ul').querySelectorAll('li');
        var floorList = document.querySelectorAll('.floor');

        // 为每个导航项绑定点击事件
        navList.forEach(function(nav, index) {
            nav.onclick = function() {
                // 1. 导航项高亮(排他思想)
                navList.forEach(function(item) {
                    item.classList.remove('active');
                });
                this.classList.add('active');

                // 2. 滚动到对应楼层(减去导航栏高度40px,避免被遮挡)
                window.scrollTo({
                    top: floorList[index].offsetTop - 40,
                    behavior: 'smooth' // 平滑滚动
                });
            }
        });
    </script>
</body>
</html>

案例 3:返回顶部按钮(滚动显示 / 隐藏)

模仿淘宝首页 “返回顶部” 效果:页面滚动超过 200px 时显示按钮,点击后平滑回到顶部。

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>返回顶部案例</title>
    <style>
        body {
            height: 3000px; /* 制造滚动空间 */
        }
        .back-top {
            position: fixed;
            right: 20px;
            bottom: 20px;
            padding: 15px 10px;
            background-color: #ffc0cb;
            color: #333;
            cursor: pointer;
            border-radius: 4px;
            display: none; /* 默认隐藏 */
            transition: display 0.3s;
        }
        .back-top:hover {
            background-color: #ff9aa2;
        }
    </style>
</head>
<body>
    <div class="back-top" id="backTop">返回顶部</div>

    <script>
        var backTopBtn = document.getElementById('backTop');
        var scrollTimer = null; // 定时器,用于平滑滚动

        // 1. 监听页面滚动,控制按钮显示/隐藏
        window.onscroll = function() {
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            // 滚动超过200px显示按钮,否则隐藏
            if (scrollTop >= 200) {
                backTopBtn.style.display = 'block';
            } else {
                backTopBtn.style.display = 'none';
            }
        }

        // 2. 点击按钮返回顶部(平滑滚动)
        backTopBtn.onclick = function() {
            // 清除之前的定时器,避免多次点击导致速度异常
            clearInterval(scrollTimer);
            
            scrollTimer = setInterval(function() {
                var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
                if (scrollTop <= 0) {
                    clearInterval(scrollTimer); // 滚动到顶部,停止定时器
                    return;
                }
                // 每次向上滚动50px(数值越大,滚动越快)
                document.documentElement.scrollTop = scrollTop - 50;
            }, 10); // 每10ms执行一次,控制滚动流畅度
        }
    </script>
</body>
</html>

8. 距离相关属性(offset/client/inner/scroll)

在 JS 中,常用四类属性获取元素或窗口的尺寸、位置、滚动距离,是实现拖拽、滚动、定位等交互的核心。

8.1 offset 系列:获取元素定位与尺寸

offset系列用于获取元素相对于定位父级的位置,以及元素的 “真实尺寸”(包含边框)。

属性 作用 特点
offsetLeft 获取元素到最近定位父级的左侧距离(无定位父级则相对于 body) 计算方式:元素左边框 → 定位父级左边框;只读属性
offsetTop 获取元素到最近定位父级的顶部距离(无定位父级则相对于 body) 计算方式:元素上边框 → 定位父级上边框;只读属性
offsetWidth 获取元素的真实宽度(内容宽 + padding + border) 不包含 margin;只读属性,不受元素隐藏(display:none)影响
offsetHeight 获取元素的真实高度(内容高 + padding + border) 不包含 margin;只读属性,不受元素隐藏(display:none)影响
offsetParent 获取元素的最近定位父级(父级无定位则返回 body) 定位指position: relative/absolute/fixed;只读属性
event.offsetX 鼠标事件中,鼠标相对于事件源元素的水平坐标 基于元素左上角(0,0);与事件绑定,需通过event对象获取
event.offsetY 鼠标事件中,鼠标相对于事件源元素的垂直坐标 基于元素左上角(0,0);与事件绑定,需通过event对象获取
案例:用 offset 获取元素位置

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>offset系列案例</title>
    <style>
        .parent {
            width: 400px;
            height: 400px;
            margin: 50px;
            padding: 20px;
            border: 2px solid #333;
            position: relative; /* 定位父级 */
        }
        .child {
            width: 200px;
            height: 200px;
            margin: 30px;
            padding: 10px;
            border: 1px solid #ff0000;
            background-color: #ffe169;
        }
        .info {
            margin: 0 50px;
            padding: 10px;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <div class="parent">
        <div class="child" id="child"></div>
    </div>
    <div class="info" id="info"></div>

    <script>
        var child = document.getElementById('child');
        var info = document.getElementById('info');

        // 获取offset相关属性
        var offsetLeft = child.offsetLeft;
        var offsetTop = child.offsetTop;
        var offsetWidth = child.offsetWidth;
        var offsetHeight = child.offsetHeight;
        var offsetParent = child.offsetParent.tagName; // 获取定位父级标签名

        // 显示结果
        info.innerHTML = `
            <p>子元素相对于定位父级(${offsetParent})的左侧距离:${offsetLeft}px</p>
            <p>子元素相对于定位父级(${offsetParent})的顶部距离:${offsetTop}px</p>
            <p>子元素真实宽度(内容+padding+border):${offsetWidth}px</p>
            <p>子元素真实高度(内容+padding+border):${offsetHeight}px</p>
        `;
    </script>
</body>
</html>

8.2 client 系列:获取元素可视尺寸与鼠标位置

client系列用于获取元素的 “可视尺寸”(不含边框)和鼠标相对于浏览器可视区的位置。

属性 作用 特点
clientWidth 获取元素可视宽度(内容宽 + padding) 不包含 border、margin;元素隐藏时返回 0;只读属性
clientHeight 获取元素可视高度(内容高 + padding) 不包含 border、margin;元素隐藏时返回 0;只读属性
event.clientX 鼠标事件中,鼠标相对于浏览器可视区的水平坐标 基于浏览器左上角(0,0);不包含滚动出去的区域;需通过event获取
event.clientY 鼠标事件中,鼠标相对于浏览器可视区的垂直坐标 基于浏览器左上角(0,0);不包含滚动出去的区域;需通过event获取
案例:用 client 获取鼠标位置

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>client系列案例</title>
    <style>
        body {
            height: 2000px;
        }
        .box {
            width: 300px;
            height: 300px;
            margin: 50px;
            padding: 20px;
            border: 2px solid #ff0000;
            background-color: #e7eebb;
        }
        .mouse-info {
            margin: 0 50px;
            padding: 10px;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <div class="box" id="box"></div>
    <div class="mouse-info" id="mouseInfo"></div>

    <script>
        var box = document.getElementById('box');
        var mouseInfo = document.getElementById('mouseInfo');

        // 监听鼠标移动事件
        document.onmousemove = function(event) {
            // 获取鼠标相对于浏览器可视区的位置
            var clientX = event.clientX;
            var clientY = event.clientY;

            // 获取盒子的可视尺寸
            var boxClientWidth = box.clientWidth;
            var boxClientHeight = box.clientHeight;

            // 显示结果
            mouseInfo.innerHTML = `
                <p>鼠标相对于浏览器可视区的X坐标:${clientX}px</p>
                <p>鼠标相对于浏览器可视区的Y坐标:${clientY}px</p>
                <p>盒子可视宽度(内容+padding):${boxClientWidth}px</p>
                <p>盒子可视高度(内容+padding):${boxClientHeight}px</p>
            `;
        }
    </script>
</body>
</html>

8.3 inner 系列:获取浏览器窗口尺寸

inner系列是窗口专属属性,仅能通过window调用,用于获取浏览器窗口的可视尺寸(包含滚动条)。

属性 作用 特点
window.innerWidth 获取浏览器窗口的可视宽度(包含垂直滚动条宽度) 只读属性;随窗口大小变化而变化
window.innerHeight 获取浏览器窗口的可视高度(包含水平滚动条高度) 只读属性;随窗口大小变化而变化
案例:对比 inner 与 client 的差异

当页面出现滚动条时,innerWidth包含滚动条宽度,document.documentElement.clientWidth不包含。

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>inner系列案例</title>
    <style>
        body {
            /* 强制显示滚动条 */
            overflow-x: scroll;
            overflow-y: scroll;
            width: 2000px; /* 超出窗口宽度,触发水平滚动条 */
            height: 2000px; /* 超出窗口高度,触发垂直滚动条 */
        }
        .size-info {
            position: fixed;
            top: 20px;
            left: 20px;
            background: #fff;
            padding: 10px;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <div class="size-info" id="sizeInfo"></div>

    <script>
        // 实时更新窗口尺寸信息
        function updateSize() {
            var innerWidth = window.innerWidth;
            var innerHeight = window.innerHeight;
            var clientWidth = document.documentElement.clientWidth;
            var clientHeight = document.documentElement.clientHeight;

            document.getElementById('sizeInfo').innerHTML = `
                <p>window.innerWidth(含滚动条):${innerWidth}px</p>
                <p>window.innerHeight(含滚动条):${innerHeight}px</p>
                <p>document.documentElement.clientWidth(不含滚动条):${clientWidth}px</p>
                <p>document.documentElement.clientHeight(不含滚动条):${clientHeight}px</p>
            `;
        }

        // 初始加载时更新
        updateSize();
        // 窗口大小改变时更新
        window.onresize = updateSize;
    </script>
</body>
</html>

8.4 scroll 系列:获取元素滚动信息

scroll系列用于获取元素的滚动距离、内容总尺寸,是实现滚动加载、滚动定位的核心。

属性 作用 特点
scrollWidth 获取元素内容的总宽度(含不可见滚动部分,内容宽 + padding) 不包含 border、margin;内容不足时等于clientWidth;只读属性
scrollHeight 获取元素内容的总高度(含不可见滚动部分,内容高 + padding) 不包含 border、margin;内容不足时等于clientHeight;只读属性
scrollTop 获取元素向上滚动的距离(卷出可视区的顶部高度) 可读写;标准浏览器用document.documentElement.scrollTop,IE 用document.body.scrollTop
scrollLeft 获取元素向左滚动的距离(卷出可视区的左侧宽度) 可读写;标准浏览器用document.documentElement.scrollLeft,IE 用document.body.scrollLeft
element.scrollIntoView() 滚动元素的父容器,使元素进入可视区 方法;参数{behavior: 'smooth'}可实现平滑滚动
案例 1:获取页面滚动距离

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>scroll系列案例1</title>
    <style>
        body {
            height: 3000px;
        }
        .scroll-info {
            position: fixed;
            top: 20px;
            right: 20px;
            background: #fff;
            padding: 10px;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <div class="scroll-info" id="scrollInfo"></div>

    <script>
        // 监听页面滚动
        window.onscroll = function() {
            // 兼容写法:获取页面垂直滚动距离
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            // 兼容写法:获取页面水平滚动距离
            var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;

            document.getElementById('scrollInfo').innerHTML = `
                <p>页面垂直滚动距离:${scrollTop}px</p>
                <p>页面水平滚动距离:${scrollLeft}px</p>
            `;
        }
    </script>
</body>
</html>
案例 2:用 scrollIntoView 实现平滑滚动到元素

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>scroll系列案例2</title>
    <style>
        body {
            height: 2000px;
        }
        .target {
            margin-top: 1500px;
            width: 300px;
            height: 100px;
            background-color: #ffc0cb;
            display: flex;
            align-items: center;
            justify-content: center;
            margin-left: 50px;
        }
        .btn {
            position: fixed;
            top: 20px;
            left: 20px;
            padding: 8px 16px;
            background-color: #ffe169;
            border: none;
            cursor: pointer;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <button class="btn" id="scrollBtn">滚动到目标元素</button>
    <div class="target" id="target">目标元素</div>

    <script>
        var scrollBtn = document.getElementById('scrollBtn');
        var target = document.getElementById('target');

        // 点击按钮,滚动到目标元素
        scrollBtn.onclick = function() {
            target.scrollIntoView({
                behavior: 'smooth', // 平滑滚动
                block: 'start' // 元素顶部与可视区顶部对齐(可选:center/end)
            });
        }
    </script>
</body>
</html>

8.5 拖拽移动效果(综合案例)

结合mousedown/mousemove/mouseup事件与offset/client系列属性,实现元素拖拽功能。

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>元素拖拽案例</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        #dragBox {
            width: 100px;
            height: 100px;
            background-color: #ffd700;
            position: absolute; /* 拖拽元素必须定位 */
            top: 100px;
            left: 100px;
            cursor: move; /* 鼠标悬浮时显示“移动”光标 */
        }
    </style>
</head>
<body>
    <div id="dragBox"></div>

    <script>
        var dragBox = document.getElementById('dragBox');
        var isDragging = false; // 标记是否正在拖拽
        var startX, startY; // 鼠标按下时的初始坐标
        var startLeft, startTop; // 元素初始位置

        // 1. 鼠标按下:记录初始状态
        dragBox.onmousedown = function(event) {
            isDragging = true;
            // 获取鼠标相对于浏览器可视区的初始坐标
            startX = event.clientX;
            startY = event.clientY;
            // 获取元素相对于定位父级的初始位置
            startLeft = dragBox.offsetLeft;
            startTop = dragBox.offsetTop;
            // 防止拖拽时选中页面文字
            dragBox.style.userSelect = 'none';
        }

        // 2. 鼠标移动:更新元素位置
        document.onmousemove = function(event) {
            if (!isDragging) return; // 未按下鼠标时,不执行拖拽逻辑

            // 计算鼠标移动的距离
            var moveX = event.clientX - startX;
            var moveY = event.clientY - startY;

            // 计算元素最终位置(初始位置 + 移动距离)
            var endLeft = startLeft + moveX;
            var endTop = startTop + moveY;

            // 更新元素位置(避免元素超出窗口,可选)
            endLeft = Math.max(0, Math.min(endLeft, window.innerWidth - dragBox.offsetWidth));
            endTop = Math.max(0, Math.min(endTop, window.innerHeight - dragBox.offsetHeight));

            // 应用新位置
            dragBox.style.left = endLeft + 'px';
            dragBox.style.top = endTop + 'px';
        }

        // 3. 鼠标抬起:结束拖拽
        document.onmouseup = function() {
            if (isDragging) {
                isDragging = false;
                // 恢复文字选中功能
                dragBox.style.userSelect = 'auto';
            }
        }
    </script>
</body>
</html>

8.6 getBoundingClientRect ():获取元素完整位置信息

Element.getBoundingClientRect()方法返回元素相对于浏览器可视区的位置和尺寸,返回值为DOMRect对象,包含top/right/bottom/left/width/height等属性,是判断元素是否在可视区的常用工具。

核心属性说明
属性 作用 计算基准
top 元素上边框到浏览器可视区顶部的距离 可视区顶部(0,0)向下为正
bottom 元素下边框到浏览器可视区顶部的距离 可视区顶部(0,0)向下为正
left 元素左边框到浏览器可视区左侧的距离 可视区左侧(0,0)向右为正
right 元素右边框到浏览器可视区左侧的距离 可视区左侧(0,0)向右为正
width 元素的真实宽度(等同于offsetWidth 内容宽 + padding + border
height 元素的真实高度(等同于offsetHeight 内容高 + padding + border
关键特点
  1. 相对可视区:属性值会随页面滚动而变化(滚动后元素位置相对可视区改变)。
  2. 实时更新:每次调用方法都会重新计算最新位置,确保数据准确。
  3. 兼容性好:支持所有现代浏览器及 IE8+,无需额外兼容处理。
案例 1:获取元素位置信息

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>getBoundingClientRect() 基础案例</title>
    <style>
        body {
            height: 2000px;
            padding-top: 100px;
        }
        .target-box {
            width: 200px;
            height: 150px;
            margin-left: 50px;
            padding: 20px;
            border: 3px solid #ff4400;
            background-color: #f0f8ff;
        }
        .info-panel {
            position: fixed;
            top: 20px;
            right: 20px;
            background: #fff;
            padding: 15px;
            border: 1px solid #ccc;
            width: 300px;
        }
        .info-panel p {
            margin: 5px 0;
            font-size: 14px;
        }
    </style>
</head>
<body>
    <div class="target-box" id="targetBox">目标元素</div>
    <div class="info-panel" id="infoPanel"></div>

    <script>
        var targetBox = document.getElementById('targetBox');
        var infoPanel = document.getElementById('infoPanel');

        // 定义更新位置信息的函数
        function updateRectInfo() {
            // 调用方法获取DOMRect对象
            var rect = targetBox.getBoundingClientRect();
            
            // 渲染位置信息
            infoPanel.innerHTML = `
                <h4>元素位置信息(相对可视区)</h4>
                <p>top(上边框到可视区顶部):${Math.round(rect.top)}px</p>
                <p>bottom(下边框到可视区顶部):${Math.round(rect.bottom)}px</p>
                <p>left(左边框到可视区左侧):${Math.round(rect.left)}px</p>
                <p>right(右边框到可视区左侧):${Math.round(rect.right)}px</p>
                <p>width(元素真实宽度):${Math.round(rect.width)}px</p>
                <p>height(元素真实高度):${Math.round(rect.height)}px</p>
            `;
        }

        // 初始加载时更新
        updateRectInfo();
        // 页面滚动时实时更新(因相对可视区位置会变化)
        window.onscroll = updateRectInfo;
        // 窗口大小改变时更新
        window.onresize = updateRectInfo;
    </script>
</body>
</html>
案例 2:判断元素是否在可视区(滚动加载前置逻辑)

利用getBoundingClientRect()判断元素是否进入可视区,是实现 “滚动加载”“懒加载图片” 的核心逻辑。

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>判断元素是否在可视区</title>
    <style>
        body {
            height: 3000px;
            padding-top: 500px;
        }
        .lazy-box {
            width: 300px;
            height: 200px;
            margin: 0 auto;
            border: 2px dashed #666;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 18px;
        }
        .status {
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: #ff4400;
            color: #fff;
            padding: 8px 16px;
            border-radius: 4px;
            display: none;
        }
    </style>
</head>
<body>
    <div class="lazy-box" id="lazyBox">待检测元素</div>
    <div class="status" id="status">元素进入可视区!</div>

    <script>
        var lazyBox = document.getElementById('lazyBox');
        var status = document.getElementById('status');

        // 判断元素是否在可视区的函数
        function isInViewport(element) {
            var rect = element.getBoundingClientRect();
            var viewportHeight = window.innerHeight; // 可视区高度
            var viewportWidth = window.innerWidth; // 可视区宽度

            // 核心逻辑:元素顶部 <= 可视区底部,且元素底部 >= 可视区顶部(垂直方向可见)
            // 同时 元素左侧 <= 可视区右侧,且元素右侧 >= 可视区左侧(水平方向可见)
            return (
                rect.top <= viewportHeight &&
                rect.bottom >= 0 &&
                rect.left <= viewportWidth &&
                rect.right >= 0
            );
        }

        // 检测并更新状态
        function checkVisibility() {
            if (isInViewport(lazyBox)) {
                status.style.display = 'block';
                lazyBox.style.backgroundColor = '#90ee90';
                lazyBox.textContent = '元素已在可视区';
            } else {
                status.style.display = 'none';
                lazyBox.style.backgroundColor = 'transparent';
                lazyBox.textContent = '待检测元素(滚动到可视区试试)';
            }
        }

        // 初始检测
        checkVisibility();
        // 滚动时实时检测
        window.onscroll = checkVisibility;
        // 窗口大小改变时检测
        window.onresize = checkVisibility;
    </script>
</body>
</html>

9. 距离相关属性对比总结

为了更清晰区分offset/client/inner/scroll四类属性的差异,整理对比表格如下:

对比维度 offset 系列 client 系列 inner 系列 scroll 系列
作用对象 任意 DOM 元素 任意 DOM 元素 仅 window(浏览器窗口) 任意 DOM 元素(含 window)
尺寸计算范围 内容 + padding + border 内容 + padding 窗口可视区(含滚动条) 内容总尺寸(含不可见部分)
位置参考基准 最近定位父级(无则 body) 浏览器可视区(鼠标位置) 无(仅尺寸,无位置) 元素自身(滚动距离)
是否受滚动影响 不受(位置固定) 鼠标位置不受,元素尺寸受 不受(窗口尺寸固定) 受(滚动距离动态变化)
是否可读写 只读 只读 只读 scrollTop/scrollLeft 可写
典型用途 元素定位、拖拽计算 鼠标位置、元素可视尺寸 窗口尺寸适配 滚动距离、滚动加载

10. JS 中两种添加事件的方式

在 JavaScript 中,为元素绑定事件主要有两种方式:on+事件名 和事件监听(addEventListener)。两种方式各有特点,适用于不同场景。

10.1 方式一:on+事件名(DOM0 级事件绑定)

这是最基础的事件绑定方式,直接通过元素的事件属性绑定函数。

基本用法

javascript

// 获取元素
var oneDiv = document.getElementById('oneDiv');

// 添加事件关联(同一事件多次绑定,后面的会覆盖前面的)
oneDiv.onclick = function() {
  console.log("第一次点击"); // 不会执行,被后面的覆盖
};
oneDiv.onclick = function() {
  console.log("第二次点击"); // 仅执行此函数
};

// 取消事件关联(三种方式)
oneDiv.onclick = null;
// 或 oneDiv.onclick = false;
// 或 oneDiv.onclick = function() { return false; };
特点
  • 简单直观,兼容性好(支持所有浏览器)
  • 同一元素的同一事件只能绑定一个处理函数,多次绑定会覆盖
  • 移除事件只需将事件属性设为 null 或 false
  • 事件处理函数中的 this 指向绑定事件的元素

10.2 方式二:事件监听(addEventListener,DOM2 级事件绑定)【推荐】

这是标准的事件绑定方式,支持为同一元素的同一事件绑定多个处理函数,更灵活。

基本语法

javascript

element.addEventListener(event, function, useCapture);

  • event:必需,事件名(不含 on 前缀,如 click 而非 onclick
  • function:必需,事件触发时执行的函数
  • useCapture:可选,布尔值(false 为事件冒泡,true 为事件捕获,默认 false
基本用法

javascript

// 定义处理函数
function fun1() {
  console.log("函数1执行");
}
function fun2() {
  console.log("函数2执行");
}

// 添加事件关联(可绑定多个函数)
oneDiv.addEventListener("click", fun1);
oneDiv.addEventListener("click", fun2);
oneDiv.addEventListener("click", function() {
  console.log("匿名函数执行");
});
// 点击元素时,三个函数按绑定顺序依次执行

// 取消事件关联(只能移除具名函数,匿名函数无法移除)
oneDiv.removeEventListener("click", fun1); // 成功移除fun1
特点
  • 支持为同一元素的同一事件绑定多个处理函数,按绑定顺序执行
  • 可通过 useCapture 参数控制事件流(冒泡 / 捕获)
  • 适用于所有 DOM 元素(不仅限于 HTML 元素)
  • 移除事件需使用 removeEventListener,且必须传入与绑定相同的函数引用
  • 事件处理函数中的 this 指向绑定事件的元素

10.3 兼容性处理(IE 低版本)

IE8 及以下版本不支持 addEventListener 和 removeEventListener,需使用专有方法 attachEvent 和 detachEvent

javascript

// 标准浏览器
oneDiv.addEventListener("click", fun1);
oneDiv.removeEventListener("click", fun1);

// IE 低版本浏览器(需加 on 前缀)
oneDiv.attachEvent("onclick", fun1);
oneDiv.detachEvent("onclick", fun1);

10.4 两种方式对比

对比维度 on+事件名(DOM0 级) addEventListener(DOM2 级)
多事件绑定 不支持,后绑定的会覆盖前面的 支持,可绑定多个函数,按顺序执行
事件流控制 不支持(仅冒泡) 支持(通过 useCapture 控制冒泡 / 捕获)
适用对象 主要用于 HTML 元素 适用于所有 DOM 元素
事件名格式 需加 on 前缀(如 onclick 不加 on 前缀(如 click
移除事件 直接赋值 nullelement.onclick = null 需用 removeEventListener,且需函数引用
兼容性 所有浏览器支持 IE8 及以下不支持,需用 attachEvent
推荐场景 简单交互,同一事件只需一个处理函数 复杂交互,同一事件需多个处理函数

10.5 事件绑定的封装(兼容所有浏览器)

为简化跨浏览器事件绑定,可封装通用函数:

javascript

// 添加事件绑定
function addEvent(obj, eventType, handler) {
  if (obj.addEventListener) {
    // 标准浏览器
    obj.addEventListener(eventType, handler);
  } else if (obj.attachEvent) {
    // IE 低版本
    obj.attachEvent("on" + eventType, handler);
  } else {
    // 最原始方式(兼容极旧浏览器)
    obj["on" + eventType] = handler;
  }
}

// 移除事件绑定
function removeEvent(obj, eventType, handler) {
  if (obj.removeEventListener) {
    // 标准浏览器
    obj.removeEventListener(eventType, handler);
  } else if (obj.detachEvent) {
    // IE 低版本
    obj.detachEvent("on" + eventType, handler);
  } else {
    // 最原始方式
    obj["on" + eventType] = null;
  }
}

// 使用示例
var box = document.getElementById('box');
function handleClick() {
  console.log('点击事件触发');
}

// 添加事件
addEvent(box, 'click', handleClick);
// 移除事件
removeEvent(box, 'click', handleClick);

11. DOM CSS 动态样式

JavaScript 可以动态操作元素的 CSS 样式,包括获取和设置样式。常用方式有 obj.stylegetComputedStyle 和 currentStyle

11.1 obj.style(操作行内样式)

直接通过元素的 style 属性操作样式,既可获取也可设置,但仅作用于元素的行内样式(style 属性中定义的样式)。

基本用法

html

预览

<div id="box" style="width: 100px; height: 100px; background-color: red;"></div>

<script>
var box = document.getElementById('box');

// 获取行内样式(仅能获取行内定义的样式)
console.log(box.style.width); // 输出 "100px"
console.log(box.style.backgroundColor); // 输出 "red"

// 设置样式(会添加到行内样式中)
box.style.width = "200px"; // 宽度变为 200px
box.style.height = "200px"; // 高度变为 200px
box.style.backgroundColor = "blue"; // 背景色变为蓝色
box.style.border = "2px solid black"; // 添加边框
box.style.fontSize = "16px"; // 字体大小变为 16px(注意驼峰命名)
</script>
注意事项
  • CSS 属性名在 JS 中需使用驼峰命名法(如 background-color → backgroundColor
  • 只能操作行内样式,无法获取 <style> 标签或外部 CSS 文件中定义的样式
  • 设置样式时必须包含单位(如 width: "100" 无效,需写 width: "100px"

11.2 getComputedStyle(获取计算后样式,非 IE 浏览器)

用于获取元素的最终计算样式(包括行内样式、内部样式、外部样式),仅能获取不能设置,支持除 IE8 及以下外的所有浏览器。

基本语法

javascript

window.getComputedStyle(元素)[样式属性]
用法示例

html

预览

<style>
#box {
  width: 150px;
  height: 150px;
  color: green;
}
</style>
<div id="box" style="background-color: yellow;"></div>

<script>
var box = document.getElementById('box');
var computedStyle = window.getComputedStyle(box);

// 获取计算后样式(包括 CSS 中定义的样式)
console.log(computedStyle.width); // 输出 "150px"(来自 CSS)
console.log(computedStyle.backgroundColor); // 输出 "rgb(255, 255, 0)"(来自行内)
console.log(computedStyle.color); // 输出 "rgb(0, 128, 0)"(来自 CSS)
</script>

11.3 currentStyle(获取计算后样式,IE 浏览器)

IE 浏览器(IE8 及以下)专用,功能与 getComputedStyle 类似,仅能获取不能设置

基本语法

javascript

元素.currentStyle[样式属性]
用法示例

javascript

// 仅 IE8 及以下生效
var box = document.getElementById('box');
console.log(box.currentStyle.width); // 获取计算后的宽度
console.log(box.currentStyle.color); // 获取计算后的颜色

11.4 获取 CSS 样式的兼容写法

为兼容所有浏览器,可封装获取样式的通用函数:

javascript

/**
 * 获取元素的计算后样式
 * @param {Object} obj - 目标元素
 * @param {String} attr - 样式属性名(如 "width"、"color")
 * @returns {String} 样式属性值
 */
function getCss(obj, attr) {
  if (obj.currentStyle) {
    // IE 浏览器
    return obj.currentStyle[attr];
  } else {
    // 标准浏览器
    return window.getComputedStyle(obj)[attr];
  }
}

// 使用示例
var box = document.getElementById('box');
console.log(getCss(box, 'width')); // 获取宽度(兼容所有浏览器)
console.log(getCss(box, 'backgroundColor')); // 获取背景色

11.5 操作类名(className

通过修改元素的 className 属性,可批量切换样式(推荐用于复杂样式变更)。

html

预览

<style>
.default {
  width: 100px;
  height: 100px;
  background-color: gray;
}
.active {
  width: 200px;
  height: 200px;
  background-color: orange;
  border: 2px solid red;
}
</style>
<div id="box" class="default"></div>

<script>
var box = document.getElementById('box');

// 切换类名(批量变更样式)
box.onclick = function() {
  if (this.className === 'default') {
    this.className = 'active'; // 应用 active 样式
  } else {
    this.className = 'default'; // 恢复 default 样式
  }
};
</script>

12. event 对象使用技巧

event 对象包含事件相关的详细信息(如事件类型、触发元素、鼠标位置等),是处理事件的核心工具。

12.1 常用属性

属性 作用
e.target 获取触发事件的元素(事件源)
e.currentTarget 获取绑定事件的元素(与 this 等效)
e.type 获取事件类型(如 clickmouseover
e.preventDefault() 阻止事件的默认行为(如链接跳转、表单提交)
e.stopPropagation() 阻止事件冒泡
e.dataset 获取元素上通过 data-* 属性定义的自定义数据
  1. 事件绑定方式

    • on+事件名 适合简单场景,兼容性好但不支持多事件绑定
    • addEventListener 功能强大,支持多事件绑定和事件流控制,推荐使用
  2. 样式操作

    • obj.style 用于操作行内样式(可读写)
    • getComputedStyle/currentStyle 用于获取计算后样式(只读)
    • className 适合批量切换样式
  3. event 对象:提供事件相关信息,如 target(事件源)、dataset(自定义数据)等,是事件处理的关键。

12.2 实用案例

案例 1:通过 e.target 简化事件处理

html

预览

<ul id="list">
  <li>项目 1</li>
  <li>项目 2</li>
  <li>项目 3</li>
</ul>

<script>
// 给父元素绑定事件,通过 e.target 判断子元素(事件委托)
document.getElementById('list').addEventListener('click', function(e) {
  // e.target 是触发事件的 li 元素
  if (e.target.tagName === 'LI') {
    console.log('点击了:' + e.target.textContent);
    e.target.style.color = 'red'; // 仅改变被点击的 li 样式
  }
});
</script>
案例 2:通过 e.dataset 获取自定义数据

html

预览

<button class="btn" data-id="1001" data-name="商品A">点击查看</button>

<script>
document.querySelector('.btn').addEventListener('click', function(e) {
  // 获取 data-* 属性数据
  console.log('商品ID:', e.target.dataset.id); // 输出 "1001"
  console.log('商品名称:', e.target.dataset.name); // 输出 "商品A"
});
</script>
案例 3:阻止默认行为

html

预览

<a href="https://www.example.com" id="link">示例链接</a>

<script>
document.getElementById('link').addEventListener('click', function(e) {
  e.preventDefault(); // 阻止链接跳转
  console.log('链接被点击,但未跳转');
});
</script>

13. JS 效果案例

13.1 苏宁易购手机端:密码显示 / 隐藏(小眼睛效果)

html

预览

<style>
.input-group {
  position: relative;
  width: 300px;
  margin: 20px;
}
.password-input {
  width: 100%;
  padding: 10px 40px 10px 10px;
  box-sizing: border-box;
}
.eye-icon {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
}
</style>

<div class="input-group">
  <input type="password" class="password-input" id="password" placeholder="请输入密码">
  <span class="eye-icon" id="eye">👁️</span>
</div>

<script>
var passwordInput = document.getElementById('password');
var eyeIcon = document.getElementById('eye');
var isVisible = false;

// 点击小眼睛切换密码显示状态
eyeIcon.addEventListener('click', function() {
  isVisible = !isVisible;
  // 切换 input 类型(password/text)
  passwordInput.type = isVisible ? 'text' : 'password';
  // 切换图标
  eyeIcon.textContent = isVisible ? '👁️‍🗨️' : '👁️';
});
</script>

13.2 京东官网:Tab 切换效果(排他思想)

html

预览

<style>
.tab-container {
  width: 500px;
  margin: 20px;
}
.tab-nav {
  display: flex;
  border-bottom: 1px solid #ccc;
}
.tab-nav li {
  padding: 10px 20px;
  cursor: pointer;
  list-style: none;
}
.tab-nav li.active {
  color: #e4393c;
  border-bottom: 2px solid #e4393c;
}
.tab-content {
  padding: 20px;
}
.tab-panel {
  display: none;
}
.tab-panel.active {
  display: block;
}
</style>

<div class="tab-container">
  <ul class="tab-nav" id="tabNav">
    <li class="active">商品介绍</li>
    <li>规格参数</li>
    <li>售后保障</li>
  </ul>
  <div class="tab-content">
    <div class="tab-panel active">商品介绍内容...</div>
    <div class="tab-panel">规格参数内容...</div>
    <div class="tab-panel">售后保障内容...</div>
  </div>
</div>

<script>
var navItems = document.querySelectorAll('#tabNav li');
var panels = document.querySelectorAll('.tab-panel');

// 为每个导航项绑定点击事件
navItems.forEach(function(item, index) {
  item.addEventListener('click', function() {
    // 排他思想:先移除所有项的 active 类
    navItems.forEach(nav => nav.classList.remove('active'));
    panels.forEach(panel => panel.classList.remove('active'));
    
    // 为当前项添加 active 类
    this.classList.add('active');
    panels[index].classList.add('active');
  });
});
</script>

13.3 小米官网:搜索框下拉列表

html

预览

<style>
.search-container {
  position: relative;
  width: 300px;
  margin: 20px;
}
.search-input {
  width: 100%;
  padding: 8px 10px;
  box-sizing: border-box;
}
.search-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  border: 1px solid #ccc;
  border-top: none;
  background: #fff;
  display: none;
}
.search-dropdown.active {
  display: block;
}
.search-dropdown li {
  padding: 8px 10px;
  list-style: none;
  cursor: pointer;
}
.search-dropdown li:hover {
  background-color: #f5f5f5;
}
</style>

<div class="search-container">
  <input type="text" class="search-input" id="searchInput" placeholder="搜索商品...">
  <ul class="search-dropdown" id="searchDropdown">
    <li>小米手机</li>
    <li>红米手机</li>
    <li>笔记本电脑</li>
    <li>智能手表</li>
  </ul>
</div>

<script>
var searchInput = document.getElementById('searchInput');
var searchDropdown = document.getElementById('searchDropdown');

// 输入框获取焦点时显示下拉列表
searchInput.addEventListener('focus', function() {
  searchDropdown.classList.add('active');
});

// 输入框失去焦点时隐藏下拉列表(延迟执行,确保点击列表项有效)
searchInput.addEventListener('blur', function() {
  setTimeout(function() {
    searchDropdown.classList.remove('active');
  }, 200);
});

// 点击列表项填充输入框
document.querySelectorAll('#searchDropdown li').forEach(function(item) {
  item.addEventListener('click', function() {
    searchInput.value = this.textContent;
    searchDropdown.classList.remove('active');
  });
});
</script>
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐