Skip to content

接下来介绍的都是 BOM,也就是浏览器对象模型,这里主要和浏览器窗口相关,和页面关系就不太大了。

BOM 在不同的浏览器中有很严重的兼容性问题,所以主要介绍兼容性问题不大的方法,定时器是 BOM 中最有用的东西,所以也是最应该掌握的。

定时器实际上就是两个函数

  • window.setTimeout(fn,time):time (毫秒) 时间后执行 fn,返回一个定时器编号
  • window.setInterval(fn,time):每隔 tine (毫秒) 时间执行一次 fn,返回一个定时器编号

这两个函数的参数都是两个,第一个是回调函数,第二个是事件。区别就在于 setTimeout 只执行一次,而 setInterval 会不断执行。

例如我要在 10 秒钟后输出一个 123

js
window.setTimeout(function(){
	console.log(123);
},10000);

例如我要每隔 10 秒输出一个 123

js
window.setInterval(function(){
	console.log(123);
},10000);

那接下来介绍一下前面提到的定时器编号是什么东西,我们比如我使用 setTimeout 要 100 秒后打印出一个东西,但是我希望用户点击了这个按钮之后就不打印了,这个时候就需要把这个定时器取消掉,取消定时器就需要用到这个定时器编号,我们使用 clearTimeout(),传入定时器编号,就可以取消掉这个 setTimeout 设置的定时器,同样,使用 clearInterval(),传入定时器编号,可以取消掉 setInterval 设置的定时器。

定时器是异步的, 不会阻塞代码

实际上, setInterval 是可以通过 setTimeout 来实现的

js
var timer;
function mySetInterval(fn,duration){
	timer = setTimeout(function(){
		//some code
		mySetInterval(fn,duration);
	},duration);
}
function myClearInterval(timer){
	clearTimeout(timer);
}

以上就用 setTimeout 模拟实现了 setInterval。

注意,定时器是非常有用的东西,比如 JS 动画就得用定时器来做,我可以让一个 DIV 每隔 10 毫秒移动 1 像素,这样连贯起来实际上就是 JS 的动画了。

这两个函数非常简单,但应用场景非常广泛,所以练习才是重点

以下是一些练习

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        /* 重置样式,确保元素没有额外的外边距和内边距 */
        * {
            margin: 0;
            padding: 0;
            list-style: none;
        }

        .container {
            font-size: 30px;
            margin-top: 50px;
            text-align: center;
        }
    </style>
</head>

<body>
    <div class="container">
    </div>
    <script>
        // 获取页面中class为container的元素
        var container = document.querySelector(".container");
        // 设置一个定时器,每秒钟执行一次
        setInterval(function () {
            // 创建一个Date对象,获取当前时间
            var date = new Date();
            // 将当前时间格式化后,设置为container元素的innerHTML
            container.innerHTML = `${date.getFullYear()}年 ${(date.getMonth() + 1).toString().padStart(2, "0")}月 ${date.getDate().toString().padStart(2, "0")}日 ${date.getHours().toString().padStart(2, "0")}时 ${date.getMinutes().toString().padStart(2, "0")}分 ${date.getSeconds().toString().padStart(2, "0")}秒`;
        }, 1000);
    </script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        body {
            text-align: center;
        }

        p {
            font-size: 2em;
        }
    </style>
</head>

<body>
    <p class="countDown">
        倒计时:<span>10</span>
    </p>
    <p class="clickCount">
        点击数:<span>0</span>
    </p>
    <p class="buttons">
        <button>点击</button>
        <button>重置</button>
    </p>
    <script>
        // 查询并获取页面中计数器和点击次数显示的元素
        var countDown = document.querySelector('.countDown span');
        var clickCount = document.querySelector('.clickCount span');
        // 查询并获取所有按钮元素
        var buttons = document.querySelectorAll('.buttons button');
        // 获取开始按钮
        var clickBtn = buttons[0];
        // 获取重置按钮
        var resetBtn = buttons[1];
        // 标记是否开始计时
        var isStart = false;
        // 记录点击次数
        var clickNum = 0;
        // 计时器初始值
        var countDownNum = 10;
        // 定时器变量
        var timer;
        /**
         * 重置游戏状态
         * 清除定时器,重置标记,点击次数和计时器数值,
         * 更新页面显示
         */
        function reset() {
            if (timer) {
                clearInterval(timer);
                timer = null;
            }
            isStart = false;
            clickNum = 0;
            countDownNum = 10;
            countDown.innerHTML = countDownNum;
            clickCount.innerHTML = clickNum;
        }
        // 绑定开始按钮点击事件
        clickBtn.addEventListener("click", function () {
            if (!isStart) {
                isStart = true;
                // 设置定时器,每秒减少计时器数值,直到归零
                timer = setInterval(function () {
                    countDown.innerHTML = --countDownNum;
                    if (countDownNum == 0) {
                        clearInterval(timer);
                        timer = null;
                        alert("您本次点击了" + clickNum + "次");
                        reset();
                    }
                }, 1000);
            } else {
                // 如果已经启动,每次点击增加点击次数
                clickCount.innerHTML = ++clickNum;
            }
        });
        // 绑定重置按钮点击事件,调用reset函数
        resetBtn.addEventListener("click", reset);
    </script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .box {
            width: 100px;
            height: 100px;
            position: absolute;
            border-radius: 50%;
        }
    </style>
</head>

<body>
    <div class="box"></div>
    <script>
        // 初始化配置对象,包括大小、颜色和起始位置
        var config = {
            size: 100, // 定义盒模型的大小
            color: "red", // 定义盒模型的颜色
            initPosition: { // 定义盒模型的初始位置
                x: 0,
                y: 0,
            }
        }
        var incX = 10; // 定义盒模型在x轴上每次移动的距离
        var incY = 10; // 定义盒模型在y轴上每次移动的距离
        var box = document.querySelector(".box"); // 获取类名为box的元素
        var currentLeft = config.initPosition.x; // 当前盒模型左边的位置
        var currentTop = config.initPosition.y; // 当前盒模型上边的位置

        /**
         * 初始化函数,用于将盒模型放置在初始位置,并检查是否超出屏幕范围
         * 无参数
         * 无返回值
         */
        function init() {
            // 检查盒模型是否超出屏幕范围
            if (config.initPosition.x + config.size > document.documentElement.clientWidth || config.initPosition.y + config.size > document.documentElement.clientHeight) {
                alert("超出屏幕范围");
                box.style.display = "none"; // 如果超出屏幕范围,则将盒模型隐藏
                return false;
            }
            // 设置盒模型的初始位置和背景颜色
            box.style.left = config.initPosition.x + "px";
            box.style.top = config.initPosition.y + "px";
            box.style.backgroundColor = config.color;
        }
        init(); // 调用初始化函数

        // 设置定时器,每隔30毫秒移动一次盒模型
        setInterval(function () {
            currentLeft += incX; // 在x轴上移动
            currentTop += incY; // 在y轴上移动
            // 当盒模型移动到边界时,改变移动方向
            if (currentLeft <= 0) {
                currentLeft = 0;
                incX = -incX;
            }
            if (currentTop <= 0) {
                currentTop = 0;
                incY = -incY;
            }
            if (currentLeft + config.size >= document.documentElement.clientWidth) {
                currentLeft = document.documentElement.clientWidth - config.size;
                incX = -incX;
            }
            if (currentTop + config.size >= document.documentElement.clientHeight) {
                currentTop = document.documentElement.clientHeight - config.size;
                incY = -incY;
            }
            // 更新盒模型的位置
            box.style.left = currentLeft + "px";
            box.style.top = currentTop + "px";
        }, 30);
    </script>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en"> 

<head>
    <meta charset="UTF-8"> 
    <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
    <meta http-equiv="X-UA-Compatible" content="ie=edge"> 
    <title>Document</title> 

    
    <style>
        * {
            /* 清除所有元素的默认内外边距 */
            margin: 0;
            padding: 0;
        }

        .container {
            /* 定义主容器类样式 */
            position: relative;
            /* 设置相对定位,以便内部绝对定位元素参照此容器 */
            overflow: hidden;
            /* 隐藏溢出内容,便于实现轮播效果 */
        }

        .container a {
            /* 容器内的链接样式 */
            display: block;
            /* 将链接作为块级元素展示 */
            width: 500px;
            /* 设定链接宽为500px */
        }

        .container img {
            /* 容器内的图片样式 */
            display: block;
            /* 将图片作为块级元素展示 */
        }

        .container .left,
        /* 定义左右控制按钮样式 */
        .container .right {
            position: absolute;
            /* 绝对定位,脱离正常文档流 */
            top: 50%;
            /* 顶部居中对齐 */
            transform: translateY(-50%);
            /* 使用CSS3变换垂直居中 */
            background-color: rgba(0, 0, 0, .5);
            /* 半透明黑色背景 */
            color: white;
            /* 文字颜色为白色 */
            height: 50px;
            /* 控制按钮高度为50px */
            width: 30px;
            /* 控制按钮宽度为30px */
            line-height: 50px;
            /* 文字行高与高度一致,居中显示文字 */
            text-align: center;
            /* 文本居中对齐 */
            user-select: none;
            /* 禁止用户选择文本 */
            font-size: 25px;
            /* 字体大小为25px */
            cursor: pointer;
            /* 更改鼠标指针为手形,表明可点击 */
        }

        .container .left:hover,
        /* 左右按钮悬停时的样式 */
        .container .right:hover {
            background-color: rgba(0, 0, 0, .8);
            /* 提高背景不透明度 */
        }

        .container .left {
            /* 左侧控制按钮的位置 */
            left: 0;
        }

        .container .right {
            /* 右侧控制按钮的位置 */
            right: 0;
        }

        .container .lists {
            /* 幻灯片导航点列表样式 */
            list-style: none;
            /* 移除列表项目符号 */
            display: flex;
            /* 使用弹性布局使导航点水平居中 */
            position: absolute;
            /* 绝对定位 */
            cursor: pointer;
            /* 更改鼠标指针为手形 */
            bottom: 20px;
            /* 位于底部,距离底部20px */
            left: 50%;
            /* 居中对齐,相对于父容器左边缘 */
            transform: translateX(-50%);
            /* 使用CSS3变换使其水平居中 */
        }

        .container .lists li {
            /* 导航点单个圆点样式 */
            height: 15px;
            /* 圆点高度为15px */
            width: 15px;
            /* 圆点宽度为15px */
            background-color: #ccc;
            /* 默认背景色为灰色 */
            margin-left: 5px;
            /* 相邻圆点间有5px间距 */
            border-radius: 50%;
            /* 圆角为半径,形成圆形 */
        }

        .container .lists li:hover,
        /* 导航点圆点悬停时的样式 */
        .container .lists li.active {
            /* 当前活动幻灯片对应的圆点样式 */
            background-color: #fff;
            /* 背景色更改为白色 */
        }

        .container .lists li:first-of-type {
            /* 第一个圆点取消左边距 */
            margin-left: 0;
        }

        .container .banners {
            /* 存放幻灯片图片的ul样式 */
            list-style: none;
            /* 移除列表项目符号 */
            position: absolute;
            /* 绝对定位,方便进行动画处理 */
        }

        .container .banners li {
            /* 幻灯片图片项的样式 */
            float: left;
            /* 浮动布局,使图片水平排列 */
        }
    </style>
</head>

<body>
    
    <div class="container">
        
        <ul class="banners">
            
        </ul>

        
        <div class="left">&lt;</div>

        
        <div class="right">&gt;</div>

        
        <ul class="lists">
            
        </ul>
    </div>
    <script>
        // 定义一个配置对象,存储幻灯片组件的相关元素和属性
        var config = {
            // 获取页面上class为".container"的元素,用作幻灯片容器
            container: document.querySelector(".container"),
            // 获取class为".banners"的元素,用作包含幻灯片图片的ul元素
            banners: document.querySelector(".banners"),
            // 获取class为".lists"的元素,用作幻灯片分页指示器ul
            lists: document.querySelector(".lists"),
            // 获取class为".left"的元素,用作左侧翻页按钮
            left: document.querySelector(".left"),
            // 获取class为".right"的元素,用作右侧翻页按钮
            right: document.querySelector(".right"),
            // 设置幻灯片容器的宽度
            sizeX: 500,
            // 设置幻灯片容器的高度
            sizeY: 300,
            // 存储幻灯片图片资源地址的数组
            imgs: [
                "imgs/banner1.jpg",
                "imgs/banner2.jpg",
                "imgs/banner3.jpg",
                "imgs/banner4.jpg"
            ],
            // 存储幻灯片链接资源的数组,这里都为"javascript:void(0)"表示无实际跳转动作
            srcs: [
                "javascript:void(0)",
                "javascript:void(0)",
                "javascript:void(0)",
                "javascript:void(0)"
            ],
            // 设置默认显示的幻灯片索引
            defaultIndex: 0,
        };

        // 全局变量,记录当前显示的幻灯片索引,初始值为配置对象中的defaultIndex
        var currentIndex = config.defaultIndex;

        // 设置幻灯片容器尺寸
        config.container.style.width = config.sizeX + "px";
        config.container.style.height = config.sizeY + "px";

        // 设置幻灯片ul元素的总宽度,并根据当前索引设置其left偏移量
        config.banners.style.width = ((config.imgs.length + 1) * 100) + "%";
        config.banners.style.left = (-currentIndex * config.sizeX) + "px";

        // 遍历图片资源数组,为幻灯片和分页指示器添加相应内容
        config.imgs.forEach(function (item, i) {
            // 为每个图片创建<li>元素,包括链接和图片源,并将其添加到幻灯片ul中
            config.banners.innerHTML += `<li><a href="${config.srcs[i]}"><img src="${item}" alt=""></a></li>`;
            // 为分页指示器添加对应的<li>元素
            config.lists.innerHTML += `<li></li>`;
        });
        // 在幻灯片ul末尾追加第一个图片元素,以便实现无缝轮播
        config.banners.innerHTML += `<li><a href="${config.srcs[0]}"><img src="${config.imgs[0]}" alt=""></a></li>`;
        // 将当前默认显示的幻灯片对应的分页指示器项设置为激活状态
        config.lists.children[currentIndex].classList.add("active");

        // 定义函数,用于跳转到指定的幻灯片页数
        function toIndex(i) {
            // 如果当前索引与目标索引相同,无需执行任何操作
            if (currentIndex === i) return;
            // 如果目标索引不在有效范围内,也无需执行操作
            if (i < 0 || i > config.imgs.length) return;

            // 更新当前显示的幻灯片索引
            currentIndex = i;

            // 计算目标幻灯片应有的left偏移量
            var left = -i * config.sizeX;
            // 获取当前幻灯片的left偏移量,并转换为浮点数
            var currentLeft = Number.parseFloat(config.banners.style.left);
            // 计算需要移动的距离
            var dis = Math.abs(left - currentLeft);
            // 确定移动方向(正负1)
            var flag = currentLeft > left ? -1 : 1;

            // 移除当前激活的分页指示器项的激活样式
            config.lists.querySelector(".active").classList.remove("active");
            // 为新的目标幻灯片页数所对应的分页指示器项添加激活样式
            if (config.lists.children[i]) {
                config.lists.children[i].classList.add("active");
            } else {
                // 若目标索引超出了范围,则激活第一个指示器项
                config.lists.children[0].classList.add("active");
            }

            // 使用定时器平滑移动幻灯片
            var timer;
            // 根据需要移动的距离调整动画速度,使得在500毫秒内完成切换,每次移动5像素
            timer = setInterval(function () {
                currentLeft = currentLeft + flag * 5;
                // 判断是否已到达目标位置
                if ((flag == -1 && currentLeft < left) || (flag == 1 && currentLeft > left)) {
                    // 到达目标位置后清除定时器,并将left值设定为目标值
                    currentLeft = left;
                    clearInterval(timer);
                    // 处理循环边界条件:如果滚动到最后一张图片后,回到第一张
                    if (i == config.imgs.length) {
                        currentLeft = 0;
                        currentIndex = 0;
                    }
                    config.banners.style.left = currentLeft + 'px';
                }
                // 实时更新幻灯片的left偏移量
                config.banners.style.left = currentLeft + 'px';
            }, 2500 / dis);

        }

        // 为分页指示器添加点击事件监听器,当点击某个指示器项时跳转到对应幻灯片
        config.lists.addEventListener("click", function (e) {
            // 点击的目标如果不是分页指示器项,则返回
            if (e.target.nodeName !== "LI") return;
            // 根据点击的指示器项索引调用toIndex函数
            toIndex(Array.from(config.lists.children).indexOf(e.target));
        });

        // 为右侧翻页按钮添加点击事件监听器,点击后跳转到下一张幻灯片
        config.right.addEventListener("click", function (e) {
            toIndex(currentIndex + 1);
        });

        // 为左侧翻页按钮添加点击事件监听器,点击后跳转到上一张幻灯片,考虑循环播放情况
        config.left.addEventListener("click", function (e) {
            // 当前索引为0时,先滚动到最后一张,然后调用toIndex函数切换至最后一张
            if (currentIndex == 0) {
                config.banners.style.left = -(config.imgs.length) * config.sizeX + "px";
                toIndex(config.imgs.length - 1);
            } else {
                // 否则直接调用toIndex函数切换至上一张
                toIndex(currentIndex - 1);
            }
        });
    </script>
</body>

</html>

以笔记为镜,可以知得失