BOM编程艺术

什么是BOM

BOM的全称为 Browser Object Model,被译为浏览器对象模型

BOM提供了独立于HTML页面内容,而与浏览器相关的一系列对象。主要被用于管理浏览器窗口及与测览器窗口之间通信等功能。

BOM由一系列对象构成,这些对象可以简单理解为是由各个览器所提供的,例如 Window对象等。

Window对象

window对象是BOM中最核心的对象

全局作用域

  1. 在浏览器环境中运行javascript逻辑时,在全局作用域中定义的对象、变量和函数都是Window对象的属性和方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var test = 666
    console.log('test:', test)
    console.log('windows.test:', window.test)
    function t() {
    console.log('this is function')
    }
    t()
    window.t()
    delete window.test
    console.log('删除windwos.test后test的值:', test)
    console.log('删除windwos.test后windows.test的值:', window.test)

    delete test
    console.log('删除test后test的值:', test)
    console.log('删除test后windows.test的值:', window.test)

  2. 根据以上测试结果,可以很清楚的发现:删除是不起作用的。

https://antmoe.gitee.io/project/2020/05/12/01_window对象.html

浏览器窗口的宽度和高度

  • innerWidthinnerHeight属性

    只读属性,返回当前浏览器窗口的可视宽度和高度。如果存在滚动条,也包含滚动条。

  • outerWidthouterHeight属性

    只读属性,返回当前浏览器窗口的整个宽度和高度。

https://antmoe.gitee.io/project/2020/05/12/02_图片跟随窗口变化.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">

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

img {
width: 100%;
}
</style>
</head>

<body>
<img id="img" src="https://ae01.alicdn.com/kf/H1d2dc39c58a84dc0960d4173b1bbc4643.jpg" alt="">
<script>
var img = document.getElementById('img')
// resize表示重新设置大小
window.addEventListener('resize', function () {
console.log(window.innerWidth, window.innerHeight)
img.style.width = window.innerWidth + 'px'
img.style.height = window.innerHeight + 'px'
})
</script>
</body>

</html>

window.innerWidth等属性得值均为数字类型,作为宽度需加单位。例如window.innerWidth + 'px'

Windows对象与self属性

Window对象的self属性返回当前浏览器窗口的只读属性。换句话讲,Self属性返回的是 Window对象的引用。

1
2
3
4
5
if (window.top != window.self) {
console.log('这个窗口不是最顶层窗口')
} else {
console.log('这个窗口是最顶层窗口')
}

open和close

  • open[url]

    参数参数打开地址。

  • close()

    关闭当前页签

以上两个方法均是操作页签

https://antmoe.gitee.io/project/2020/05/12/03_打开与关闭浏览器窗口.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<button id="btn">按钮</button>
<button id="close">关闭</button>
<script>
var btn = document.getElementById('btn')
var closebtn = document.getElementById('close')

btn.addEventListener('click', function () {
window.open('https://baidu.com')
})
closebtn.addEventListener('click', function () {
window.close()
})
</script>
</body>

</html>

Navigator对象包含了一些有美浏览器状态的信息。可以通过window.navigator属性得到 Navigator对象。

https://antmoe.gitee.io/project/2020/05/12/04_navigator.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<script>
// 以下属性均为只读属性
console.log('浏览器的代码名:', navigator.appCodeName)
console.log('浏览器的名称:', navigator.appName)
console.log('浏览器的平台和版本信息:', navigator.appVersion)
console.log('运行浏览器的操作系统平台:', navigator.platform)
console.log('运行浏览器的userAgent:', navigator.userAgent)
// 判断用户使用的平台
var ua = navigator.userAgent
if (/windows/i.test(ua)) {
console.log('当前是Windows系统')
} else if (/mac/i.test(ua)) {
console.log('当前是mac操作系统')
} else if (/android/i.test(ua)) {
console.log('当前是android操作系统')
} else if (/iphone/i.test(ua)) {
console.log('当前是iphone操作系统')
}
</script>
</body>

</html>

使用switch语句判断用户浏览器信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var ua = navigator.userAgent
var regex = /Chrome|Edge|Firefox/i
var info = ua.match(regex)
console.log(info)
switch (info[0]) {
case 'Chrome':
console.log('Chrome')
break
case 'Edge':
console.log('Edge')
break
case 'Firefox':
console.log('Firefox')
break
case 'msie':
console.log('msie')
break
default:
console.log('你的浏览器是自创的吧?')
}

使用常规方式判断用户浏览器信息

1
2
3
4
5
6
7
8
var ua = navigator.userAgent
if (/Chrome/i.test(ua) && /Edg/i.test(ua)) {
console.log('Edge')
} else if (/Firefox/i.test(i)) {
console.log('Firefox')
} else if (/Chrome/i.test(ua)) {
console.log('Chrome')
}

https://antmoe.gitee.io/project/2020/05/13/1.html

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<script>
var ua = navigator.userAgent

if (/Chrome/i.test(ua) && /Edg/i.test(ua)) {
console.log('Edge')
} else if (/Firefox/i.test(i)) {
console.log('Firefox')
} else if (/Chrome/i.test(ua)) {
console.log('Chrome')
}

var regex = /Chrome|Edge|Firefox/i
var info = ua.match(regex)
switch (info[0]) {
case 'Chrome':
console.log('Chrome')
break
case 'Edge':
console.log('Edge')
break
case 'Firefox':
console.log('Firefox')
break
case 'msie':
console.log('msie')
break
default:
console.log('你的浏览器是自创的吧?')
}
</script>
</body>

</html>

History对象

History对象包含用户在测览器中访问过的URL(网址)。

  • length属性

    History对象的length属性可以获取用户在浏览器中访问网址的数量。

  • 前进和后退功能

    方法名称描述
    forward()实现跳转下一个页面,作用和浏览器的前进按钮一样
    back()实现跳转到上一个页面,作用和浏览器的回退按钮一样
    go()实现跳转到指定的页面。如果为负数表示后退,如果为正数表示前进

Location对象

Location对象包含了浏览器的地址栏中的信息,该对象主要用于获取和设置地址。
Location对象很特别,因为该对象既是 Window对象的属性,又是 Document对象的属性。

属性/方法名称描述
host返回服务器名称和端口号
hostname返回服务器名称
href返回当前加载页面的完整URL
pathname返回当前URL中的目录和文件名
port返回当前URL中的端口号
protocol返回页面使用的网络协议
assign()载入一个新的文档,作用和直接修改 Location相同
reload()重新载入当前文档,作用和刷新按钮一样。参数为true时,则会强制清空缓存刷新页面
replace()用新的文档替换当前文档(不会生成历史记录,不能使用回退按钮回退)
  1. 获取和设置地址

    https://antmoe.gitee.io/project/2020/05/12/05_获取和设置地址.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>

    <body>
    <button id="btn">按钮</button>
    <script>
    var btn = document.getElementById('btn')
    btn.addEventListener('click', function () {
    console.log('Location对象为:', window.location)
    window.location = 'https://www.baidu.com'
    })
    // 也可以用下边的方式
    // btn.addEventListener('click', function () {
    // console.log('Location对象的href属性为:', location.href)
    // location.href = 'https://www.baidu.com'
    // })
    </script>
    </body>

    </html>

定时器

定时器的具体方法由 Window对象提供,共有以下两种定时器

  • 延迟执行:指的是指定程序代码在指定时间后被执行,而不是立即被执行
  • 周期执行:指的是指定程序代码在指定时间为间隔,重复被执行。

目前,HTML页面中多数动态效果,以及动画效果等均由定时器内容完成。

延迟执行

语法:var timeoutID=scope.setTimeout(function/code[,delay])

  • function/code

    受调用的函数或执行的代码

  • delay

    延迟的毫秒数(1秒等于1000毫秒),函数的调用会在该延迟之后发生。如果省略该参数,delay取默认值0

  • 返回值

    该方法的返回值timeoutID是一个正整数,表示定时器的编号。这个值可以传递给clearTimeout()来取消该定时器。

基本语法测试

1
2
3
4
5
6
7
8
9
10
11
12
13
setTimeout(function () {
console.log('默认立即制行')
})
setTimeout(function () {
console.log('延迟3秒执行')
}, 3000)
console.log('测试是否被延迟制行影响')

// 清除定时 以下代码会导致 test 并不会输出
var t = setTimeout(function () {
console.log('test')
}, 3000)
clearTimeout(t)

以上代码的测试如图所示,可以看到延迟制行会打乱代码执行的顺序。即*延迟10秒执行*语句并不会导致下边的语句不执行。

https://antmoe.gitee.io/project/2020/05/13/01_延迟制行.html

周期执行

语法:var timeoutID =scope.setInterval(function/code[,delay])

  • function/code

    受调用的函数或执行的代码

  • delay

    延迟的毫秒数(1秒等于1000毫秒),函数的调用会在该延迟之后发生。如果省略该参数,delay取默认值0

  • 返回值

    该方法的返回值timeoutID是一个正整数,表示定时器的编号。这个值可以传递给clearInterval()来取消该定时器。

基本语法测试

1
2
3
4
5
6
7
8
9
10
11
12
setInterval(function () {
console.log('默认立即制行')
})
setInterval(function () {
console.log('延迟每3秒执行')
}, 3000)
console.log('测试是否被延迟制行影响')
// 清除定时
var t = setInterval(function () {
console.log('test')
}, 3000)
clearInterval(t)

可以看到,几乎与延迟执行相同。执行顺序不会受到延迟的影响。清除定时器同样导致定时消失。

将延迟执行改写为周期执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// TAG 常规写法
function fun() {
console.log('这是递归写法')
setTimeout(fun, 1000)
}
fun()

// TAG 匿名函数写法
(function () {
console.log('这是匿名函数写法')
setTimeout(arguments.callee, 1000)
})()

// TAG 匿名函数写名字写法
(function fun1() {
console.log('这是匿名写名字写法')
setTimeout(fun1, 1000)
})()

https://antmoe.gitee.io/project/2020/05/13/02_周期执行.html

动画方法requestAnimationFrame()

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()

语法:window.requestAnimationFrame(callback);

  • callback

    下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻。

  • 返回值

    一个 long 整数,请求 ID ,是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。

基本语法测试

1
2
3
4
5
6
7
8
9
10
11
console.log('this is a message ')
/*
NOTE requestAnimationFrame(callback)
NOTE 参数表示动画逻辑的回调函数
NOTE 返回值表示当前执行动画的标识
*/
requestAnimationFrame(function () {
console.log('this is animation...')
})

console.log('this is a message2 ')

类似于延迟执行,执行一次就不会在执行了。

兼容性问题

1
var requestAnimationFrame = webkitRequestAnimationFrame || mozRequestAnimationFrame || requestAnimationFrame

https://antmoe.gitee.io/project/2020/05/13/03_HTML5的动画方法.html

小案例

以下两个案例需要注意的点就是周期函数的返回值需要在全局作用域声明。在时间内部进行赋值。

动态时间显示

整体来说,这是一个非常简单的小案例。其基本逻辑用一个周期定时器包裹起来即可。

https://antmoe.gitee.io/project/2020/05/13/04_动态时间显示.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态显示时间</title>
</head>

<body>
<button id="start">开始显示</button>
<button id="stop">停止显示</button>
<h2 id="showtime"></h2>
<script>
var startBtn = document.getElementById('start')
var stopBtn = document.getElementById('stop')
var showtime = document.getElementById('showtime')
var t;
startBtn.addEventListener('click', function () {
// TAG 禁用按钮
startBtn.setAttribute('disabled', 'disabled')
t = setInterval(function () {
// TAG 1. 获取当前时间
var date = new Date()
var hour = date.getHours()
var minute = date.getMinutes()
var second = date.getSeconds()
// TAG 2. 格式化当前时间
var time = hour + ':' + minute + ':' + second
// TAG 3. 动态显示
showtime.textContent = time
})
})
stopBtn.addEventListener('click', function () {
startBtn.removeAttribute('disabled')
clearInterval(t)
})
</script>
</body>

</html>

方块移动

这个案例也很简单,同样是使用周期定时器。

https://antmoe.gitee.io/project/2020/05/13/05_方块自动移动.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">

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

#box {
width: 50px;
height: 50px;
background-color: coral;
position: absolute;
top: 200px;
left: 100px;
}
</style>
</head>

<body>
<div id="box"></div>
<script>
var box = document.getElementById('box')
var t
// NOTE 开关,false表示没有被点击过,应开始移动.
var flag = false
box.addEventListener('click', function () {
if (!flag) {
t = setInterval(function () {
// TAG 1. 获取当前方块的left
var style = window.getComputedStyle(box)
var left = parseFloat(style.left)
// TAG 2. 增加left样式属性值
left++
// TAG 3. 利用内联样式覆盖外联样式
box.style.left = left + 'px'
}, 10)
flag = true
} else {
clearInterval(t)
flag = false
}
})


</script>
</body>

</html>

小球自动向下移动

在线地址:https://antmoe.gitee.io/project/2020/05/16/1_小球自动向下移动.html

  1. 创建DIV
  2. 通过不断改变其top属性,实现不断向下移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>小球自动向下移动</title>
<style>
body {
margin: 0;
}

.box {
width: 50px;
height: 50px;
background-color: coral;
border-radius: 50%;
position: relative;
left: 400px;
top: -50px;

}
</style>
</head>

<body>
<!-- <div id="box"></div> -->
<script>
var body = document.body
// 创建div id为box的标签,并加入到body元素种
function createBox() {
var WIDTH = window.innerWidth
var div = document.createElement('div')
div.setAttribute('class', 'box')
var random = Math.random() * (WIDTH - 50);
div.style.left = random + 'px'
body.appendChild(div)
}
// 控制div向下移动
function moveDown() {
var boxs = document.getElementsByClassName('box')
for (var i = 0; i < boxs.length; i++) {
var box = boxs[i]
var style = window.getComputedStyle(box)
var boxTop = parseFloat(style.top)
boxTop++
box.style.top = boxTop + 'px'
}
}
// 创建多个小球
for (var i = 0; i < 10; i++) {
createBox()
}
// 设置定时器,向下移动
setInterval(function () {
// 调用向下移动函数
moveDown()
}, 100)

</script>
</body>

</html>

菜单自动隐藏

在线地址:https://antmoe.gitee.io/project/2020/05/16/2_菜单自动隐藏.html

与上一个类似,通过不断改变高度,实现不断变小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<html lang="en">

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

#menu {
width: 100px;
height: 300px;
border: 1px solid black;
position: absolute;
left: 400px;
top: 100px;
}
</style>
</head>

<body>
<div id="menu"></div>
<script>
// 1. 获取指定元素
var menu = document.getElementById('menu')
var flag = false
var t = setInterval(function () {
// 2. 获取有效项式
var style = window.getComputedStyle(menu)
// 3. 获取指定元素的高度值
var height = parseFloat(style.height)
// 判断高度是否为零
if (height <= 0) {
// 将指定元素隐藏
menu.style.display = 'none'
clearInterval(t)
} else {
// 4. 减少元素的高度
height--
menu.style.height = height + 'px'
}
}, 100)

</script>
</body>

</html>

键盘控制方块移动

在线地址:https://antmoe.gitee.io/project/2020/05/16/3_键盘控制方块移动.html

  1. 常规的获取HTML元素

  2. 常规的监听键盘事件(keydown

  3. 水平移动

    设置一个标志位,用于判断是向左还是向右。另外需要记录计时器的返回值。用于清除计时器。

  4. 垂直移动

    与水平方向同理

  5. 空格停止(清除移动)

    将记录的定时器编号清除即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<!DOCTYPE html>
<html lang="en">

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

#box {
width: 50px;
height: 50px;
background-color: coral;
position: absolute;
top: 200px;
left: 100px;
}
</style>
</head>

<body>
<div id="box"></div>
<script>
var html = document.documentElement
var body = document.body
var box = document.getElementById('box')
var boxStyle = window.getComputedStyle(box)
// 定义水平移动标志,垂直移动标志,垂直事件编号 水平事件编号
var leavel_flag, vertical_flag, vertical, level

document.addEventListener('keydown', function (e) {
var boxTop = parseFloat(boxStyle.top)
var boxLeft = parseFloat(boxStyle.left)
// 获取按下的按键
var key = e.code
switch (key) {
case 'ArrowUp':
vertical_flag = true
vertical = vertical_move()
break
case 'ArrowDown':
vertical_flag = false
vertical = vertical_move()
break
case 'ArrowLeft':
leavel_flag = false

level = level_move()
break
case 'ArrowRight':
leavel_flag = true
level = level_move()
break
case 'Space':
cancel()
break
}
})
// 取消计时器
function cancel() {
if (vertical) {
clearTimeout(vertical)
}
if (level) {
clearTimeout(level)
}
}
function level_move() {
cancel()
// TAG flag 为true时,+操作,即向右
if (leavel_flag) {
return setInterval(function () {
box.style.left = (++boxLeft) + 'px'
}, 50)

} else { //TAG 否则向左
return setInterval(function () {
box.style.left = (--boxLeft) + 'px'