盒子模型-DOM操作相关
DOM,文档对象模型,提供一系列的属性和方法让我们能够通过JS操作页面中的元素
获取属性/方法API
获取元素方法
document.getElementById(id) // 根据id获取元素
[context].getElementsByTagName(tagname) // 根据标签名获取元素
[context].getElementsByClassName(classname) // 根据类名获取元素
document.getElementsByName(name) // 根据name属性获取元素(IE中只对表单元素的name有用)
[context].querySelector(selector) // 通过选择器获取第一个元素
[context].querySelectorAll(selector) // 通过选择器获取所有元素
注:
其中[context]
开头的在IE6-8中不兼容
获取元素属性
document // 整个文档对象
document.documentElement // document对象的根节点
document.head // head元素
document.body // body元素
childNodes // 所有子节点(不只是元素节点)
children // 所有元素子节点
// IE6-8中会把注释节点当做元素节点获取
parentNode // 元素父节点
firstNode/firstElementChild // 第一个子节点/第一个元素子节点
lastChild/lastElementChild // 最后一个子节点/最后一个元素子节点
previousSibling/previousElementSibling // 上一个子节点/上一个元素子节点
nextSibling/nextElementSibling // 下一个子节点/下一个元素子节点
注:
所有带Element
的在IE6-8中不兼容
注:
childNodes
属性可以获取所有子节点,但是除了IE6-8
以外的版本里,都会把空白区也当成节点,即文本节点,比如:
html中:
<ul id="ul1">
<li></li>
<li></li>
<li></li>
</ul>
js中:
var oUl = document.getElementById("ul1")
alert(oUl.childNodes.length);
会发现弹窗结果为7而不是3,原因就是childNodes
会把空白处也当成节点(文本节点),因此三个标签前后都有一个文本节点,合起来就7个。
为了解决这个问题可以使用nodeType
(childNode
下的属性),当是文本节点时值为3,元素节点(即标签)时值为1,可以用其来判断是否为标签
还有个children
属性,和childNode
同级,其识别子节点只识别标签,即childNode
和nodeType
的结合,比如对于上面的html,使用如下js语句结果返回就是3:
var oUl = document.getElementById("ul1")
alert(oUl.children.length);
注:
求子节点数量只包括其下第一层的节点
元素操作
document.createElement(tagname) // 创建一个元素
document.createTextNode(text) // 创建一个文本节点
[element].innerHTML = xxx // 添加/修改节点内部HTML代码
[element].innerText = xxx // 添加/修改节点内部文本信息
[parent].appendChild(newElement) // 往parent节点里的最后添加子节点
[parent].insertBefore(newElement, element) // 往parent节点里的指定位置添加节点
[element].cloneNode(true/false) // 拷贝节点(参数代表深/浅拷贝)
[parent].removeChild(element) // 删除节点
简单举例:
var div = document.createElement('div');
div.innerHTML = "动态创建的div标签";
document.body.appendChild(div)
var node = document.getElementById("node");
var div = document.createElement('div');
odiv.insertBefore(div, node;
// 将新创建的div节点插入到node节点前
注:
这里appendChild
添加的是一个新建的节点,所以会认为该方法的功能是追加节点,但其实际上的功能是把原来的节点删了,再添加到新的地方上去,比如用appendChild
操作一个本身存在的节点,会发现原来的节点没了,而出现在了新的位置上,所以准确地说该方法是移动节点
属性操作
[element].attributes // 元素所有属性
[element].xxx = xxx // 修改节点属性
delete [element].xxx // 删除节点属性
[element].setAttribute("xxx", xxx) // 添加/修改节点属性
[element].getAttribute("xxx") // 获取节点属性值
[element].removeAttribute("xxx") // 删除节点属性
其他属性
tBodies/rows/cells
表格特有的属性,分别对应表格<table>
的<tbody>/<tr>/<td>
,比如平常要获取表格的某一格内容:
document.getElementsByTagName('tbody')[i].getElementsByTagName('tr')[i].getElementsByTagName('td')[i]
现在就可以改成:
document.getElementsByTagName("table").tBodies[i].rows[i].cells[i]
因为一个表格可以有好几个<tbody>、<tr>和<td>,所以上面那三个都是数组,还有像tHead
、tFoot
也可以用,而这两个因为一个表格只有一个,所以其是元素而不是一个数组
class操作
[element].classList // 获取节点类,以list形式
[element].className // 获取节点类,以字符串形式
[element].classList.add("xxx") // 给节点添加类
[element].classList.contains("xxx") // 查看节点是否有某个类
[element].classList.remove("xxx") // 删除节点的某个类
样式操作
[element].style.xxx // 获取当前元素的行内样式(只能获取行内的,如:CSS文件中定义的无法获取)
[element].style.xxx = xxx; // 修改和设置行内样式(只能修改行内的,如:CSS文件中定义的无法修改)
[element].className = xxx; // 设置样式类
[element].style.xxx = ""; // 删除样式
window.getComputedStyle(element, 伪类) // 获取元素计算后的最终样式(没有设置的则会按默认来)
[element].currentStyle // IE6-8没有getComputedStyle方法,此时需要使用该属性获取所有样式
盒子模型-js属性
基于一些属性和方法,获取当前元素的样式信息
属性
- client
clientWidth/clientHeight
clientTop/clientLeft
- offset
offsetWidth/offsetHeight
offsetTop/offsetLeft
offsetParent
- scroll
scrollWidth/scrollHeight
scrollTop/scrollLeft
client相关
clientWidth/clientHeight
获取盒子可视区域宽高(content+padding)
- 内容溢出不影响该属性值大小
- 获取的结果没有单位(其他盒子模型属性也是)
- 获取的结果是四舍五入的整数(其他盒子模型属性也是)
举例:
let box = document.getElementById("xxx");
box.offsetWidth
获取当前屏幕可视区域宽高:
let w = document.documentElement.clientWidth || document.body.clientWidth;
let h = document.documentElement.clientHeight || document.body.clientHeight;
clientLeft/clientTop
获取盒子左边框和上边框的大小
scroll相关
scrollWidth/scrollHeight
- 内容没溢出时,结果和
client
相关的一样 - 内容溢出时,结果约等于真实的宽高(padding + content)
- 不同浏览器获取的结果不一定相同
- 设置overflow属性对最后结果会产生影响
举例:
let box = document.getElementById("xxx");
box.scrollWidth
document.documentElement.scrollHeight || document.body.scrollHeight
// 整个页面真实高度
scrollLeft/scrollTop
滚动条卷去的宽高
边界值:
min = 0
max = 整个高度scrollHeight - 屏幕高度clientHeight
注:
13个盒子模型属性中,只有这两个是可读写的属性,其他的都是只读属性,例如下面的代码是合法的:
box.scrollTop = 0;
offset相关
offsetWidth/offsetHeight
获取盒子本身的宽高(content+padding+border)
offsetParent
获取父参照物(未必是父元素)
- 同一平面中,最外层元素是所有后代元素的父参照物,但脱离文档流的元素,父参照物就未必是最外层元素了
-
body
的父参照物为null
,举例:
document.body.offsetParent = null;
offsetTop/offsetLeft
距离其父参照物的上/左偏移(当前元素的外边框到父参照物的内边框)
获取元素相对body偏移
- 获取父参照物的偏移和边框
- 获取父参照物的父参照物的偏移和边框
- ...
- 直到父参照物为空为止
- 将全部加到一起
function offset(ele) {
let parent = ele.offsetParent;
let left = ele.offsetLeft;
let top = ele.offsetTop;
while (parent && parent.tagName !== "BODY") {
if(!/MSIE 8\.0/.test(navigator.userAgent)) {
left += parent.clientLeft;
top += parent.clientTop;
}
left += parent.offsetLeft;
top += parent.offsetTop;
parent = parent.offsetParent;
}
return { top, left }
}
获取CSS样式相关
获取内联样式
document.xxx.style
获取CSS标签样式
document.styleSheets[0].rules[0].style
获取动态样式
window.getComputedStyle(xxx)
能够获取当前元素所有经过浏览器计算过的样式
- 页面中呈现的元素的所有样式都是经过浏览器计算的
- 即使没有设置过的样式也都会自动计算
- 在IE6-8中不兼容该方法,需要通过
currentStyle
属性获取
// 传入两个参数,第一个是元素,第二个是其伪类:`:after`/`:before`
// 获取的是`CSSStyleDeclaration`类的对象,包含了当前元素所有的样式信息
let style = window.getComputedStyle(element, null);
style.display // 获取对应属性结果
style["backgroundColor"] // 属性名中有"-"用驼峰代替
元素相关
常见属性
nodeType
元素节点是1,文本节点是3
更多参考:https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType
title
鼠标悬浮提示
value
如表单里的值
textContent/innerText
元素文本内容
区别参考:https://blog.csdn.net/qq_39207948/article/details/86099905
innerHTML
内部文档
style
样式属性
activeElement
当前页面的焦点元素
事件相关
所有的事件都是以on
开头命名的,在事件调用函数时可以调用多个,以分号隔开,举例:
<body onload="fun1();fun2()" onunload="fun3()">
常见事件
onload
页面资源加载完成事件,当js代码绑定操作时,不管是在html中还是通过js文件引入,必须得在标签后面出现,因为如果执行js,会因为找不到对应绑定的标签而报错。当然还有另一种办法,那就是在前面先用window.onload()
来解决,其意思就是这段代码在页面加载完成时才执行,举例:
window.onload = function() {
var element = document.getElementById("b");
element.onclick = function() {
alert('xyz');
}
}
onunload
页面卸载事件,即关闭页面
onmousedown
当按下鼠标时触发
onmouseup
鼠标点下后松开时触发
onmousemove
鼠标移动时触发
onmouseover
鼠标进入时触发,鼠标移入时全选文本框举例:
<input type="text" onmouseover="this.select()">
onmouseout
和上面相反,鼠标移出时触发
onclick
鼠标单击时触发
ondbcllick
鼠标双击时触发
onfocus
当焦点在这个元素内时触发
onblur
当焦点离开这个元素时触发
onkeydown
键盘按下时触发,可以通过事件的keyCode
来得知是哪个键(ASCII码),举例:
document.onkeydown = function(ev) {
oEvent = ev || event;
alert(oEvent.keyCode);
}
再比如用键盘上下左右控制标签移动:
var odiv = document.getElementById('a');
document.onkeydown = function(ev) {
oEvent = ev || event;
switch (oEvent.keyCode) {
case 37:
odiv.style.left = odiv.offsetLeft - 10 + 'px';
break;
case 39:
odiv.style.left = odiv.offsetLeft + 10 + 'px';
break;
case 38:
odiv.style.top = odiv.offsetTop - 10 + 'px';
break;
case 40:
odiv.style.top = odiv.offsetTop + 10 + 'px';
break;
}
}
然后还有一些如ctrl
,其给了特殊的字符ctrlKey
来代替,当然在keyCode
中数值为17,还有shiftKey
、altKey
,然后现在实现按ctrl+回车
把输入框的东西弄到消息框里,此时要注意的是因为按回车的时候焦点是在输入框,所以事件要写在文本框里:
var odiv = document.getElementById('a');
var otext = document.getElementById('c');
document.onkeydown = function(ev) {
oEvent = ev || event;
if (oEvent.keyCode == 13 && oEvent.ctrlKey) { //ctrl+回车
odiv.textContent += '\n' + otext.value;
otext.value = '';
}
}
onkeyup
键盘松开时触发
onsubmit/onreset
表单里点击提交和重置时触发事件
oncontextmenu
鼠标右键的上下文菜单,我们可以修改这个方法从而改变右键后的内容:
document.oncontextmenu = function(){
alert('别点右键');
return false;
};
所以现在可以根据这个方法写个自定义菜单:
html中:
#a {
background: blue;
width: 100px;
height: 150px;
position: absolute;
display: none;
}
<div id="a">
<div>选项1</div>
<div>选项2</div>
<div>选项3</div>
<div>选项4</div>
</div>
js中:
window.onload = function() {
var odiv = document.getElementById('a');
document.oncontextmenu = function(ev) {
oEvent = ev || event;
odiv.style.display = 'block'; //显示菜单
odiv.style.background = 'blue';
odiv.style.left = oEvent.clientX + 'px'; //菜单出现在鼠标处
odiv.style.top = oEvent.clientY + 'px';
return false;
};
document.onclick = function(ev) {
oEvent = ev || event;
if (oEvent.clientX > parseInt(odiv.style.left) && oEvent.clientX <= parseInt(odiv.style.left) + 100 && oEvent.clientY > parseInt(odiv.style.top) && oEvent.clientY <= parseInt(odiv.style.top) + 150) {
odiv.style.background = 'red'; //当在菜单内点击背景变色
} else {
odiv.style.display = 'none'; //菜单外点击菜单消失
}
}
}
onhashchange
地址栏地址改变
DOMSubtreeModified
dom子元素节点修改事件,举例:
document.addEventListener("DOMSubtreeModified", (e) => {console.log(e)})
DOMNodeInserted
dom子元素节点添加事件
DOMNodeRemoved
dom子元素节点删除事件
注:
上面几个的原理是通过MutationEvent
监听实现的,现在的标准是使用MutationObserver
来进行监听,举例:
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
.aaa {
height: 500px;
width: 500px;
background: black;
}
.bbb {
height: 300px;
width: 300px;
background: gainsboro;
}
.hide {
display: none;
}
</style>
</head>
<body>
<div class="aaa">
<button onclick="togglebbb()">点击隐藏/显示</button>
<div class="bbb"></div>
<div class="ccc"></div>
</div>
</body>
<script type="text/javascript">
// 修改bbb、ccc节点属性
function togglebbb() {
document.querySelector(".ccc").setAttribute("test", "1");
let node = document.querySelector(".bbb");
if (node.classList.contains("hide")) return node.classList.remove("hide");
node.classList.add("hide");
}
let node = document.querySelector(".aaa");
// 监听aaa节点及子节点的属性变化
let config = { attributes: true, subtree: true };
let callback = (mutationsList, observer) => {
Array.from(mutationsList).map(mutation => {
console.log("节点:", mutation.target, "改变属性:", mutation.attributeName);
});
// 只监听一次以后关闭监听
observer.disconnect();
};
// 浏览器兼容
let MutationObserver =
window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
// 设置回调
let observer = new MutationObserver(callback);
// 启动观察
observer.observe(node, config);
</script>
</html>
动态绑定事件
一般一个标签同一个事件只能绑定一次,假如要给一个标签绑定两个onclick
事件,那么结果很可能就是后一个事件把前一个给覆盖了,举例:
var obtn = document.getElementById('a');
obtn.onclick = function(){
alert('a');
}
obtn.onclick = function(){
alert('b');
}
结果只会提示b,所以如果想要实现函数不会被覆盖,可以一个事件有多个函数的话,那么就要用到动态绑定事件
addEventListener
element.addEventListener("click", fun, false)
// 三个参数依次为事件类型、回调函数、相关配置
举例:
element.addEventListener("事件", 函数, false) //这里事件去掉on,比如click
上面的参数,前两个很好理解,第三个参数如果不传则按默认的来,如果传入相关配置,则需要是对象类型,部分配置参考:
once 事件只调用一次,调用完则移除事件
passive 是否为被动调用,如果设置为true,将无法使用`preventDefault()`方法,大部分事件默认都是false,但是浏览器为了提升性能,在一些事件上设置为了true,如网页缩放(ctrl+滚轮)
举例-自定义区域缩放事件,并且阻止浏览器默认缩放事件:
<style>
.container {
height: 500px;
width: 500px;
background: gainsboro;
overflow: auto;
}
.content {
height: 300px;
width: 300px;
color: white;
background: black;
}
</style>
<body>
<div class="container">
<div class="content">鼠标在黑色区域内按住ctrl+滚轮滑动,实现放大缩小</div>
</div>
</body>
<script>
const func = e => {
if (!e.target.classList.contains("content")) return e.preventDefault();
let content = e.target;
let width = parseInt(content.style.width || "100%");
let height = Math.max(parseInt(content.style.width || "100%"), 30);
// 浏览器兼容
if ((e.deltaY || e.detail) < 0) {
content.style.width = `${width * 1.1}%`;
content.style.height = `${height * 1.1}%`;
} else {
content.style.width = `${width / 1.1}%`;
content.style.height = `${height / 1.1}%`;
}
e.preventDefault();
}
// mousewheel事件浏览器默认设置passive为true,为了能够阻止默认事件,需要我们手动设置为true
document.addEventListener("DOMMouseScroll", func , {passive:false});
document.addEventListener("mousewheel", func , {passive:false});
</script>
参考:
https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
https://blog.csdn.net/csdnXiaoZou/article/details/87276026
绑定事件在IE下比较特殊,需要使用到下面的方法绑定:
obj.attachEvent('事件', 函数) //这里事件写完整名,比如onclick
解决IE兼容性
var obtn = document.getElementById('a');
if (obtn.attachEvent) { //IE下
obtn.attachEvent('onclick', abc);
} else { //FireFox/chrome下
obtn.addEventListener('click', abc, false);
}
function abc() {
alert('a');
}
obtn.onclick = function() {
alert('b');
}
解除绑定
IE下:detachEvent('事件', 函数)
,DOM方式:removeEventListener('事件', 函数, false)
事件的绑定和解绑参考:https://blog.csdn.net/tswc_byy/article/details/82824798
事件对象相关API
监听事件时,默认会传递事件对象本身
target
事件对象的DOM元素
clientX/clientY
横纵坐标位置,举例点击时显示鼠标位置:
document.onclick = function(event) {
alert(event.clientX + ',' + event.clientY);
}
cancelBubble
在HTML页面中默认允许事件冒泡,比如给document
加个onclick
事件,然后再在页面里一个标签加个onclick事件,当点击标签时,会发现标签和document事件都触发了,如下代码:
document.onclick = function() {
alert("document's event")
}
var odiv = document.getElementById('a');
odiv.onclick = function() {
alert("div's event")
}
结果会发现两个alert都执行了,可以发现事件冒泡就是标签执行完事件后会继续执行父节点的事件,一直往上层执行,所以这样有时候可能就会造成一些意外的结果。为了避免事件冒泡,可以给对应的标签事件里设置:
ev.cancelBubble = true;
stopPropagation
阻止事件传播
preventDefault
阻止默认行为
其他
滚动条事件监听
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.box {
width: 100%;
height: 1000px;
background: repeating-linear-gradient(45deg, blue 0px, blue 30px, white 30px, white 60px);
}
</style>
</head>
<body>
<div class="box"></div>
</body>
<script>
document.addEventListener("scroll", function(e) {
console.log("scroll:", window.scrollY);
});
</script>
</html>
表单上传相同数据不触发change事件解决
思路:在每次提交后清空表单即可,代码示例:
<body>
<form class="upload">
<input type="file" multiple onchange="change(this)" />
<input type="reset" id="reset" style="display: none;" />
</form>
</body>
<script>
function change(_this) {
for(let file of _this.files) console.log(file.name);
document.querySelector("#reset").click();
// 每次上传完成清空表单
}
</script>
纯前台下载功能实现
// 方式一:通过base64下载
function downloadByBase64() {
// 下载数据
let data = ["1", "2", "3", "4", "5"];
let str = data.join(",");
str += "\n";
// encodeURIComponent解决中文乱码
let uri = "data:text/csv;charset=utf-8,\ufeff" + encodeURIComponent(str);
// 创建a标签
let link = document.createElement("a");
link.style.display = "none";
link.href = uri;
// 默认文件名
link.download = "test.csv";
document.body.appendChild(link);
// 触发点击
link.click();
document.body.removeChild(link);
}
// 方式二:通过二进制流下载
function downloadByBin() {
let data = ["1", "2", "3", "4", "5"];
let str = data.join(",");
str += "\n";
let link = document.createElement("a");
link.download = "test.csv";
link.style.display = "none";
let blob = new Blob([str]);
link.href = URL.createObjectURL(blob);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
浏览器下匿名函数绑定事件解绑(基于chrome提供的api)
1.通过getEventListeners
获取对应的dom节点上绑定的所有事件
2.获取事件上对应的listener
3.利用和绑定对应的方式并传入listener进行解绑
举例(chrome命令行下,放在js文件中会报错):
let dom = document.querySelector(".bind-event");
let f = getEventListeners(dom )["copy"][0].listener;
dom .removeEventListener("copy", f)