DOM & BOM – 大杂烩
Document.scrollingElement
参考:
why to use 'html, body' for scrollTop instead of just 'html'
使用document.scrollingElement控制窗体滚动高度
要监听 root scrollbar 的 scroll event,使用
document.addEventListener('scroll', () => { // do something... }, { passive: true, capture: true });
window 也可以,不过通常是用 document。
其它的 document.documentElement,document.body 都不对。
如果要拿 root scrollbar 的 scrollTop,不是用 document.scrollTop 哦。
而是
(document.scrollingElement ?? document.documentElement).scrollTop
Mobile scroll hide address bar & scroll event
手机 scroll 的时候会自动把 address bar 隐藏起来. 但有一个前提条件.
那就是需要用 body / document scroll
如果套了一层 container, 并让 container 负责 scroll, 那么这个 address bar 就不会隐藏了.
如果设置 html or body scroll 的效果和没有设置其实是一样的. address bar 依然会隐藏
顺便说一下 scroll event
document.addEventListener("scroll", (e) => { console.log("document", e.target); }); document.documentElement.addEventListener("scroll", (e) => { console.log("documentElement", e.target); }); document.body.addEventListener("scroll", (e) => { console.log("body", e.target); }); console.log(document.scrollingElement === document.documentElement); // true
如果 .container 负责 scroll, 那么没有 scroll event 会触发. 只有 .container 元素监听 scroll 才会触发.
如果 body 设置或没有设置 scroll, 那么在 chrome 游览器, document 触发 scroll event.
同时 scrollingElement 是 document.documentElemenet
获取 transform translate
参考: Stack Overflow – How to get value translateX by javascript
CSS Style
transform: translateX(-10%) translateY(-55px);
JavaScript
export function getTranslate(element: Element): { translateX: number; translateY: number } { const currentTransform = window.getComputedStyle(element).transform; const matrix = new WebKitCSSMatrix(currentTransform); return { translateX: matrix.m41, translateY: matrix.m42, }; }
先通过 computedStyle 拿到 matrix, 然后用 CSSMatrix parse. m41, m42 分别是 translateX, Y. 还有其它属性比如 translateZ, scale, rotate 等.
value 是计算后的 number.
注意: 如果使用了 CSS variables 就拿不到了. 结果总是返回 zero.
--translate-x: calc(var(--spacing-4) * -1);
--translate-y: calc(var(--spacing-4) * -1);
transform: translateX(var(--translate-x)) translateY(var(--translate-y));
通常, 如果想用 JS 控制 transform. 用 CSS Variables 是不错的方式. 参考: How to Use CSS Variables for Animation
还有一个比较 modern 的方法是不要使用 transform, 而是直接写 scale, translate, rotate 等 CSS 属性 (但目前支持度还比较低)
keyCode 废弃, 请用 key
已经废弃很多年了, 之前 Angular 为了支持 IE 很多地方还是用 keyCode, 害得我老是混乱...
参考:
MDN – KeyboardEvent.keyCode Deprecated
现在改成使用 event.key 和 event.code。
.key 和 .code 是有区别的哦
第一个是 .key,第二个是 .code。
两者最大的区别是 code 是 unique 的,而 key 不是。
比如,回车键会有两个不同的 code -- "Enter" 和 "NumpadEnter"。
但回车键的 key 只有一个 "Enter"。
再比如 += 号
如果没有按 shift 点击这个键的话
key 是返回值是 "="
有按住 shift 的话,返回值是 "+"
但 code 就不一样,不管有没有按 shift,它都返回 "Equal",我们需要用 event.shiftKey 另外判断是否有按住 shift。
总结:key 是表达的意思,code 是按键的位置,如果分清楚 user 按什么 "键",那就用 code,如果你只在乎 user 按键后想表达的 "意" 那就用 key。
举几个例子
1. 想知道 user 是不是想表达 "加",那么就用 e.key === '+',如果想知道 user 是不是按了 "Shift" 键 + "NumpadAdd" 键 (这两个加起来就是 "加" 的意思),就用 e.shiftKey && e.code === 'NumpadAdd'。
另外,在 dispatch KeyboardEvent 时,最好三个值都给,因为我们不知道监听的人会用哪一个做判断。
const keyboardEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'NumpadEnter', keyCode: 13, }); input.dispatchEvent(keyboardEvent)
Attribute 和 Property 的区别
参考: Stack Overflow – What is the difference between properties and attributes in HTML?
attribute 是声明在 element 上的. 游览器创建 element 对象后会把 attribute 的值过户到 property 里面.
它不总是一一对应的, 而且中间还可能会做点手脚, 比如 input type="abc" 最终 property type 会是 text. 因为 abc 不是合法的 type.
另外它们不总是同步的. 比如 input.id = 'new', 那么 attribute 也会变成 new. 但是 input.value = 'new', attribute 并不会变成 new.
所以要小心使用. 通常我们只用到 property. attribute 比较少会用到的.
Attribute value
三个点
1. value 只能是 string (e.g. '0', 'true' 都是 string)
<div class="target" abc="0" def="true"></div> console.log(target.getAttribute("abc") === "0"); // true console.log(target.getAttribute("def") === "true"); // true
2. 没有写等于, 表示 = empty string, 下面是等价的
<div class="target" abc></div> <div class="target" abc=""></div>
3. 自定义的 attribute 不会映射到 property
console.log((target as any).abc); // undefined
4.没有 attribute 的情况下 element.getAttribute('abc') 返回 null.
Dataset
当我们想把资料记入到 element 上时就可以使用 dataset.
它和 custom attribute 差不多玩法, 只是 prefix 统一 data- 这样就不容易撞, 然后游览器会帮忙影射到 property dataset 里头
<div class="target" data-my-abc="0"></div> console.log(target.hasAttribute("data-my-abc")); // true console.log(target.getAttribute("data-my-abc") === "0"); // true console.log(target.dataset.myAbc === "0"); // true
四点
1. 和 attribute 一样 value 只能是 string
2. dataset.myAbc 用的是 camelCase. 所以它是从 kebab-case 转成 camelCase 哦.
3. dataset.myAbc 如果没有 attribute 的话, 会返回 undefined (和 getAttribute 不同哦, 后者是返回 null)
4. set property dataset.myAbc = 'new value' 会同步到 attrbute 哦.
tabindex
div by default 是不可以被 focus 的.
button by default 就可以被 focus
要想 focus div, 我们可以写 tabindex = -1
这个表示可以 focus (mouse / program) 但是不可以 tab (keyboard)
如果要可以 tab 那么是 tabindex = 0
tabindex 的号码可以用来表示 focusable 和 tabable
还有顺序功能. 游览器会从 1 开始 tab to next 然后越来越大, 数字重复就用 parent -> child -> sibling 的顺序 (所以对于一个区块只要分配一个值就可以了) . 最后才去 tabindex 0.
所以修改后的顺序就是
1,1,2,2,2,2,3,4,5,0,0,0.... 这种
display none 和 visibility: hidden 是不可以 focus 的. 然后和 element 被 remove 一样 focus 也是会跳去 body
当从 a element focus to b element 的时候, a 的 blur 会触发, 而这个时候 document.activeElement 是 body 哦.
document.click() 这种模拟 click 的话, 是不会 focus 的哦, 可以再调用 .focus() 就可以了
Focus & Blur
-
focus 和 blur 事件是不会冒泡的。
要冒泡可以改用 focusin 和 focusout。或者用 focus + capture 提前捕获做处理。
-
auto focus body
当 focused 的 element 被 remove from DOM,focus 会自动跳去 focus body。
需要注意两点:
a. 游览器是有记忆的,如果此时按 tab,游览器会 focus 到刚才 removed element 的附近,而不是从头开始哦。
b. remove element 导致的 auto focus body 不会触发 focus 事件,blur 会触发,还有 event.relatedTarget 会是 null,document.activeElement 会是 body。(注:不管 body tabindex 多少,都是这样)
-
触发顺序
点击一个 button
mousedown (body) -> focus (button) -> focusin -> mouseup -> click
(body) 表示 document.activeElement 是 body
再点击另一个 button
mousedown (button1) -> blur (body) -> focusout (body) -> focus (button2) -> focusin -> mouseup -> clickkeyboard tab 的顺序也是一样的,把上面的 mousedown 换成 keydown 就可以了。
-
event.relatedTarget
监听 focus,event.relatedTarget 可以让我们知道它从哪里 blur 来的。
监听 blur,event.relatedTarget 可以让我们知道它要 focus 去哪里。
提醒:如果 element 被 remove 自动 blur to body 那 relatedTarget 会是 null,如果手动 blur to body (body 有 tabindex), 那 relatedTarget 会是 body。
HTML 常用 encode
online convert tool: HTML Encode Online
用来实现 multiple space.
new line 参考: Stack Overflow – Insert line break inside placeholder attribute of a textarea?
" double quote "
关于 click, mouse, touch, pointer event
mouse
mouse 是针对滑鼠的 event
mousedown 点下去时触发
mouseup 点下去后放开时触发
mousemove 移动时触发(不管你有没有点下去,只要移动 mouse 就触发了)
touch
touch 是针对触屏的 event
touchstart 和 mousedown 类似,按下去时触发
touchend 和 mouseend 类似,放开时触发
touchmove 和 mousemove 类似,但是它一定是 touching 的时候触发的。(废话触屏一定是要触摸到先丫。。哈哈)
touchcancel 当 touch 出现问题时触发,比如太多手指了,屏幕不支持那么多,于是就会有一些被 cancel 掉。
click
click 在滑鼠和触屏都会触发
mousedown > mouseup > click
touchstart > touchend > click
以前触屏 click 会有一个 delay 300ms,但现在已经没有了。
pointer
mouse event 在触屏是不会触发的。
touch event 在滑鼠也是不会触发的。
但假如我们要支持多种设备,那么就需要监听 mouse 和 touch 两个,这样就很麻烦。
于是就有了 pointer。
pointer 事件在滑鼠和触屏都会触发。
比如 pointermove 在滑鼠等价于 mousemove,在触屏等价于 touchmove。
注意: 游览器的默认行为经常会影响到 pointermove。但不会影响到 touchmove。具体原因我懒的研究了。
pointermove 触发几下就会被中断。
想要掌控多一点的话,除了可以用 event.preventDefault() 之外,也可以在 html, body 加上 touch-action 属性。
body { // touch-action: none; touch-action: pinch-zoom pan-y pan-x; }
none 表示,关闭所有游览器 touch 事件,完全由我们来操作。
pinch-zoom pan-y 指的是只允许游览器处理 pinch-zoom 和 pan-y 事件,其余事件都不要处理。(pinch-zoom 是放大,pan-y 是 scroll)
关于 hover 事件
JavaScript 没有 hover 事件,只有 mouseenter, mouseleave 或者 mouseover, mouseout。
mouseenter, mouseleave 对比 mouseover, mouseout
它们有点类似 focus 和 blur 对比 focusin 和 focusout 的关系。
mouseover, mouseout 有冒泡概念,监听父节点,当子节触发时也会收到。
mouseenter,mouseleave 没有冒泡概念,与子节点无关。
mousemove
mousemove 则一定有冒泡概念,没有没有冒泡的方法。
event.relatedTarget
relatedTarget 可以让我们知道 mouseenter 之前和 mouseleave 之后目标元素是什么。
比如从 divA mouseleave,然后 mouseenter 到 divB,在 divA mouseleave 时 event.relatedTarget 就是 divB,
同样的,在 divB mouseenter 时 event.relatedTarget 就是 divA。