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(); ...
随机推荐
- 27 首页banner文库失效
安卓app 首页banner文库没有连接功能
- 3.1 Y86-64指令集体系结构
程序员可见的状态 这里的程序员即可以是用汇编代码写程序的人,也可以是产生机器级代码的编译器.程序员可见的状态如下,有15个程序寄存器(%rax,%rbx等),三个一位的条件(ZF,OF,SF) ,程序 ...
- 说说RabbitMQ延迟队列实现原理?
使用 RabbitMQ 和 RocketMQ 的人是幸运的,因为这两个 MQ 自身提供了延迟队列的实现,不像用 Kafka 的同学那么苦逼,还要自己实现延迟队列.当然,这都是题外话,今天咱们重点来聊聊 ...
- django信号中的条件判断不符合时如何提示错误并返回
在Django中,如果你在信号(Signal)处理函数中需要进行条件判断,如果条件不符合,你可以触发一个异常,并在视图或其他地方捕获这个异常,然后返回相应的错误提示. 以下是一个简单的例子,演示如何在 ...
- 论如何直接用EF Core实现创建更新时间、用户审计,自动化乐观并发、软删除和树形查询(上)
前言 数据库并发,数据审计和软删除一直是数据持久化方面的经典问题.早些时候,这些工作需要手写复杂的SQL或者通过存储过程和触发器实现.手写复杂SQL对软件可维护性构成了相当大的挑战,随着SQL字数的变 ...
- JavaScript高级~数组偏平化
方式一: let arr=[11,[22,[33,[44]]],[55,66,77],88,99,['00']] let arr2=arr.toString().split("," ...
- JavaScript一天一个算法题~持续更新中。。。。。
1,数组去重 i.暴力去重 思路:建一个空数组,通过判断原数组的元素是否在空数组内,如果在,不放入,不在,放入空数组. function clearCommnetArray(array){ let a ...
- JavaScript中的new map()和new set()使用详细(new map()和new set()的区别)
简介:new Map(): 在JavaScript中,new Map()用于创建一个新的 Map 对象.Map 对象是一种键值对的集合,其中的键是唯一的,值可以重复.new Set(): 在JavaS ...
- Python threading实现多线程 提高篇 线程同步,以及各种锁
本文主要讲多线程的线程之间的资源共享怎么保持同步. 多线程基础篇见,Python threading实现多线程 基础篇 Python的多线程,只有用于I/O密集型程序时效率才会有明显的提高,如文件/输 ...
- 为什么我@Value中明明显示了值,他却是null
今天尝试把一些重要东西写入application.yml里,结果在使用的时候发现value取不出来值原因有2个: 1.没有写@compent,没有把这个类交给spring管理 2.在service层n ...