J50 DOM操作

175 阅读7分钟

1.CSS盒子模型

  • 1.传统盒子模型
    • width/height:内容的宽高
    • padding:内填充
    • border:边框
    • 盒子本身的大小=内容宽高+内填充padding+边框border
  • 2.CSS中新的盒子模型属性:box-sizing:border-box
    • width/height:盒子的宽高,不再是内容的宽高了
    • width:300px;盒子最后的大小就是300
    • 如果我们调整他的border和padding,内容也跟着自动缩放,以保证盒子最后的大小是300px

2.JS盒子模型属性

1.JS中提供了一些对应的API属性方法,可以让我们在JS中后去盒子的相关样式;

2.API(Application Programing Interface):凡是可以被调取使用的都能称作是API

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>JS盒子模型属性</title>
	<link rel="stylesheet" href="reset.min.css">
	<style>
		.outer {
			box-sizing: border-box;
			margin: 20px auto;
			width: 500px;
			height: 500px;
			background: lightcoral;
			border: 10px solid orangered;
		}

		.box {
			box-sizing: border-box;
			margin: 20px auto;
			padding: 15px;
			width: 300.4px;
			height: 300px;
			border: 10px solid lightblue;
			background: lightcyan;
			font-size: 18px;
			line-height: 30px;
		}
	</style>
</head>

<body>
	<div class="outer" id="outer">
		<div id="box" class="box">
每天都是新的开始,新的开始一定要给与自己更多的快乐和幸福。就算昨天拥有悲伤、
失败和痛苦,这一切都已经留给了昨天,现在就是一个新的起点,打开窗子,让清风吹在脸上,
让视野再宽阔一些。告诉自己,把昨天的悲伤变成今天的快乐,把昨天的失败变成今天的成功,
把昨天的不幸变成今天的幸福,如果昨天快乐,昨天幸福,昨天成功,
那么,为了同样的目标,今天也还是一个新的起点。
		</div>
	</div>
	</body>

</html>
  • 1.clientWidth:可是窗口的宽度
  • 2.clientHeight:可是窗口的高度

1.内容+padding;内容溢出没有影响

console.log(box.clientWidth);//=>280
console.log(box.clientHeight);//=>280

2.获取浏览器可视窗口(浏览器一屏幕的宽高)

//=>document.documentElement 获取HTML元素对象
console.log(document.documentElement.clientWidth);
console.log(document.documentElement.clientHeight); 
  • 3.clientLeft:左边框的宽度
  • 4.clientTop:上边框的高度

获取 左边框和上边框的大小(没有获取有边框和下边框的盒子模型)

console.log(box.clientLeft);//=>10
console.log(box.clientTop);//=>10
  • 5.offsetWidth:就是clientWidth基础上加上了左右边框(算上边框的宽度)
  • 6.offsetHeight:就是clientHeight基础上加上了上下边框(算上边框的高度)

只是在client的基础上加上了边框的大小,所以内容是否溢出对其也没有影响

console.log(box.offsetWidth);
console.log(box.offsetHeight);
  • 7.offsetLeft:获取距离左偏移
  • 8.offsetTop:获取距离上偏移
  • 9.offsetParent:获取距离其父元素参照物

1.父级参照物不是父级元素 offsetParent

2.距离其父参照物的内边框(从当前元素外边框开始,到父参照物的内边框)

3.父参照物指的是:同一个平面中最外层的元素是内层所有元素的父参照物

4.默认情况下,所有的元素都在同一个文档流(同一个平面)中,这样他们的父参照物都是BODY,所获取的偏移也都是距离BODY的

5.但是一旦某些元素设置了position(relative/absolute/fixed)就会形成一个新的平面,那么其内部元素的父参照物就不在是BODY,而是当前元素本身

console.log(outer.offsetParent); //=>BODY
console.log(box.offsetParent); //=>BODY

outer.style.position = 'relative';
console.log(outer.offsetParent); //=>BODY
console.log(box.offsetParent); //=>OUTER
  • 10.scrollWidth:包括溢出的宽度
  • 11.scrollHeight:包括移除的高度

1.在内容没有溢出的情况下, scrollWidth/scrollHeight与clientWidth/clientHeight结果是一样的

2.在内容有溢出的情况下,它的结果包含了溢出内容宽高(但是这个值是一个约等于的值,不完全准确的:在不同浏览器中,因为对内容渲染机制的差异,结果是不一样的,而且我们设置的overflow的值也对最后的结果有影响)

console.log(box.scrollWidth);
console.log(box.scrollHeight);

3.获取当前页面真实的宽度和高度,包括那部分溢出的内容

console.log(document.documentElement.scrollHeight);
console.log(document.documentElement.scrollWidth); 
  • 12.scrollLeft:横向滚动条卷去的宽度
  • 13.scrollTop:竖向滚动条卷去的高度

scrollLeft/scrollTop是13个盒子模型属性中唯二可以修改的属性(其余都是只读的,只有这两个是可读写的),通过修改对应的值,可以控制滚动条的滚动

最小值0 最大值 scrollHeight-clientHeight

//1.在内容高度溢出的时候,不知道溢出多少,给盒子多大,可以用这个api测试一下内容的高度是多少
console.log(box.scrollTop);

//2.在内容横向溢出的时候,不知道溢出多少,给盒子多大,可以用这个api测试一下内容的高度是多少
console.log(box.scrollLeft);

3.案例:回到顶部

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>回到顶部</title>
<link rel="stylesheet" href="reset.min.css">
<style>
    html,
    body {
    	height: 1000%;
    	background: -webkit-linear-gradient(top left, red, green, blue, orange, pink);
    }
    
    #GOTO {
    	position: fixed;
    	right: 0;
    	bottom: 100px;
    	width: 80px;
    	height: 50px;
    	line-height: 50px;
    	text-align: center;
    
    	display: none;
    }
    </style>
    </head>
    
    <body>
    <button id="GOTO">回到顶部</button>
    <script>
    // 开始不显示回到顶部按钮,当随着滚动条滚动,卷去的高度超过了一屏幕的大小,我们再显示这个按钮
    window.onscroll = function () {
    	// 不论我们基于什么方式,控制了滚动条的滚动,都会触发这个事件,让绑定的方法执行
    	let sT = document.documentElement.scrollTop,
    		cH = document.documentElement.clientHeight;
    	if (sT >= cH) {
    		GOTO.style.display = 'block';
    	} else {
    		GOTO.style.display = 'none';
    	}
    };
    
    GOTO.onclick = function () {
    	document.documentElement.scrollTop = 0;
    };
</script>
</body>
</html>

4.封装样式

1.从主体获取当前元素的左偏移和上偏移

兼容问题:在标准的IE8浏览器中,偏移值是当前元素外边框距离父参照物的外边框的距离(其它浏览器都是距离父参照物内边框),所以我们计算偏移值的时候,如果是IE8浏览器,则边框不需要在加了

function offset(element) {
	// 1.首先获取当前元素的父参照物和其距离父参照物的偏移
	let parent = element.offsetParent,
		top = element.offsetTop,
		left = element.offsetLeft;
	// 2.循环依次向上查找父参照物(一直到找不到为止)
	while (parent) {
		// 加上父参照物的边框(非IE8浏览器中才加)
		if (!/MSIE 8/.test(navigator.userAgent)) {
			left += parent.clientLeft;
			top += parent.clientTop;
		}
		// 加上父参照物的偏移值
		left += parent.offsetLeft;
		top += parent.offsetTop;
		// 继续向上查找
		parent = parent.offsetParent;
	}
	// 3.把查找的结果返回
	return {
		// top:top 
		top,
		left
	};
}
//  控制台:navigator.userAgent获取当前浏览器的版本信息
// 使用:控制台
/*outer.style.position='relative';
box.offsetLeft
offset(box)*/

2.获取样式

//element 获取的元素下的 
//attr:要获取样式的属性名   
//例如:控制台 getCss(box.width) getCss(box.Height)
function getCss(element, attr) {
// 优化:如果我们获取的结果符合 "数字+单位" 的格式,我们默认就把单位去掉,
//变为纯数字;如果获取的是“纯数字的字符串”,我们也把其转换为数字;
//如果是组合值或者非数字的,则不进行任何的处理;
	let value = window.getComputedStyle(element)[attr],
		reg = /^\d+(px|rem|em)?$/i;
	if (reg.test(value)) {
		value = parseFloat(value);
	}
	return value;
}

3.给元素设置样式

// JS中给元素设置样式一般也有几种方法:
// =>设置行内样式  元素.style.xxx=xxx | 元素.style.cssText='xxx'
// =>设置样式类  元素.className=xxx
function setCss(element, attr, value) {
	// 优化:如果传递的是opacity,在IE低版本中用的filter设置样式 
	if (attr === "opacity") {
		element['style']['opacity'] = value;
		element['style']['filter'] = `alpha(opacity=${value*100})`;
		return;
	}
	// 优化:对于某些特定样式(例如:width/height/margin
	// /padding/marginTop.../paddingTop.../left/top/bottom/right...)
	//如果传递的value没有带单位的(纯数字)我们手动默认给他加上PX单位
	let reg = /^(width|height|margin|padding)?(top|left|bottom|right)?$/i;
	if (reg.test(attr)) {
		// 只有人家value没有传单位,我们才自己加单位(只有传递的是有效数字,我们才设置单位)
		if (!isNaN(value)) {
			value += 'px';
		}
	}
	element['style'][attr] = value;
}
/*
//使用:控制台
例如:
setCss(box,'width','200px')
setCss(box,'height','auto')
*/

4.批量设置样式

// options是一个对象,包含了我们需要批量设置样式的属性名和属性值
function setGroupCss(element, options) {
	// 循环传递的样式对象,分别调取setCss设置样式即可
	for (let key in options) {
		if (!options.hasOwnProperty(key)) break;
		setCss(element, key, options[key]);
	}
}

/*
使用:控制台
setGroupCss(box, {
	width: 100,
	height: 100
}); */

5.实现批量设置

  • 1.如果传递的是三个参数则为单一设置样式
  • 2.如果传递的是两个参数
    • 第二个参数是个对象:则为批量设置样式
    • 第二个参数是个字符串:则为获取样式
function css(element) {
	let len = arguments.length,
		attr = arguments[1],
		value = arguments[2];
	if (len >= 3) {
		// 单一设置样式
		setCss(element, attr, value);
		return;
	}
	if (attr !== null && typeof attr === "object") {
		// 批量设置
		setGroupCss(element, attr);
		return;
	}
	// 获取样式
	return getCss(element, attr);
}

使用:控制台
例如:
//1.获取box盒子的宽度
css(box,'width')
//2.批量设置样式
css(box,{
    color:'red',
    opactity:0.5
})
//3.单一设置样式
css(box,'background','orang')

5.样式库

utils.js

/*
 * UTILS中集合了我们以后项目中经常会使用的方法 
 */
let utils = (function () {
function getCss(element, attr) {
	let value = window.getComputedStyle(element)[attr],
		reg = /^\d+(px|rem|em)?$/i;
	if (reg.test(value)) {
		value = parseFloat(value);
	}
	return value;
}

function setCss(element, attr, value) {
	if (attr === "opacity") {
		element['style']['opacity'] = value;
		element['style']['filter'] = `alpha(opacity=${value*100})`;
		return;
	}
	let reg = /^(width|height|margin|padding)?(top|left|bottom|right)?$/i;
	if (reg.test(attr)) {

		if (!isNaN(value)) {
			value += 'px';
		}
	}
	element['style'][attr] = value;
}

function setGroupCss(element, options) {
	for (let key in options) {
		if (!options.hasOwnProperty(key)) break;
		setCss(element, key, options[key]);
	}
}

function css(element) {
	let len = arguments.length,
		attr = arguments[1],
		value = arguments[2];
	if (len >= 3) {
		// 单一设置样式
		setCss(element, attr, value);
		return;
	}
	if (attr !== null && typeof attr === "object") {
		// 批量设置
		setGroupCss(element, attr);
		return;
	}
	// 获取样式
	return getCss(element, attr);
}

function offset(element) {
	let parent = element.offsetParent,
		top = element.offsetTop,
		left = element.offsetLeft;
	while (parent) {
		if (!/MSIE 8/.test(navigator.userAgent)) {
			left += parent.clientLeft;
			top += parent.clientTop;
		}
		left += parent.offsetLeft;
		top += parent.offsetTop;
		parent = parent.offsetParent;
	}
	return {
		top,
		left
	};
}

// window['_css'] = css;
// window['_offset'] = offset;

return {
	css,
	offset
};
})();

6.图片延迟加载(第一屏)图片的懒加载

为什么需要做图片的懒加载 : 第一次加载页面的时候,如果请求真实图片资源,会减缓页面的渲染速度,为了提高首次打开的体验度,我们最开始不加载真实的图片资源,只有当第一次页面整体都渲染完成后,页面中已经呈现出除图片以外的其它资源后,再去按需加载真实的图片资源渲染=> 进一步优化,最开始只加载首屏需要的图片,等到滚动条滚动的时候,再把当前屏幕中需要渲染的图片加载(避免不必要的资源加载)

  • 1.给需要延迟加载的图片设置一个外层盒子,图片在这个盒子中
  • 2.我们把图片的SRC设置为空,把后续需要请求的真实图片地址赋值给IMG的自定义属性
  • 3.在最开始渲染的样式中:
    • 给外层的盒子设置等待加载的背景LOGO(或者背景颜色,如果是背景图片,图片一定要非常的小,最好控制在5KB以内),让盒子起到一个占位的作用
    • 让所有的SRC为空的IMG默认是隐藏的
  • 4.需要等到页面加载完成后(除图片资源,其它资源都加载完成了 window.onload),在JS中获取真实图片的地址,赋值给其SRC(此时才去加载真实的图片进行渲染)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图片的延迟加载</title>
<!-- IMPORT CSS -->
<link rel="stylesheet" href="css/reset.min.css">
<style>
	img:not([src]),
	img[src=""] {
		display: none
	}

	.lazyImgBox {
		box-sizing: border-box;
		width: 300px;
		height: 500px;
		background: url("images/default.gif") no-repeat center center #DDD;
	}

	.lazyImgBox img {
		width: 100%;
		height: 100%;
	}
</style>

<script>
	/*
	 不放在末尾,还想在DOM结构加载完成后执行
		1. 把SCRIPT改成异步的即可
		2. 或者监听事件,在DOM结构加载完成在执行这个代码 
		   =>DOMContentLoaded DOM结构加载完
		   =>load 所有资源都加载完 (晚于DOMContentLoaded触发的)
	 */

	/* window.addEventListener('DOMContentLoaded', function () {
		let lazyImgBox = document.querySelector('.lazyImgBox');
		console.log(lazyImgBox);
	}); */

	/* window.onload = function () {
		let lazyImgBox = document.querySelector('.lazyImgBox');
		console.log(lazyImgBox);
	}; */
</script>
<!-- 
	async VS defer
	   async 从服务器获取资源是异步的(此时GUI继续渲染),但是一但资源请求回来,
	   会立即把请求回来的JS执行(此时会阻断GUI的继续渲染) => 多个asyncJS请求,
	   谁先回来就先执行谁,没有顺序
	   defer 从服务器获取资源是异步的,只不过请求回来资源后没有立即执行,
	   需要等到GUI绘制完成,再去按照顺序把所有请求回来的JS逐一执行
 -->
<!-- <script src="js/1.js" async></script> -->
<!-- <script src="js/1.js" defer></script> -->
</head>

<body>
<div class="lazyImgBox">
	<img src="" alt="" data-image="images/1.jpg">
</div>

<!-- IMPORT JS -->
<script>
	var lazyImgBox = document.querySelector('.lazyImgBox'),
		img = lazyImgBox.querySelector('img');

	/* window.onload = function () {
		// 获取IMG中的真实图片地址 DATA-IMAGE,赋值给它的SRC
		let trueSRC = img.getAttribute('data-image');
		img.src = trueSRC;
		img.removeAttribute('data-image');
	}; */

	setTimeout(function () {
		// 定时器是异步的,它能够触发执行的条件:不仅是到达时间了,
		//而且是主线程已经加载完成了(所以可以用它代替window.onload)

		// 进一步优化:在IE浏览器中,如果我们加载的图片不存在,
		//在图片区域不展示任何图片,会展示一个“×”,不太好看,
		//所以我们在赋值SRC之前,一般先自己校验一下地址的真实性
		var dataImage = img.getAttribute('data-image');
		var tempImg = new Image;
		//=>创建一个临时的图片对象(它不会增加到页面中,它就是用来检测地址的合法性的)
		tempImg.src = dataImage;
		tempImg.onload = function () {
			// 当前地址是正确的(tempImg.onload代表图片能正常加载出来)
			img.src = dataImage;
		};
		img.removeAttribute('data-image');
		tempImg = null;
	}, 1000);
</script>
</body>
</html>

7.图片延迟加载(第二屏)以后图片的懒加载,及渐隐渐显加载出来真真实图片

<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title>图片的延迟加载</title>
<!-- IMPORT CSS -->
<link rel="stylesheet" href="css/reset.min.css">
<style>
	/* img:not([src]),
	img[src=""] {
		display: none
	} */

	.lazyImgBox {
		box-sizing: border-box;
		margin: 1000px 0;
		width: 300px;
		height: 500px;
		background: url("images/default.gif") no-repeat center center #DDD;
	}

	.lazyImgBox img {
		width: 100%;
		height: 100%;
		/* 为了后期图片显示有动画效果 */
		opacity: 0;
		transition: opacity .3s ease 0s;
	}
</style>
</head>

<body>
<div class="lazyImgBox">
	<img src="" alt="" data-image="images/1.jpg">
</div>

<!-- IMPORT JS -->
<script src="js/utils.js"></script>
<script>
	let lazyImgBox = document.querySelector('.lazyImgBox'),
		img = lazyImgBox.querySelector('img'),
		HTML = document.documentElement;

	// B:图片所在盒子底边距离BODY顶端的距离
	// cH:浏览器可视窗口一屏幕的高度
	// A:浏览器底边框距离BODY顶端的距离
	let B = utils.offset(lazyImgBox).top + lazyImgBox.offsetHeight,
		cH = HTML.clientHeight,
		A = 0;
	window.onscroll = function () {
		if (img.isLoad) return; //=>已经处理过了,则其余的都不在处理
		A = cH + HTML.scrollTop;
		// 延迟加载的条件:图片完全出现在可视窗口中(B<=A)
		if (B <= A) {
			// 问题:当我们把图片加载出来(或者地址不存在没加载,但是也处理了),
			//但是对着浏览器继续滚动,B<=A的条件一直成立,会一直触发延迟加载操作 
			//=>在第一次处理完成后,后续即使在符合条件我们也不处理了
			lazy();
		}
	};

	// 进行图片延迟加载的
	function lazy() {
		let dataImage = img.getAttribute("data-image"),
			tempImage = new Image;
		tempImage.src = dataImage;
		tempImage.onload = () => {
			img.src = dataImage;
			// 动画:改变他的OPCITY样式即可
			// img.offsetHeight;
			//=>先让上面显示的样式渲染(触发一次回流),再让下面透明度的样式改变,
			//这样就能看到动画效果了
			utils.css(img, 'opacity', 1);
		};
		img.removeAttribute('data-image');
		tempImage = null;
		img.isLoad = true;
		//=>不论成功还是失败,都标识一下当前图片我已经处理过了
	}
</script>
</body>

</html>