DOM

只有进入DOM的元素才有尺寸, 仅在JavaScript内存里存在的元素是无法获得尺寸的:

const element = document.createElement('div')
element.style.width = '100%'
element.style.height = '100%'
element.offsetWidth // 0
element.offsetHeight // 0
element.getBoundingClientRect()
// {
// bottom: 0,
// height: 0,
// left: 0,
// right: 0,
// top: 0,
// width: 0,
// x: 0,
// y: 0
// }
window.getComputedStyle(element).width // ''
window.getComputedStyle(element).height // ''

元素的布局属性, 它不会反映元素的transform变换后的结果, 元素属性还会将值四舍五入为整数.
如果需要小数值和渲染属性(与布局属性相对), 则应该使用 element.getBoudingClientRect().

offsetWidth和offsetHeight是包括border和scroll在内的元素的可视尺寸值.
它们最接近通常意义上的元素大小.

元素内容的大小, 不会算上border, 会算上padding, 不会算上内容的scroll, 不会算上溢出可视范围的不可见部分.

元素内容的大小, 不会算上border, 会算上padding, 会算上溢出可视范围的不可见部分.
它们最接近内容意义上的元素大小, 比如取整个文档的高度时, 就会使用scrollHeight.

返回元素的各项渲染属性, 会反映transform变换后的结果.
比元素属性精度高, 值可以带有小数.

此方法对元素边界的定义等同于元素属性的offsetWidth和offsetHeight.

该函数获取元素计算后的CSS属性, 通过它获取得到的width和height属性是应用值字符串, 即它们只会是 px 单位的字符串.

即样式表里权重最高的属性值, 该值要么是用户设置, 要么是继承自父元素的, 要么是初始值.

计算值是根据规范计算出来的值, 它会将大多数特殊值和相对值转换为绝对值.
但是, 对于那些需要布局才能确定绝对值的属性, 它仍然无法得出绝对值.

在CSS 2.0时, 计算值就是最终值, 在CSS 2.1中被新增的应用值取代.

应用值是渲染时最终使用的绝对值, 大部分应用值与计算值相同,
只有部分与布局有关的属性不同, 因为这些属性直到此时才确定下来.

在CSS 2.1时, 引入了应用值的概念, 取代了CSS 2.0的计算值作为最终值.

大多数富文本编辑器是使用 contenteditable 属性实现的.
配合(正在从标准中移除的)document.execCommand, 可以实现很多功能.

contenteditable的缺点在于浏览器行为缺乏一致性, 生成的HTML结果大相径庭.

Monaco这样的编辑器会使用一个隐形的textarea处理用户输入,
实时将用户输入反应到渲染上.

async属性表示该脚本独立于页面和其他脚本, 它不受执行顺序的影响, 可以在任何时间点运行.
带有async属性的脚本会与页面解析并行下载, 并在可用时立即运行.

async可以让页面在解析时不被脚本执行阻塞住.

带有defer的脚本会在文档解析完毕之后, DOMContentLoaded事件触发之前运行.
多个带有defer的脚本会按照它们在HTML里的先后顺序依次执行.

defer可以让页面在解析时不被脚本执行阻塞住.

defer不对内联脚本生效, 内联脚本仍然会立即执行.

无论图片载入成功与否, 图片的complete属性都会被设置为true.

浏览器在img和iframe上原生具有一个名为loading的属性, 可以实现懒加载.

Safari是主流浏览器里唯一不支持此属性的.
https://caniuse.com/loading-lazy-attr

https://github.com/aFarkas/lazysizes

user agent会在图片加载失败时显示破碎图片, 这有可能破坏页面的设计.
比如指定了尺寸的图片在载入失败后会于破碎图片上出现边框,
在Chrome(v92)上, 破碎图片的边框有时会无视元素所在容器的尺寸溢出.

object元素的一个鲜为人知的功能是它在无法加载资源时会将它的后代作为fallback:

<object data="avatar.jpg" type="image/jpg">
<img src="default.jpg" />
</object>

该方案的缺陷是object元素不支持响应式图片切换.

参考: https://stackoverflow.com/a/29111371/5462167

如果指定了alt, 会显示替代图片的文本.
一些浏览器在显示替代文本后就不会显示破碎图片, 一些浏览器会将替代文本显示在破碎图片的右侧.
重点在于, 使用空alt时, 通常会引入特殊行为, 既不显示破碎图片, 也不显示替代文本.

对picture元素来说, 此方案可能无效, 可能是因为相关行为没有被明确定义.

可以通过onError事件修改图片的src属性完成.

picture和source元素的组合是img元素srcset和sizes属性的替代品.

浏览器会按顺序从上到下选取最适合的图片资源, 因此条件最苛刻的项目应该排在最前面.
最后一个img元素将作为实际使用的img元素, 如果需要给图片设置alt属性, 则应该设置在此元素上.

示例:

<picture>
<source srcset="image.avif" type="image/avif" />
<source srcset="image.webp" type="image/webp" />
<img src="image.jpg" />
</picture>

picture和source元素都没有error事件, 在资源出错时仍然需要从img元素上捕获error事件.

picture元素内的source和img只用于为浏览器选取最合适的资源,
当资源无法访问时, 并不会由下一个项目替代无法访问的资源.

由于无法手动控制picture选择将哪个资源选为实际使用的资源,
因此当需要处理资源访问错误时, 也无法像直接使用img那样给src属性重新设置一个替代图像URL就解决问题,
而是要先找到导致失败的那个子元素, 然后修改子元素的属性.

当前使用的资源URL可以通过 img.currentSrc 得到, 该属性是一个只读属性, 值是绝对URL,
因此未必与元素上设置的attribute值相同.
加上srcset可以包含查询条件, 通过currentSrc匹配出错的元素会很麻烦, 因此不建议使用此方案.

这是推荐的解决方案, 在出错后移除img以外的所有source元素, 将img作为最终fallback使用.

使用此资源时需要满足的媒体查询条件.

示例:
(max-height: 500px)

资源的URL.

与img元素的srcset属性类似, 是由逗号分隔的一组规则, 但一般只是被用来替代src的功能.

如果srcset属性里包含规则, 则浏览器会把规则视作一种媒体查询条件.
然而, 无论是宽度描述符还是像素密度描述符, 都可以被media属性的媒体查询替代, 没有必要在srcset里使用规则.

定义元素的mime类型.

该属性是由逗号分隔的一组规则, 定义浏览器可以选择的资源.
在没有sizes属性的情况下, 浏览器会自己根据显示设备选择相应的资源, 例如Retina屏幕会选择2x的资源.

规则的语法:
<url> 省略描述符的资源, 该定义相当于 <url> 1x.
<url> <width>w 带有宽度描述符的资源, width是一个正整数.
<url> <pixel density>x 带有像素密度描述符的资源, pixel density是一个正浮点数.

像素密度指的是1个CSS像素代表几个物理像素, 1x即为1:1, 2x即为1:2, 以此类推.
1个CSS像素固定为1英寸的96分之1, 代表96ppi的屏幕.
像素密度为2x时, 屏幕为192ppi.
像素密度为3x时, 屏幕为288ppi.
像素密度通常最高只用到4x, 平板电脑和显示器通常在1~2x之间, 超过2x的像素密度一般只出现在手机上.
由于手机是移动设备, 提供更高分辨率的图片意味着需要更多资源, 实际上没有提供高于2x图片的强烈需求.

当像素密度并非是一个整数时.

可以通过 window.devicePixelRatio 得到浏览器当前的像素密度值.

该属性是由逗号分隔的一组规则, 用于表示在特定viewport下最佳的资源宽度,
浏览器会从srcset属性里选择对应宽度的资源.
为了使用sizes属性, srcset的必须以 w 描述符来定义宽度, 否则该属性会失效.

规则的语法:
<media condition> <size>
最后一条规则可以作为缺省值, 此时允许省略media condition部分.

示例:
(max-height: 500px) 1000px 如果viewport的高度低于500px, 则从srcset里挑选1000px的资源.

如果该srcset属性不存在,或不包含具有宽度描述符的值,则该sizes属性无效。

如果图片不是jpeg, 则应该提供jpeg版本的图片, 因为这是支持最广泛的有损压缩格式.

如果图片不是webp, 则应该提供webp版本的图片, 它比jpeg的压缩比高, 并且浏览器支持已经相当好.

avif是一个受支持更少, 编码速度是离线级, 压缩率在实际场景里不如webp, 并且不支持渐进加载的图片格式.
看不到任何必要提供avif格式的图片的必要.

当使用sharp(libvips)时, 发现avif的编码速度远逊于jpeg和webp.
在我的测试中, avif需要花费webp 20倍的时间才能达到webp的质量.

在我的测试中, avif文件在体积上更靠近jpeg而不是webp.

在移动设备上, 这些原生input元素可以提供比自定义组件更好的用户体验:

  • date(专门的输入控件)
  • time(专门的输入控件)
  • datetime-local(专门的输入控件, Firefox有bug所以不支持)
  • month(专门的输入控件)
  • week(专门的输入控件)
  • tel(专门的键盘)
  • number(专门的键盘)
  • password(会出现密码管理器的提示)
  • color(专门的输入控件)
  • innerText
    获取基于布局的内容文本.
    该属性忠于以文本在网页上的排版效果返回文本(因此性能较差). 其结果类似于选中文本后复制得到的文本.
    由IE最先引入, 标准化较晚.
    由于实现该属性需要引入布局支持, JSDOM未正确实现该属性.

当从上一级节点获取innerText时, 像`<script>`和`<style>`这样的子元素内的文本内容不会被包含.

  • textContent
    获取所有文本节点的串联文本, 这意味着空白符将与源代码一致.
    该属性会忽略文本在网页上的显示效果, 忽略`<br>`, `<p>`等元素对排版的影响.

当从上一级节点获取textContent时, 像`<script>`和`<style>`这样的子元素内的文本内容也会被包含.

HTMLElement是Element的子类.

HTMLElement显式表示自己是HTML元素, Element则适用于所有类型的元素, 例如XML元素(最常见的例子是内嵌于HTML的svg).

一些属性只存在于HTML元素, 例如className, dataset.

参考: https://www.w3.org/TR/DOM-Level-3-Events/#event-flow

DOM事件流分为三个阶段, 按顺序触发相应的事件监听器:

  1. 1.
    捕获阶段(capture phase)
    从最外层的父元素开始发出事件, 层层递进直至目标(真正发出事件的元素).
    在捕获阶段可以先于发出事件的元素(目标)进行操作,
    需要在注册事件监听器时设置 useCapture{ capture: true } (默认为false).
  2. 2.
    目标阶段(target phase)
    调用目标元素直接相关的事件监听器, 这是 DOM 事件流里最不反常识的部分.
    在该阶段可以调用 stopPropagation() 阻止事件冒泡.
  3. 3.
    冒泡阶段(bubble phase)
    从目标元素向外开始发出事件, 层层递进直至最外层的父元素.
    如果目标阶段的元素阻止事件冒泡, 则该阶段不会发生.

之所以同时存在捕获阶段和冒泡阶段, 是因为早期浏览器的事件传播没有标准化: Netscape使用捕获, IE使用冒泡.

如果 { capture: true } 被设置在发出事件的元素监听器上,
则该监听器与一般的监听器具备相同行为, 且只根据注册监听器的先后顺序决定调用顺序.

该属性用于检查事件是否允许冒泡, 其值为 false 时会跳过事件的冒泡阶段(buble phase)

该元素指当前事件监听器绑定的元素.
该属性只在事件处理阶段可用, 如果在事件处理阶段以后查看, 属性值将变成 null.

在目标阶段与 target 相同.
在捕获阶段和冒泡阶段与 target 不同.

该属性指真正发出事件的元素.

此外还存在相同功能的属性 Event.srcElementEvent.toElement,
这是出于对兼容性考虑而存在的属性, 不推荐使用.

该方法用于停止事件传播, 即彻底切断之后的事件传播(与所处的阶段无关).
但对于 currentTarget 上存在相同事件监听器的情况, 这些事件监听器仍能获取到此事件,
为阻止此行为, 诞生了 stopImmediatePropagation().

该方法不会阻止浏览器的默认行为, 例如点击链接仍将跳转到新的网页.

该方法用于停止事件传播, 与 stopPropagation() 的不同之处在于,
该方法还会停止传播给在=currentTarget=上的其他事件监听器(即该元素的同一事件有多个监听器存在).

该方法用于阻止浏览器的默认行为
(例如阻止勾选复选框, 阻止跳转到其他网页, 阻止在输入框里输入特定字符), 该方法不会停止事件的传播.

如果一个事件的默认行为是不可取消的({ cancelable: false }), 则该方法没有效果.

鼠标双击的事件.

关于dblclick有一个臭名昭著的问题, 即连续点击不会被识别为双击.
这使得它变得相当不可靠, 开发者们宁可用click事件的 e.detail 属性来处理此需求.

与MouseEnter/MouseLeave相似, 区别在于:

  • 事件会冒泡, 因此父元素会接收到子元素的MouseOver和MouseOut事件.
  • 鼠标指针移动到子元素上时, 会触发MouseOut.

onMouseOver和onMouseOut事件有relatedTarget属性,
分别指代鼠标指针移进之前的元素和鼠标指针移除之后的元素.

当鼠标从窗口外移入或移出窗口时, 该属性的值可能为null.

快速的鼠标移动有可能不触发MouseOver事件.
没有触发MouseOver, 就不会触发MouseOut.

可以保证的是, 如果触发了MouseOver事件, 则一定会触发相应的MouseOut.

与MouseOver和MouseOut相似, 区别在于:

  • 事件不会冒泡.
  • 鼠标指针移动到子元素上时, 不会触发MouseLeave.

pointer系列事件是替代部分mouse和touch系列事件的新标准.
https://javascript.info/pointer-events

该方法在PointerDown事件里调用,
允许后续的PointerMove事件在指针/手指脱离当前操作元素的情况下继续触发,
直到出现PointerUp/PointerCancel事件为止.

  1. 1.
    keypress事件接收到的已废弃属性 KeyboardEvent.keyCodeKeyboardEvent.which 与keyup和keydown不同.
  2. 2.
    keypress不会单独响应修饰键, 例如单独按下弹起 Ctrl 不会触发事件.

img + p
该组合器选择同级别紧跟img的元素p.

img ~ p
该组合器选择同级别img之后的元素p.

p:nth-of-type(4n)
该伪类选中同级别序数为4的倍数的p元素.

其他形式:
p:nth-of-type(1)
p:nth-of-type(4n+1)
p:nth-of-type(even)
p:nth-of-type(odd)

:nth-of-type 出现的原因是为了弥补 :nth-child 在功能上的不足:

  • :nth-child 是按所有同级元素的绝对索引值选择元素的,
    在选择元素后才会检查元素是否与其他选择器相匹配.
  • :nth-of-type 是先基于元素类型获取元素列表, 然后才开始按顺序匹配,
    需要的元素类型以外的元素不会被被列入这个列表里, 因此每一项索引值都对应一个此类型的元素.

该伪类选择没有任何兄弟元素的元素.

p:only-of-type
该伪类选择同级别, 没有其他相同类型元素的元素.

该伪类选择同级别, 此类型元素中的第一个元素.

该伪类选择同级别, 此类型元素中的最后一个元素.

该伪类的作用是选择URL hash对应id的元素.

因此, 当URL为 https://example.com/index.html#section1 时,
该伪类指代 <section id="section1">Example</section>

此伪类选择没有子元素的元素.

伪元素使用两个冒号 ::, 例如 ::before::after.
旧的伪元素规范使用单个冒号 :, 为保持兼容性, 大多数浏览器仍支持旧伪元素的单冒号语法.

::before
::after
li::marker
::selection

a[title] /* 存在此属性 */
a[title="title"] /* 属性值等于 */
a[title="title" i] /* 属性值等于(忽略大小写) */
a[title*="title"] /* 属性值包含 */
a[title^="title"] /* 属性值头部等于 */
a[title$="title"] /* 属性值尾部等于 */
a[title~="title"] /* 属性值是以单词的形式包含(像class那样) */

nodename 此节点的所有名为nodename的子节点

/li 所有li子节点

//li 所有li后代节点

//title[@lang='eng']
//li[@class="item"] 此匹配只对class属性完全等于item的元素有效
//li[contains(@class, "item")] 此匹配对任何包含item的class属性有效
//li[contains(@class, "item") and @name="item"] 匹配多个属性
//li[starts-with(@class, "item")] 匹配以特定字符串开头的属性

//li/a/@href

/text()
//h2[text()="Title"]

元素序号从1开始.

//li[1]/a/text
//li[last()]/a/text
//li[position()<3]/a/text() 前两个
//li[last()-2]/a/text() 倒数第三个

//li[1]/ancestor::* 返回所有祖先
//li[1]/ancestor::div 返回所有div祖先
//li[1]/attribute::* 返回所有属性值
//li[1]/child::a[@href="link.html"] 所有符合href属性条件的a子节点
//li[1]/descendant::span 所有span后代节点
//li[1]/following::*[2] 当前节点之后(同文档下)的第二个节点
//li[1]/following-sibling::* 当前节点之后的所有同级(兄弟)节点

运算符 描述 例子
or
and
mod 取余
管道符号
计算节点集 //book 管道符号 //cd
+ 加法
- 减法
* 乘法
div 除法
= 等于
!= 不等于
< 小于
<= 小于或等于
> 大于
>= 大于或等于

HTML5虽然通过带有语义的标签提升了网页的可访问性, 但实际上网页的可访问性仍是一团糟.

可访问性问题的根源来自于以下几点:

  • 网页提供的用户界面(元素)是碎片化的, 自制组件在网页开发过程中很普遍.
  • 即使网页像Windows程序那样有着标准的系统控件, 最终也会因为不满足实际需求而被自定义控件替代.
  • 可访问性工具市场未能达成统一意见, 也没有跟上最新的推荐标准.
  • 开发人员在考虑功能实现的同时考虑可访问性是巨大的认知负担.
  • 在相当多的国家缺乏保证可访问性的法律法规, 而大部分业务并不考虑残障人士市场, 因此缺乏驱动力.

由于可访问性的优先级总是会低于实际需求, 即使开发人员非常专注于提升网页的可访问性, 也经常会被新的变化破坏.

虽然像MDN这样的网站会告诉你可访问性不是"全有或是全无",
但在缺乏专职人员的情况下, 检验可访问性很可能要求开发人员遍历W3C规范和全部的HTML, 因此会成为一项繁重的任务.
如果机器能够自动检查语义, 那么就不需要开发人员添加标签了, 因此难以指望通过工具减少负担.

W3C的一种规范, 用于改善可访问性问题.

W3C提供的ARIA使用指导: https://w3c.github.io/using-aria/

理论上, 在HTML5里已经存在的语义元素不应该被设置aria属性.

驱动人们使用WAI-ARIA的原因可能是因为一些组件测试库强制要求使用aria属性作为选择器.
造成这一现象的根源在于, 随着CSS in JS和原子化CSS的流行, 在测试中使用CSS选择器定位元素的做法正变得越来越脆弱.

W3C提供的role列表: https://www.w3.org/TR/wai-aria-1.1/#role_definitions
MDN提供的role文档: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques#roles

语义化元素承担的角色, 使用 role 属性设置, 在一些网站上已经彻底取代传统的css类名
(现代网页的类名经常是随机值).

aria先于HTML5出现, 并且部分成为HTML5的元素.
由于其多样性, 不难发现可以在很大程度上取代HTML5的语义元素,
规范要求开发人员优先使用现有的语义HTML元素, 而不是提供aria.
如果与现成元素出现语义上的冲突, 则 不得改变现有语义HTML元素, 而是建议用div替代.

这是一个导航条, 通常设置在nav元素上.

这是一个搜索表单, 通常设置在form元素上.

这是一个文章单元, 可能在页面中有多个这样的单元, 通常设置在article元素上.

一些补充内容, 可能设置给aside元素.

这是一项警告.

这是一个进度条.

语义化元素的属性.
个人不推荐深入相关属性, 因为这些属性的理想化成分过多, 对于实际使用这些可访问性标签的工具来说可能根本没有意义.

该元素的最小值.

该元素的最大值.

表示这是一项动态更新的内容(通常由JavaScript导致), 有几种不同的值.

默认值, 内容更新不应该告知用户.

当用户空闲时, 告知用户内容更新.

应该尽快告知用户内容更新.

通常与aria-iive一同使用.
aria-atomic="true" 表示元素是一个原子, 屏幕阅读器应该读出整个元素, 而不仅仅是更新的内容.

功能上类似于aria-atomic.

表示此项必填.

aria-required="true"

表示此项被禁用.

aria-disabled="true"

input元素专用的属性, 在没有label元素的情况下可以说明input元素的语义.

语义化元素的当前状态.

提升键盘可访问性, 用于改变元素的焦点顺序.

tabindex="0" 让通常不可变为焦点的元素支持变为焦点.