元素类名
使用元素的className属性可以设置元素的类名,也可以获取类名
<body>
<div class="box"></div>
<script>
var div = document.querySelector('div')
div.className = 'test'
console.log(div) // <div class="test"></div>
console.log(div.className) // test
</script>
</body>
classList类名操作:
添加类名,语法:类名列表.add(类名)
<style>
.box{
width: 100px;
height: 100px;
background-color: #f00;
}
.green{
background-color:#0f0;
}
.border{
border:10px solid #00f;
}
</style>
<body>
<button>按钮</button>
<div class="box green"></div>
</body>
<script type="text/javascript">
// 获取标签元素
var box = document.querySelector(".box");
// 获取标签类名列表
var list = box.classList;
console.log(list);
// 获取按钮
var btn = document.querySelector("button");
// 给按钮绑定单击事件
btn.onclick = function(){
// 给类名列表中添加一个类名
list.add('border')
}
</script>
删除类名,语法:类名列表.remove(类名)
btn.onclick = function(){
// 将green从类名列表中删除
list.remove('green')
console.log(box.classList)
}
让类名在删除和添加之间切换,语法:类名列表.toggle(类名)
btn.onclick = function(){
// 让green类名在删除和点击之间切换
list.toggle('green')
}
属性操作
标签属性
元素.setAttribute(属性名,属性值) # 设置元素的属性
元素.getAttribute(属性名); # 获取元素属性
元素.removeAttribute(属性名); # 删除元素属性
H5的自定义属性操作
标签.dataset.XXX // 获取属性的值
页面卷去的距离
语法:
document.documentElement.scrollTop
兼容写法 - 不管什么情况下都能获取到:
var t = document.documentElement.scrollTop || document.body.scrollTop
短路运算
利用逻辑运算中的&&
和||
让赋值操作变得更灵活,并带有选择性
语法:
var 变量 = 数据1 && 数据2
当数据1为true,不能决定整个条件的结果,还需要进行到数据2,所以此时会将数据2赋值给变量。
当数据1为false,就已经知道整个条件的结果了,就没有必要进行到数据2了,所以此时会将数据1赋值给变量
var 变量 = 数据1 || 数据2
当数据1为true,就已经能决定整个条件的结果了,就没有必要进行数据2了,此时就将数据1赋值给变量
当数1为false,还不能决定整个条件的结果,需要进行到数据2,此时就会将数据2赋值给变量
节点操作
获取节点:
获取所有子标签节点:父.children
获取第一个子标签节点: 父.firstElementChild
获取最后一个子标签节点: 父.lastElementChild
获取父标签节点:子.parentElement
获取上一个兄弟标签节点: 标签.previouseElementSibling标签.nextElemenetSibling
创建标签:
document.createElement('标签名字符串')
插入节点:
给父标签追加子标签: 父.appendChild(子标签对象)
将新的子标签插入到某个旧的子标签前面: 父.insertBefore(新的子标签, 旧的子标签)
替换节点:
使用新的子标签替换掉旧的子标签: 父.removeChild(新的子标签, 旧的子标签)
删除节点:
父标签将指定的子标签删除:父.removeChild(子标签)
克隆节点
将一个标签复制一份出来: 标签.cloneNode()
返回一个标签对象,这样只能复制一个空的标签,没有内容。
标签.cloneNode(true)返回一个标签对象,这样可以将标签中的内容也复制出来。
获取标签尺寸
包含标签边框的尺寸:
标签.offsetWidth
标签.offsetHeight
不包含边框的尺寸:
标签.clientWidth
标签.clientHeight
获取元素位置
标签.offsetLeft
标签.offsetTop
获取的是相对于设置过定位的父标签的左边距和上边距离,返回纯数字。
获取边框大小
标签.clientTop
标签.clientLeft
回流和重绘
我们在做案例的时候,通常一个标签要设置很多样式。为了方便我们批量设置样式,可以封装一个批量设置样式的函数:
function setStyle(ele, styleObj) {
for(var key in styleObj) {
ele.style[key] = styleObj[key]
}
}
这个函数在批量设置样式的时候,每遍历一次,设置一次样式,每次设置样式都设置在了行内,这样会造成多次回流,影响页面性能。
浏览器渲染过程
-
解析html生成DOM树,解析css,生成CSSOM树,将DOM树和CSSOM树结合,生成渲染树;
-
根据渲染树,浏览器可以计算出网页中有哪些节点,各节点的CSS以及从属关系 - 回流
-
根据渲染树以及回流得到的节点信息,计算出每个节点在屏幕中的位置 - 重绘
-
最后将得到的节点位置信息交给浏览器的图形处理程序,让浏览器中显示页面
回流
回流:英文叫reflow,指的是当渲染树中的节点信息发生了大小、边距等问题,需要重新计算各节点和css具体的大小和位置。
例:在css中对一个div修饰的样式中,修饰了宽度、高度等样式,浏览器需要重新计算标签大小,这个计算的过程,就是回流的过程。
容易造成回流的操作:
-
布局流相关操作
-
盒模型的相关操作会触发重新布局
-
定位相关操作会触发重新布局
-
浮动相关操作会触发重新布局
-
-
节点操作
改变节点的结构或其中的文本结构会触发重新布局。
重绘
重绘:英文叫repaint,当节点的部分属性发生变化,但不影响布局,只需要重新计算节点在屏幕中的绝对位置并渲染的过程,就叫重绘。比如:改变元素的背景颜色、字体颜色等操作会造成重绘。
回流的过程在重绘的过程前面,所以回流一定会重绘,但重绘不一定会引起回流。
容易造成重绘操作的css:
-
color
-
border-style
-
border-radius
-
text-decoration
-
box-shadow
-
outline
-
background
优化
不管是回流还是重绘,都会对浏览器的渲染造成影响,所以我们在项目中,尽量避免回流。
合并样式修改
减少造成回流的次数,如果要给一个节点操作多个css属性,而每一个都会造成回流的话,尽量将多次操作合并成一个,例:
var oDiv = document.querySelector('.box');
oDiv.style.padding = '5px';
oDiv.style.border = '1px solid #000';
oDiv.style.margin = '5px';
操作div的3个css属性,分别是padding、border、margin,此时就可以考虑将多次操作合并为一次。
方法1:使用style的cssText
oDiv.style.cssText = 'padding:5px; border:1px solid #000; margin:5px;';
方法二:将这几个样式定义给一个类名,然后给标签添加类名
<style>
.pbm{
padding:5px;
border:1px solid #000;
margin:5px;
}
</style>
<script>
var oDiv = document.querySelector('.box');
oDiv.classList.add('pbm');
</script>
批量操作DOM
当对DOM有多次操作的时候,需要使用一些特殊处理减少触发回流,其实就是对DOM的多次操作,在脱离标准流后,对元素进行的多次操作,不会触发回流,等操作完成后,再将元素放回标准流。
var data = [
{
id:1,
name:"商品1",
},
{
id:2,
name:"商品1",
},
{
id:3,
name:"商品1",
},
{
id:4,
name:"商品1",
},
// 假设后面还有很多
];
var oUl = document.querySelector("ul");
for(var i=0;i<data.length;i++){
var oLi = document.createElement("li");
oLi.innerText = data[i].name;
oUl.appendChild(oLi);
}
这样每次给ul中新增一个li的操作,每次都会触发回流。
方法1:方法一:隐藏ul后,给ul添加节点,添加完成后再将ul显示
oUl.style.display = 'none';
for(var i=0;i<data.length;i++){
var oLi = document.createElement("li");
oLi.innerText = data[i].name;
oUl.appendChild(oLi);
}
oUl.style.display = 'block';
此时,在隐藏ul和显示ul的时候,触发了两次回流,给ul添加每个li的时候没有触发回流。
方法二:创建文档碎片,将所有li先放在文档碎片中,等都放进去以后,再将文档碎片放在ul中
var fragment = document.createDocumentFragment();
for(var i=0;i<data.length;i++){
var oLi = document.createElement("li");
oLi.innerText = data[i].name;
fragment.appendChild(oLi);
}
oUl.appendChild(fragment);
文档碎片就是一个虚拟的DOM节点。对文档碎片操作不会造成回流。
方法三:将ul拷贝一份,将所有li放在拷贝中,等都放进去以后,使用拷贝替换掉ul
var newUL = oUl.cloneNode(true);
for(var i=0;i<data.length;i++){
var oLi = document.createElement("li");
oLi.innerText = data[i].name;
newUL.appendChild(oLi);
}
oUl.parentElement.replaceChild(newUl, oUl);
避免多次触发布局
如下回到顶部的操作
goBack.onclick = function(){
setInterval(function(){
var t = document.documentElement.scrollTop || document.body.scrollTop;
t += 10;
document.documentElement.scrollTop = document.body.scrollTop = t;
},20)
}
每隔20毫秒都会重新获取滚动过的距离,每次都会触发回流,代码优化如下:
goBack.onclick = function(){
var t = document.documentElement.scrollTop || document.body.scrollTop;
setInterval(function(){
t += 10;
document.documentElement.scrollTop = document.body.scrollTop = t;
},20)
}
只获取一次,每次都让数字递增,避免每次都获取滚动过的距离。
对于页面中比较复杂的动画,尽量将元素设置为绝对定位,操作元素的定位属性,这样只有这一个元素会回流,如果不是定位的话,容易引起其父元素以及子元素的回流。
修改批量设置样式函数
function setStyle(ele, styleObj) {
var cssText = ''
for(var key in styleObj) {
var cssProp = key
for(var a = 0; a < cssProp.length; a++) {
var charCode = cssProp.charCodeAt(a)
if(charCode >= 65 && charCode <= 90) {
cssProp = cssProp.slice(0, a) + '-' + cssProp[a].toLowerCase() + cssProp.slice(a+1)
}
}
cssText += cssProp + ':' + styleObj[key] + ';'
}
ele.style.cssText = cssText
}