DOM & BOM – 冷知识 (新手)
JS 无法 query select 到伪元素
参考: 使用JS控制伪元素的几种方法
JS style remove property 是 kebab-case
set property 是 camelCase
navList.style.maxHeight = `${window.innerHeight - navHeight}px`;
navList.style.overflowY = 'auto';
remove property 是 kebab-case
navList.style.removeProperty('max-height');
navList.style.removeProperty('overflow-y');
camelCase 是 remove 不掉的哦, 小心.
JS getComputedStyle property camelCase and kebab-case
window.getComputedStyle(div).getPropertyValue('padding-top')
window.getComputedStyle(div).paddingTop
注意哦, 一个是 kebab-case 一个是 camelCase.
当 getComputedStyle 遇上 inline 返回值会是 auto
通常发生在想获取 span 的 width 的时候, 可以改拿 element.offsetWidth, 或者将 element display 改成 inline-block.
querySelector Child Layer Only
参考: Stack Overflow – Using querySelectorAll to retrieve direct children
swiperContainer.querySelector<HTMLElement>('> .swiper')!;
直接写 > 是不行的. 要加一个 :scrope, 支持度还不错, 只有 ie 不支持.
const swiper = swiperContainer.querySelector<HTMLElement>(':scope > .swiper')!;
这样就可以了.
Custom Event 默认是不冒泡的
dispatch 的时候要开启, 默认是不冒泡的哦
ellipsis.dispatchEvent(new CustomEvent('ellipsisopen', { bubbles: true }));
有些 event 默认是不冒泡的
比如 focus、blur。
HTMLScriptElement text vs textContent
参考: MDN – HTMLScriptElement, Node.textContent
set 的时候, 它的表现和 textContent 是一摸一样的.
get 的时候, textContent 会把子孙的 text node 也拿出来, 但是 text 则不会. 当然 script 里面是不应该出现子孙 element 的丫.
测试代码


const script = document.createElement("script");
const text = document.createTextNode(`console.log('Hello World');`);
script.appendChild(text);
const comment = document.createComment("my comment");
script.appendChild(comment);
const div = document.createElement("div");
div.appendChild(document.createTextNode(`Hello World`));
script.appendChild(div);
console.log(script.text); // console.log('Hello World');
console.log(script.textContent); // console.log('Hello World');Hello World
document.head.appendChild(script);
body.offsetWidth 不包含 vertical scrollbar width
一般的 div.offsetWidth 是包含 scrollbar width 的,div.clientWidth 则不包含。
但 body 和 html 很奇怪,它的 offsetWidth 是不包含 scrollbar width 的。
比如我的屏幕 1920px,当 scrollbar 出现后,offsetWidth 变成了 1903px。
所以呢,如果想要获取 body scrollbar width 可以用另一招:
拿 window.innerWidth 减掉 body.offsetWidth。
window.innerWidth 是 viewport width 包含了 scrollbar width,而 body.offsetWidth 则没有包含 scrollbar width。
提醒:body.offsetHeight 是有包含 horizontal scrollbar height 的哦,想获取 viewport height without scrollbar 通常是用 document.documentElement.clientHeight。
Input Date
原生 input type="date" 的 value format 是 yyyy-mm-dd. 其它不支持哦.
比如 input.value = '20-01-2023' 会完全被无视掉. 参考这篇
Browser Auto-fill Password
很多 browser 都有账号密码管理功能. 当网站有 input password 的时候, browser 会自动帮用户填写.
为了安全, browser 不会让 JS 获取到 autofill 的内容, autofill 时也不会触发任何 input event.
一直到用户 first interact (e.g. click, focus) 之后, JS 才可以获取到 value.
JS 获取不到 value 有时候会破坏体验, 比如 floating label.
这时我们需要一些小技巧来解决问题.
CSS selector :-webkit-autofill 或者 :autofill 可以查找出被 autofill 的 input.
虽然我们依然拿不到 value, 但至少是可以把 label 做一个 floating 了.
Keydown event keep fire
当 user 长按一个键时,keydown 和 keypress event 会一直触发。
我们可以通过 e.repeat 判断 event 是第一下 fire,还是后续的连续 fire.
window.addEventListener('keydown', e => {
console.log(e.repeat);
});
input 事件 の keydown, keypress, compositionstart, input compositionend, keyup, change
input 事件顺序和细节:
keydown
event.key 可以获知用户按了哪一个键。
keypress
keypress 只有在按字符键才会触发,如果用户按 Shift, Alt 或 Ctrl 这些键是不会触发的。
长按一个键,keydown 和 keypress 会连续触发,通过 event.repeat 可以知道是第一次触发还是连续触发。
如果用户开启中午输入法,它按键时 compositionstart 会触发,如下图
input 事件只有在 input value changes 时触发。
Shift, Alt 或 Ctrl 都不会触发。
但是它和 keypress 是有区别的哦,比如 backspace 键 keypress 是不触发的,因为它不是字符,
但 input 是可能会触发的,比如 'abc' backspace 变成 'ab' 那 input 会触发。
不过如果 input 是 empty string backspace 后任然是 empty string,那就不会触发了。
总之,keypress 依据键是不是字符,input 依据 value 有没有变更。
另外,keydown, keypress, compositionstart 触发时,input.value 还没有新值,直到 input 事件 input.value 才有新值。
compositionend 在关闭中午输入法时触发。
此时,如果我按下 1 号键,首先会触发 keydown,event.key 是 'Process'
接着触发 input 事件,再来是 compositionend 事件
keyup 在手指离开按键时触发。
在 input blur 以后如果 value 有变更那会触发 change 事件。
:tel, :mailto 不需要 target="_blank"
:tel 和 :mailto 是直接开启 App,所以不需要 new tab。
如果是 link to WhatsApp 和 Google Map 就需要 target="_blank",因为它是开启游览器 new tab 然后才 trigger 开启 App。
remove DOM === remove event listener?
body 里有一个 button
<body>
<button>click</button>
</body>
添加一个点击事件给 button
const button = document.querySelector('button')!;
button.addEventListener('click', () => console.log('hello world'));
1 秒后把 button 从 body 移除
window.setTimeout(() => {
document.body.removeChild(button);
}, 1000);
2 秒后 dispatch event
window.setTimeout(() => {
button.dispatchEvent(new Event('click'));
}, 2000);
请问:会 log 'hello world' 吗?
答案是:会!
click event target not same as mousedown or mouseup
有一个 div,里面有一个 input
<div class="search-box">
<input>
</div>
监听 div 和 input 的 mousedown, mouseup, click 事件
const searchBox = document.querySelector<HTMLElement>('.search-box')!;
const input = document.querySelector('input')!; input.addEventListener('click', event => {
console.log('input click', event.target);
});
input.addEventListener('mousedown', event => {
console.log('input mousedown', event.target);
});
input.addEventListener('mouseup', event => {
console.log('input mouseup', event.target);
}); searchBox.addEventListener('click', event => {
console.log('div click', event.target);
});
searchBox.addEventListener('mousedown', event => {
console.log('div mousedown', event.target);
});
searchBox.addEventListener('mouseup', event => {
console.log('div mouseup', event.target);
});
点击 input 的效果
input 先触发,然后冒泡到 div,这个很好理解。
点击 div 的效果
只有 div 触发,这个也很好理解。
长点击 input 然后移动到 div 才放开。
效果
注意看,mouseup 和 click 的 target 是 div。
长点击 div 然后移动到 div 才放开。
效果
注意看,mouseup target 是 input,但是 click 却是 div 哦。
上面这 2 个移动的例子 mousedown 和 mouseup 虽然不同,但至少是上下层关系,如果是 sibling 那 click event 将完全不会触发。
总结:
mousedown 和 mouseup 好理解,你鼠标在哪里 target 就是那个。
click 不太好理解
mousedown 和 mouseup 哪一个比较在高层,哪一个就是 click 的 target。
比如上面例子
input -> div,target = div
div -> input, target 还是 div,因为 div 比 input 高层
如果 mousedown 和 mouseup 不是上下层 (比如它们是 sibling),那 click event 将完全不会触发。
switch browser tab will trigger blur & focus event
一开始 focus input, blur body 正常。
接着 focus input,然后 switch browser tab。
此时会触发 blur,document.activeElement 依然是 input。
然后 switch 回来时会 trigger focus,document.activeElement 依然是 input。
分开监听事件,触发时机会不同
我们分 2 次监听 button click 事件
document.querySelector('button')!.addEventListener('click', () => {
console.log('first click start'); // 1
queueMicrotask(() => {
console.log('first click end'); // 2
});
requestAnimationFrame(() => console.log('first click animation')); // 5
}); document.querySelector('button')!.addEventListener('click', () => {
console.log('second click start'); // 3
queueMicrotask(() => {
console.log('second click end'); // 4
});
});
虽然在 callback 函数里,我们写了 queueMicrotask 延迟 console end,但是第二个 button click 事件依然后于第一个的 console end。
也就是说,虽然用户是同一时间点击,但分开监听事件的触发不是同步的,而是有间隔的。
不过 requestAnimationFrame 依然是最后执行的。
getComputedStyle transform 会得到 Matrix
参考:
Stack Overflow – How to get value translateX by javascript
Stack Overflow – Get the value of -webkit-transform of an element with jquery
有一个 CSS transform,里面有 translate,我们想用 JavaScript DOM API 拿到最终的 translateX 和 translateY 值。
transform: rotateZ(2deg) translate(70px, 20px);
使用 getComputedStyle
const h1 = document.querySelector<HTMLElement>('h1')!;
const style = window.getComputedStyle(h1);
console.log('transform', style.transform); // matrix(0.999391, 0.0348995, -0.0348995, 0.999391, 69.2594, 22.4308)
console.log('transform', style.getPropertyValue('transform')); // matrix(0.999391, 0.0348995, -0.0348995, 0.999391, 69.2594, 22.4308)
它返回的是一个 string,里面有 6 个号码,最后 2 个便是 translateX 和 translateY。
我们可以自己 parse 这个 string,或者用 built-in 的方法 -- DOMMatrixReadOnly
const matrix = new DOMMatrixReadOnly(style.transform);
console.log('matrix', matrix.e); // 2d translateX
console.log('matrix', matrix.f); // 2d translateY
console.log('matrix', matrix.m41); // 3d translateX
console.log('matrix', matrix.m42); // 3d translateY
一个是 2d 一个是 3d,我没有认真研究它的区别,毕竟我没有用 3d,有兴趣的可以看上面的参考链接。
Right click 会触发 mousedown
click 只能监听 left click,要监听 right click 要监听 contextmenu 事件。
但 mousedown 却可以监听到 left click 和 right click。
那如何分辨是 left 还是 right click 呢?
用 event.button,它是一个 number,
0 代表 left click
1 代表 wheel click
2 代表 right click
button.addEventListener('mousedown', e => console.log(e.button));
HTMLElement.contains 和 closest 都算自己
document.body.contains(document.body);
body 包含 body,因为自己也算。
document.body.closest('body') === document.body;
body 往上找可以找到 body,因为自己也算。
Mouse Click or Keyboard Enter?
我们知道不仅仅是 mouse click,keyboard enter 和 space 也能触发 click 事件。
那从 PointerEvent 中,我们是否可以识别出来是真的 mouse click 还是 keyboard enter 触发的呢?
可以,判断 screenX or screenY 的值是否等于 0,等于 0 表示是 keyboard 触发的 click 事件。
<button>click me</button>
Scripts
document.querySelector('button').addEventListener('click', e => console.log(e.screenX === 0 ? 'keyboard' : 'mouse'));
效果
这招是从 Angular Material 源码里学来的。
DOM & BOM – 冷知识 (新手)的更多相关文章
- 前端不为人知的一面--前端冷知识集锦 前端已经被玩儿坏了!像console.log()可以向控制台输出图片
前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前 ...
- 转:前端冷知识(~~some fun , some useful)
前端不为人知的一面——前端冷知识集锦 前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Qu ...
- 盘点 Python 中的那些冷知识(二)
上一篇文章分享了 Python中的那些冷知识,地址在这里 盘点 Python 中的那些冷知识(一) 今天将接着分享!! 06. 默认参数最好不为可变对象 函数的参数分三种 可变参数 默认参数 关键字参 ...
- web 前端冷知识
前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前 ...
- 10个不为人知的 Python 冷知识
转载: 1. 省略号也是对象 ...这是省略号,在Python中,一切皆对象.它也不例外. 在 Python 中,它叫做 Ellipsis . 在 Python 3 中你可以直接写…来得到这玩意. 而 ...
- 10 个不为人知的Python冷知识
1. 省略号也是对象 ... 这是省略号,在Python中,一切皆对象.它也不例外. 在 Python 中,它叫做 Ellipsis . 在 Python 3 中你可以直接写-来得到这玩意. > ...
- 前端不为人知的一面–前端冷知识集锦 原文地址(http://web.jobbole.com/83473/);
前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前 ...
- DOM&BOM笔记
day01正课:1. DOM概述2. ***DOM树3. *查找 1. DOM概述: DHTML:动态网页技术的统称 DHTML=HTML+CSS+JS 鄙视题: HTML XHTML DHTML X ...
- .Net冷知识之动态查找类型时的程序集路径问题
今天就说说.Net中通过反射取得某个类型时,我们怎么知道这个类型在硬盘上的哪个角落?比如说,假如我们需要要求服务端动态载入某个数据源,那服务端怎么知道数据源在哪? 网上大部分的教程都写着,可以使用As ...
- 什么是BOM?,什么是DOM? BOM跟DOM之间的关系
什么是BOM? BOM是browser object model的缩写,简称浏览器对象模型.是用来获取或设置浏览器的属性.行为,例如:新建窗口.获取屏幕分辨率.浏览器版本号等. 比如 alert(); ...
随机推荐
- 深入理解 React 的 useSyncExternalStore Hook
深入理解 React 的 useSyncExternalStore Hook 大家好,今天我们来聊聊 React 18 引入的一个新 Hook:useSyncExternalStore.这个 Hook ...
- 咬文嚼图式的介绍二叉树、B树/B-树
前言 因为本人天资愚钝,所以总喜欢将抽象化的事务具象化表达.对于各类眼花缭乱的树,只需要认知到它们只是一种数据结构,类似数组,切片,列表,映射等这些耳熟能详的词汇.对于一个数据结构而言,无非就是增删改 ...
- [oeasy]python0104_指示灯_显示_LED_辉光管_霓虹灯
编码进化 回忆上次内容 x86.arm.riscv等基础架构 都是二进制的 包括各种数据.指令 但是我们接触到的东西 都是屏幕显示出来的字符 计算机 显示出来的 一个个具体的字型 ...
- 浅谈:HTTP 和 HTTPS 通信原理
1.HTTP基本概念 1.1 HTTP是什么? HTTP (超文本传输协议)协议被用于在 Web 浏览器和网站服务器之间传递信息, HTTP 协议以明文方式发送内容,不提供任何方式的数据加密,如果攻 ...
- CF709B 题解
洛谷链接&CF 链接 本篇题解为此题较简单做法及较少码量,并且码风优良,请放心阅读. 题目简述 给定 \(N\) 个点,在一条数轴上,位置为 \(x_1,-,x_n\),你的位置为 \(p\) ...
- ngnix简介和基础
一.Nginx简介 Nginx 是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP代理服务器 是一个模块化软件 [1].安装nginx 使用源码包编译安装 cd /opt ...
- [UE源码] 关于使用UE待改进的一些尝试
UE从自己做了一款游戏后,发现了蓝图以及UE引擎本身的一些优缺点: 1.蓝图在一些简单的逻辑上书写方便,直观,而且编译速度快,但是也有一些其他问题: 结构体赋值后,无法二次修改 只有3种容器Array ...
- web3 产品介绍: walletconnect 连接Web3 DApps与用户的移动加密钱包
WalletConnect是一种去中心化的开源协议,旨在连接Web3 DApps与用户的移动加密钱包,提供更安全.更便捷的加密货币交易体验.在本文中,我们将介绍WalletConnect的主要特点.工 ...
- 【Java】Collection 集合框架概述
Collection 集合框架概述 1.集合.数组都是为了存储数据而产生的 2.为什么需要集合?为了更灵活方便的存储数据,且集合能存储的容量比数组更大 3.存储的概念还停留在内存活动范围内,也只是短暂 ...
- 绝对要收藏!!! JavaEE开发常用注解
目录 前言 1.Mybatis常用注解 2.SpringMVC常用注解 3.Spring常用注解 1. IoC注解 2. DI注解 3. 事务注解 4.SpringBoot常用注解 5.Lombok注 ...