第12章 DOM操作
*1. 向DOM中注入HTML
1.1 将HTNL字符串转换成DOM
- 转换的步骤如下所示:
- 确保HTML字符串是合法有效的
- 将它包裹在任意符合浏览器规则要求的闭合标签中
- 使用innerHTML将这串HTML插入到虚拟的DOM元素中
- 提取该DOM节点
预处理HTML源字符串
// 确保自闭合元素被正确解释
// 单标签
const tags = /^(area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
// 通过正则把错误的单标签转换为标签对
function convert(html) {
return html.replace(/(<(\w+)[^>]*?)\/>/g, (all, front, tag) => {
return tags.test(tag) ? all : front + "></" + tag + ">";
});
}
console.log(convert("<a/>"));
// <a></a>
console.log(convert("<hr />"));
// <hr />
包装HTML
- 根据HTML语义,一些HTML元素必须包装在某些容器元素中。有两种方式可以解决(都需要构建问题元素和容器之间的映射关系)
- 通过innnerHTML将该字符串直接注入到它的特定父元素中,该父元素提前使用内置的document.creatElemnet创建好
- HTML字符串可以在使用对应父元素包装后,直接注入到任意容器元素中
- 需要包装在其他元素中的元素
元素名称 | 父级元素 |
---|---|
<option>, <optgroup> | <select multiple>...</select> |
<legend> | <fieldset>...</fieldset> |
<thead>, <tbody>, <tfoot>, <colgroup>, <caption> |
<table>...</table> |
<tr> | <table><thead>...</thead></table> <table><tbody>...</tbody></table> <table><tfoot>...</tfoot></table> |
<td>, <th> | <table><tbody><tr>...</tr></tbody></table> |
<col> | <table> <tbody></tbody> <colgroup>...</colgroup> </table> |
使用具有multiple属性的<select>元素,因为它不会自动检查任何包含在其中的选项
对<col>的兼容处理需要一个额外的,否则<colgroup>不能正确生成
// 将元素标签转换为一系列DOM节点
function getNodes(htmlString, doc) {
// 需要特殊父级容器的元素映射表。
// 每个条目包含新节点的深度,以及父元素的HTML头尾片段
const map = {
"<td": [3, "<table><tbody><tr>", "</tr></tbody></table>"],
"<th": [3, "<table><tbody><tr>", "</tr></tbody></table>"],
"<tr": [2, "<table><thead>", "</thead></table>"],
"<option": [1, "<select multiple>", "</select>"],
"<optgroup": [1, "<select multiple>", "</select>"],
"<thead": [1, "<table>", "</table>"],
"<tbody": [1, "<table>", "</table>"],
"<tfoot": [1, "<table>", "</table>"],
"<colgroup": [1, "<table>", "</table>"],
"<caption": [1, "<table>", "</table>"],
"<col": [2, "<table><tbody></tbody><colgroup>", "</colgroup></table>"]
}
const tagName = htmlString.match(/<\w+/);
let mapEntry = tagName ? map[tagName[0]] : null;
// 如果映射表中有匹配,使用匹配结果
// 如果没有,则构造空的父标记,深度为0作为结果
if (!mapEntry) { mapEntry = [0, "", ""] }
// 创建用于包含新节点的元素,如果传入了文档对象,则使用传入的,否则使用当前的
let div = (doc || document).createElement("div");
// 使用匹配得到的父级容器元素,包装后注入新创建的元素中
div.innerHTML = mapEntry[1] + htmlString + mapEntry[2];
// 参照映射关系定义的深度,向下遍历刚刚创建的DOM树,最终得到新创建的元素
while (mapEntry[0]--) {
div = div.lastChild;
}
// 返回新创建的元素
return div.childNodes;
}
1.2 将DOM元素插入到文档中
// 新增frgment参数,新增节点将被添加到这个DOM片段中
function getNodes(htmlString, doc, fragment) {
const map = {
"<td": [3, "<table><tbody><tr>", "</tr></tbody></table>"],
"<th": [3, "<table><tbody><tr>", "</tr></tbody></table>"],
"<tr": [2, "<table><thead>", "</thead></table>"],
"<option": [1, "<select multiple>", "</select>"],
"<optgroup": [1, "<select multiple>", "</select>"],
"<thead": [1, "<table>", "</table>"],
"<tbody": [1, "<table>", "</table>"],
"<tfoot": [1, "<table>", "</table>"],
"<colgroup": [1, "<table>", "</table>"],
"<caption": [1, "<table>", "</table>"],
"<col": [2, "<table><tbody></tbody><colgroup>", "</colgroup></table>"]
}
const tagName = htmlString.match(/<\w+/);
let mapEntry = tagName ? map[tagName[0]] : null;
if (!mapEntry) { mapEntry = [0, "", ""] }
let div = (doc || document).createElement("div");
div.innerHTML = mapEntry[1] + htmlString + mapEntry[2];
while (mapEntry[0]--) {
div = div.lastChild;
}
// 添加节点到DOM片段中
if (fragment) {
while (div.firstChild) {
fragment.appendChild(div.firstChild);
}
}
return div.childNodes;
}
<div id="test"><b>Hello</b>, I'm Wango!</div>
<div id="test2"></div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// 在DOM的国歌位置插入DOM片段
function insert(elems, args, callback) {
if (elems.length) {
const doc = elems[0].ownerDocument || elems[0];
const fragment = doc.createDocumentFragment();
const scripts = getNodes(args, doc, fragment);
const first = fragment.firstChild;
if (first) {
for (let i =0; elems[i]; i++) {
callback.call(elems[i], i > 0 ? fragment.cloneNode(true) : fragment);
}
}
}
}
const divs = document.querySelectorAll("div");
insert(divs, "<b>Name:</b>", function(fragment) {
this.appendChild(fragment);
});
insert(divs, "<span>First</span><span>Last</span>", function (fragment) {
this.parentNode.insertBefore(fragment, this);
});
});
</script>
2. DOM的特性和属性
通过DOM方法和属性访问特性值
<div></div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const div = document.querySelector("div");
// HTML DOM的原生特性,通常能被属性表示
div.setAttribute("id", "news-01");
console.log(div.id);
// news-01
console.log(div.getAttribute("id"));
// news-01
div.id = "news-02";
console.log(div.getAttribute("id"));
// news-02
// 但自定义特性不能被元素属性表示,需要使用
// setAttribute()和getAttribute()
div.setAttribute("data-news", "breaking");
console.log(div.getAttribute("data-news"));
// breaking
});
</script>
在HTML5中,为遵循规范,建议使用data-作为自定义属性的前缀,方便区分自定义特性和原生特性
3. 令人头疼的样式特性
常用的style元素属性是一个对象,该对象的属性与元素标签内指定的样式相对应。
3.1 样式在何处
<style>
div {
font-size: 1.8em;
border: 0 solid gold;
}
</style>
<div style="color: #000;" title="Hello"></div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const div = document.querySelector("div");
// 内联样式被记录
console.log(div.style.color);
// rgb(0, 0, 0)
// <style>标签内定义的的样式没有被记录
console.log(div.style.fontSize === "1.8em");
// false
console.log(div.style.borderWidth === "0");
// false
// 样式对象中不反应从CSS样式表中继承的任何样式信息
// 新赋值的样式被记录
div.style.borderWidth = "10px";
console.log(div.style.borderWidth);
// 10px
});
</script>
内联样式中的任何值,都优先于样式表继承的值(即使样式表规则使用!important的注释)
3.2 样式属性命名
一种访问样式的简单方法
<div style="color: red;font-size: 10px;background-color: #eee;"></div>
<script>
// 处理样式函数
// 如果传入value,将相应样式属性值赋值为value
// 如果没有传入value,则返回改样式属性值
// 可以通过它来设置/读取样式属性
function style(elem, key, value) {
// 将属性名转为驼峰格式
// 以同时兼容驼峰式和连字符式样式名
key = key.replace(/-([a-z])/ig, (all, letter) => {
return letter.toUpperCase();
});
// 如果传入value则将相应样式属性值设置为value
if (typeof value !== "undefined") {
elem.style[key] = value;
}
return elem.style[key];
}
document.addEventListener("DOMContentLoaded", () => {
const div = document.querySelector("div");
// 设置属性
style(div, "font-size", "20px");
style(div, "background-color", "#000");
console.log(div.style.fontSize === "20px");
// true
console.log(div.style.backgroundColor === "rgb(0, 0, 0)");
// true
// 读取属性
console.log(style(div, "font-size"));
// 20px
console.log(style(div, "background-color"));
// rgb(0, 0, 0)
});
</script>
3.3 获取计算后样式
一个元素的计算后样式(computed style)都是应用在该元素上的所有样式的组合,这些样式包括样式表、元素的style内联样式、、浏览器内置样式、JS脚本对style所作的各种操作等
<style>
div {
background-color: #ffc;
display: inline;
font-size: 1.8em;
border: 1px solid crimson;
color: green;
}
</style>
<div style="color: crimson;" id="test" title="hello"></div>
<script>
// 用于获取元素计算后属性
function fetchComputedStyle(elem, property) {
// getComputedStyle是浏览器提供的全局函数,可直接调用
const computedStyle = getComputedStyle(elem);
if (computedStyle) {
// 将传入的样式名转换为中横线分割
// 以同时兼容驼峰式和连字符式样式名
property = property.replace(/([A-Z])/g, "-$1".toLowerCase());
// getComputedStyle返回的对象提供了getPropertyValue方法
// 这个方法接收中横线分割格式的样式名
return computedStyle.getPropertyValue(property);
}
}
document.addEventListener("DOMContentLoaded", () => {
const div = document.querySelector("div");
console.log(fetchComputedStyle(div, "background-color"));
// rgb(255, 255, 204)
console.log(fetchComputedStyle(div, "color"));
// rgb(220, 20, 60) 返回的是内联样式中color的值,内联样式将css样式覆盖了
console.log(fetchComputedStyle(div, "borderWidth"));
// 1px
console.log(fetchComputedStyle(div, "borderTop"));
// 1px solid rgb(220, 20, 60)
});
</script>
3.4 测量元素的高度和宽度
- height和width的默认值都是auto,所以无法获取准确的值
- 使用offsetHeight和offsetWidth,但这两个值包含了padding值
- 隐藏元素(display: none)没有尺寸,offsetHeight和offsetWidth为0
- 获取隐藏元素在非隐藏状态下的尺寸可以先取消隐藏,获取值,再隐藏,具体为:
- 将display设置为block(可以获取值了,但元素会可见)
- 将visibility设置为hidden(使元素不可见,但元素位置会显示一个空白)
- 将position设置为absolute(将元素移出正常的可视区)
- 获取元素尺寸
- 恢复先前更改的属性
<div id="div1" style="display: none;width: 100px;height: 200px;background-color: #000;"></div>
<div id="div2" style="width: 300px;height: 400px;background-color: #00ff00;"></div>
<script>
(function(scope) { // 使用立即执行函数创建私有作用域
const PROPERTIES = {
position: "absolute",
visibility: "hidden",
display: "block"
}
scope.getDimensions = elem => {
const previous = {}; // 用于保存原有属性值
for (let key in PROPERTIES) {
previous[key] = elem.style[key]; // 保存原有值
elem.style[key] = PROPERTIES[key]; // 替换设置
}
const results = { // 保存结果
width: elem.offsetWidth,
height: elem.offsetHeight
}
for (let key in PROPERTIES) { // 还原设置
elem.style[key] = previous[key];
}
return results;
}
})(window);
document.addEventListener("DOMContentLoaded", () => {
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2");
console.log(getDimensions(div1).height);
// 200
console.log(getDimensions(div1).width);
// 100
console.log(getDimensions(div2).height);
// 400
console.log(getDimensions(div2).width);
// 300
});
</script>
检查offsetHeight和offsetWidth属性值是否为0,可以非常有效地确定一个元素的可见性
4. 避免布局抖动
- 抖动原因:代码对DOM执行一系列(通常是不必要的)连续的读取和写入时(浏览器执行大量重新计算),浏览器无法优化布局操作
引起布局抖动的API和属性
接口对象 | 属性名 |
---|---|
Element | clientHeight, clientLeft, clientTop, clientWidth, focus, getBoundingClientRect, getClientRects, innerText, offsetHeight. offsetLeft, offsetParent, offsetTop, offsetWidth, outerText, scrollByLines, scrollByPages, scrollHeight, scrollIntoView, scroollIntoViewIfNeeded, scrollLeft, scrollTop, scrollWidth |
MouseEvent | layerX, layerY, offsetX, offsetY |
Window | getComputedStyle, scrollBy, scrollTo, scroll, scrollY |
Frame, Document, Image |
height, width |
<div id="div1">Hello</div>
<div id="div2">World</div>
<div id="div3">!!!!!!!!</div>
<script>
// 获取元素
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2");
const div3 = document.getElementById("div3");
// 执行一系列来纳许的读写操作,修改DOM使得布局失效
const div1Width = div1.clientWidth;
div1.style.width = div1Width/2 + "px";
const div2Width = div2.clientWidth;
div2.style.width = div2Width/2 + "px";
const div3Width = div3.clientWidth;
div3.style.width = div3Width/2 + "px";
// 防抖的一种方法:批量读写
// 批量读取所有布局属性
const div1Width = div1.clientWidth;
const div2Width = div2.clientWidth;
const div3Width = div3.clientWidth;
// 批量写入所有布局属性
div1.style.width = div1Width/2 + "px";
div2.style.width = div2Width/2 + "px";
div3.style.width = div3Width/2 + "px";
</script>
第12章 DOM操作的更多相关文章
- jQuery 第二章 实例方法 DOM操作选择元素相关方法
进一步选择元素相关方法: .get() .eq() .find() .filter() .not() .is() .has() .add()集中操作 .end()回退操作 .get() $(&qu ...
- 第3章 jQuery的DOM操作
一. DOM 分为DOM核心,HTML-DOM和CSS-DOM 1.DOM核心 不专属与javascript. 获取对象:document.getElementsByTagName('div') 获 ...
- 第三章(jQuery中的DOM操作)
3.1 DOM 操作分类 ①DOM Core 包括(getElementById() , getElementsByTagName() , getAttribute() , setAttribute( ...
- 第四章 JavaScript操作DOM对象
第四章 JavaScript操作DOM对象 一.DOM操作 DOM是Document Object Model的缩写,即文档对象模型,是基于文档编程的一套API接口,1988年,W3C发布了第一级 ...
- jQuery系列 第七章 jQuery框架DOM操作
第七章 jQuery框架的选择器 jQuery框架继承和优化了JavaScript访问DOM对象的特性,我们使用jQuery框架提供的api可以更加方便的操作DOM对象. 7.1 创建DOM节点 使用 ...
- JQuery制作网页—— 第三章 JavaScript操作DOM对象
1. DOM:Document Object Model(文档对象模型): DOM操作: ●DOM是Document Object Model的缩 ...
- 第二章 jquery的dom操作
三个方面 dom核心,html-dom和css-dom 一. 1.dom core核心 document.getElementsByTagName("form") 获取表单 ...
- JavaScript交互式网页设计 • 【第7章 jQuery操作 DOM】
全部章节 >>>> 本章目录 7.1 DOM 对象和 jQuery 对象 7.1.1 DOM 对象 7.1.2 jQuery 对象 7.1.3 jQuery 对象和 DOM ...
- javascript dom编程艺术笔记第三章:DOM操作的5个基本方法
JavaScript的 DOM操作,主要是对DOM这三个字母中D.O.M的操作.D代表的是document(文档),即我们可以使用javascript对文档进行操作,O代表的是object(对象),对 ...
随机推荐
- Linq to SQL 语法整理(子查询 & in操作 & join )
子查询 描述:查询订单数超过5的顾客信息 查询句法: var 子查询 = from c in ctx.Customers where (from o in ctx.Orders group o by ...
- 效率神器-uTools推荐和使用
提高办公开发效率...非常好用 功能很多很全,官网:https://u.tools/ 文档:https://u.tools/docs/guide/about-uTools.html
- 传输层-Transport Layer(上):传输层的功能、三次握手与四次握手、最大-最小公平、AIMD加法递增乘法递减
第六章 传输层-Transport Layer(上) 6.1传输层概述 在之前的几章内容中,我们自底向上的描述了计算机网络的各个层次,还描述了一些处于不同层次下的经典网络协议(如以太网.无线局域网.或 ...
- 基数排序(Radix Sort)
基数排序(Radix Sort) 第一趟:个位 收集: 第二趟:十位 第三趟:百位 3元组 基数排序--不是基于"比较"的排序算法 递增就是把收集的过程返过来 算法效率分析 需要r ...
- 八、TestNG忽略测试
一个TestNG 测试类中如果有的方法不想测试可以使用 enabled 属性 enabled = false 该方法不参与测试 enabled = true 该方法参与测试 @Test 不写en ...
- screw一键生成数据库文档
1. 简介 在项目开发和交付阶段,数据库文档是必不可少的.对于大型项目多个数据库几百甚至几千张表来说,手写数据库文档必然是耗时且痛苦的.因此需要一个插件自动生成文档. screw提供了多种文件 ...
- Python实现全自动购买火车票!抢票回家过年咯
本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理这个是实现结果,因为一天只能取消三次,所以最后一步点击确认被我注释了1.首先实现使用selenium登 ...
- JavaScript实现自定义右键菜单
JavaScript实现自定义右键菜单,思路如下: 1. 屏蔽默认右键事件: 2. 隐藏自定义的菜单模块(如div.ul等): 3. 右键点击特定或非特定区域,显示菜单模块: 4. 再次点击,隐藏菜单 ...
- 图解JanusGraph系列 - JanusGraph指标监控报警(Monitoring JanusGraph)
大家好,我是洋仔,JanusGraph图解系列文章,实时更新~ 图数据库文章总目录: 整理所有图相关文章,请移步(超链):图数据库系列-文章总目录 源码分析相关可查看github(码文不易,求个sta ...
- MySQL管理基础
#1.数据库连接管理 mysql命令说明 第一个功能:连接数据库(在前面mysql命令的使用里面讲解了,这里就不讲解了) 第二个功能:mysql客户端自带的命令功能 mysql命令的使用(mysql ...