Skip to content

DOM

Document Object Model

Live DOM Viewer:https://software.hixie.ch/utilities/js/live-dom-viewer/

Node / 节点

节点有 12 种:

js
// https://dom.spec.whatwg.org/#node
interface Node : EventTarget {
  const unsigned short ELEMENT_NODE = 1;
  const unsigned short ATTRIBUTE_NODE = 2;
  const unsigned short TEXT_NODE = 3;
  const unsigned short CDATA_SECTION_NODE = 4;
  const unsigned short ENTITY_REFERENCE_NODE = 5; // legacy
  const unsigned short ENTITY_NODE = 6; // legacy
  const unsigned short PROCESSING_INSTRUCTION_NODE = 7;
  const unsigned short COMMENT_NODE = 8;
  const unsigned short DOCUMENT_NODE = 9;
  const unsigned short DOCUMENT_TYPE_NODE = 10;
  const unsigned short DOCUMENT_FRAGMENT_NODE = 11;
  const unsigned short NOTATION_NODE = 12; // legacy
}

元素节点:可以拥有子项。
文本节点:没有子项。

注释也是一种

空格 和 换行 在 文本节点 中是有效的,只是 body 会忽略掉。

Node 的内建类

tagName 仅适用于 Element 节点
nodeName 任意 Node 都有

nodeValue 和 data 是非 Element 的属性。

js
document.body.nodeName; // 'BODY'
document.body.tagName; // 'BODY'

more:https://html.spec.whatwg.org/#htmlinputelement

html = document.documentElement
head = document.head
body = document.body
document.body 的值可能是 null。因为 浏览器 可能还没有读到它。

childNodes:所有子节点
firstChild:第一个
lastChild:最后一个
hasChildNodes:boolean

NodeList 是可迭代,不是 Array。 NodeList 的元素是可读的,不能修改。

find

document.getElementById
document.querySelectorAll(css) 通过 css 找
document.querySelector(css) 只找一个
element.matches(css) boolean ,是否存在 css
element.closest(css) 通过 css 在父 Node 种找

js
document.querySelectorAll("ul > li:last-child");

常用 dom 属性

innerHTML 的特性是先移除,后重新添加
outerHTML 不会修改 DOM 元素 ,而是删除后插入新的 HTML
textContent 去掉标签的 innerHTML,纯文本
hidden 是否可见

attributes and properties

attributes 是 HTML 的
properties 是 DOM 的

  • elem.hasAttribute(name) —— 检查特性是否存在。
  • elem.getAttribute(name) —— 获取这个特性值。
  • elem.setAttribute(name, value) —— 设置这个特性值。
  • elem.removeAttribute(name) —— 移除这个特性。

data-开头是 attributes 为保留字段,用于 coder 使用

js
elem.getAttribute("data-xxxxxxx");

也可以:

js
elem.dataset.xxxxxxx;

DOM API

node.append 在 node 末尾 插入 节点或字符串
node.prepend 在 node 开头 插入
node.before 在 node 前面 插入
node.after 在 node 后面 插入
node.replaceWith 将 node 替换为给定的 值

这些 API 适用于 node,参数是 string 或者 node

所有插入方法都会自动从旧位置删除该节点。

js
<div id="first">First</div>
<div id="second">Second</div>
<script>
  // 无需调用 remove
  second.after(first); // 获取 #second,并在其后面插入 #first
</script>

如果是 Element 插入 html,则用这个:

js
insertAdjacentHTML(where, html); // 将 HTML 插入
insertAdjacentText(where, txt); // 将 文本 插入
insertAdjacentElement(where, elem); // 将 元素 插入

where 是一个字符串,具体值为:
"beforebegin" —— elem 之前
"afterbegin" —— elem 内第一个
"beforeend" —— elem 内最后一个
"afterend" —— elem 之后

移除节点:node.remove()

elem.cloneNode(true) 深度克隆 elem.cloneNode(false) 包括子元素

DocumentFragment:一种特殊 DOM,用来装 DOM,方面同意插入,类似 <></>

过时的:parentElem.appendChild、parentElem.insertBefore、parentElem.removeChild

document.write:在没有 DOM 时的 API,非常远古了!

css class 和 style

todo

DOM 的自动修正

不正确的 HTML,在形成 DOM 时,会自动更正它。

如:没有 html 和 body 会自动生成

some code

Point 需要回头在复习一下,然后独立出来。

js
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

/**
 * parent 的几个限制,position非static、Table/Td/Th、body
 *
 * a   b
 *  ┏━┓
 *  ┗━┛
 * d   c
 *
 * all 不包括滚动条Width/Height
 */
export const getDOMLoactionInfo = (dom) => {
  if (!dom) return null;

  // 父类
  const parent = dom.offsetParent;

  const width = dom.offsetWidth;
  const height = dom.offsetHeight;

  const a = new Point(dom.offsetLeft, dom.offsetTop);
  const b = new Point(dom.offsetLeft + width, dom.offsetTop);
  const c = new Point(dom.offsetLeft + width, dom.offsetTop + height);
  const d = new Point(dom.offsetLeft, dom.offsetTop + height);

  const result = {
    parent,
    width,
    height,
    point: {
      leftTop: a,
      topRight: b,
      rightBottom: c,
      bottomRight: d,
      a,
      b,
      c,
      d,
    },
    borderAndScrollBar: {
      width: dom.clientLeft,
      height: dom.clientTop,
    },
    contentAndpadding: {
      width: dom.clientWidth,
      height: dom.clientHeight,
    },
    scroll: {
      hasScroll: {
        x: dom.scrollLeft,
        y: dom.scrollTop,
      },
    },
    all: {
      // 不包括 滚动条宽度
      width: dom.scrollWidth,
      height: dom.scrollHeight,
    },
  };

  return result;
};

/**
 * 这个 width/height 是浏览器 网页所占内容的。即不包括浏览器 title 那部分。
 *
 * 当 HTML 中没有 <!DOCTYPE HTML> 时,可能会出现一些稀奇古怪的情况。
 */
export const getDocumentInfo = () => {
  return {
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight,

    all: {
      width: Math.max(
        document.body.scrollWidth,
        document.documentElement.scrollWidth,
        document.body.offsetWidth,
        document.documentElement.offsetWidth,
        document.body.clientWidth,
        document.documentElement.clientWidth
      ),
      height: Math.max(
        document.body.scrollHeight,
        document.documentElement.scrollHeight,
        document.body.offsetHeight,
        document.documentElement.offsetHeight,
        document.body.clientHeight,
        document.documentElement.clientHeight
      ),
    },
  };
};

export const windowScroll = () => {
  // document.documentElement.scrollTop === window.pageYOffset
  return {
    x: window.scrollX,
    y: window.scrollY,
  };
};

export const stopScroll = () => {
  document.body.style.overflow = "hidden";
};

export const regainScroll = () => {
  document.body.style.overflow = "";
};

export const getPointOfWindow = (dom) => {
  const {
    x, // 矩形原点 对于窗口的 X/Y 坐标。可以为负
    y,

    width, // 矩形的 width
    height,
  } = dom.getBoundingClientRect();

  const a = new Point(x, y);
  const b = new Point(x + width, y);
  const c = new Point(x + width, y + height);
  const d = new Point(x, y + height);

  return {
    leftTop: a,
    topRight: b,
    rightBottom: c,
    bottomRight: d,
    a,
    b,
    c,
    d,
    width,
    height,
  };
};

/**
 * 嵌套最多(the most nested)的元素
 *
 * 可能为 Null
 */
export const getDomByWindowPoint = (x, y) => {
  return document.elementFromPoint(x, y);
};