Day5
类和样式
- 这里的属性:
elem.classList
。(可以获取当前元素的所有的class名称)
elem.classList
是一个特殊的对象,它具有 add/remove/toggle
单个类的方法。
classList
的方法:
elem.classList.add/remove(class)
—— 添加/移除类。elem.classList.toggle(class)
—— 如果类不存在就添加类,存在就移除它。elem.classList.contains(class)
—— 检查给定类,返回true/false
。
-
样式
对于多词(multi-word)属性,使用驼峰式 camelCase:
background-color => elem.style.backgroundColor z-index => elem.style.zIndex border-left-width => elem.style.borderLeftWidth
删除一个样式:方法
elem.style.removeProperty('style property')
。所以,我们可以像这样删除一个属性/ 或者获取元素style并将其设置为空字符串即可document.body.style.background = 'red'; //将 background 设置为红色 setTimeout(() => document.body.style.removeProperty('background'), 1000);
当然也可以使用elem.style.cssText去设置多个css属性
-
计算样式
**
style
属性仅对"style"
特性(attribute)值起作用,而没有任何 CSS 级联(cascade)。**请注意是特性而不是属性。解决办法:(计算样式)getComputedStyle``(`element`,` `[`pseudo`]``) // element为读取样式值的元素
<head> <style> body { color: red; margin: 5px } </style> </head> <body> The red text <script> alert(document.body.style.color); // 空的 alert(document.body.style.marginTop); // 空的 </script> </body>
如上面代码段所示,是拿不到style内容的,因为是级联起来的
<head> <style> body { color: red; margin: 5px } </style> </head> <body> <script> let computedStyle = getComputedStyle(document.body); // 现在我们可以读取它的 margin 和 color 了 alert( computedStyle.marginTop ); // 5px alert( computedStyle.color ); // rgb(255, 0, 0) </script> </body>
元素大小与滚动
-
offsetParent
是最接近的祖先(ancestor),在浏览器渲染期间,它被用于计算坐标。- 最近的祖先为下列之一:
- CSS 定位的(
position
为absolute
、relative
、fixed
或sticky
), - 或
<td>
,<th>
,<table>
, - 或
<body>
。
- CSS 定位的(
<main style="position: relative" id="main"> <article> <div id="example" style="position: absolute; left: 180px; top: 180px">...</div> </article> </main> <script> //这里通过id进行获取元素 alert(example.offsetParent.id); // main alert(example.offsetLeft); // 180(注意:这是一个数字,不是字符串 "180px") alert(example.offsetTop); // 180 </script>
有以下几种情况下,
offsetParent
的值为null
:- 对于未显示的元素(
display:none
或者不在文档中)。 - 对于
<body>
与<html>
。 - 对于带有
position:fixed
的元素。
- 最近的祖先为下列之一:
-
offsetWidth
以及offsetHeight
为元素的整体大小(包括border,padding,width|height)
-
clientTop\clientLeft
大多数情况是表示:上边框宽度以及左边框宽度 -
clientWidth\clientHeight
大多数情况是表示:内容区域宽高(包括padding,但是不包括滚动条)总结
元素具有以下几何属性:
offsetParent
—— 是最接近的 CSS 定位的祖先,或者是td
,th
,table
,body
。offsetLeft/offsetTop
—— 是相对于offsetParent
的左上角边缘的坐标。offsetWidth/offsetHeight
—— 元素的“外部” width/height,边框(border)尺寸计算在内。clientLeft/clientTop
—— 从元素左上角外角到左上角内角的距离。对于从左到右显示内容的操作系统来说,它们始终是左侧/顶部 border 的宽度。而对于从右到左显示内容的操作系统来说,垂直滚动条在左边,所以clientLeft
也包括滚动条的宽度。clientWidth/clientHeight
—— 内容的 width/height,包括 padding,但不包括滚动条(scrollbar)。scrollWidth/scrollHeight
—— 内容的 width/height,就像clientWidth/clientHeight
一样,但还包括元素的滚动出的不可见的部分。scrollLeft/scrollTop
—— 从元素的左上角开始,滚动出元素的上半部分的 width/height。
除了
scrollLeft/scrollTop
外,所有属性都是只读的。如果我们修改scrollLeft/scrollTop
,浏览器会滚动对应的元素。
Windows大小和滚动
-
文档可见部分的 width/height(内容区域的 width/height):
document.documentElement.clientWidth/clientHeight
-
整个文档的 width/height,其中包括滚动出去的部分:
let scrollHeight = Math.max( document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.body.clientHeight, document.documentElement.clientHeight );
滚动:
- 读取当前的滚动:
window.pageYOffset/pageXOffset
。 - 更改当前的滚动:
window.scrollTo(pageX,pageY)
—— 绝对坐标,window.scrollBy(x,y)
—— 相对当前位置进行滚动,elem.scrollIntoView(top)
—— 滚动以使elem
可见(elem
与窗口的顶部/底部对齐)。
页面上的任何点都有坐标:
-
相对于窗口的坐标 ——
elem.getBoundingClientRect()
。 -
相对于文档的坐标 ——
elem.getBoundingClientRect()
加上当前页面滚动。// 获取元素的文档坐标 function getCoords(elem) { let box = elem.getBoundingClientRect(); return { top: box.top + window.pageYOffset, right: box.right + window.pageXOffset, bottom: box.bottom + window.pageYOffset, left: box.left + window.pageXOffset }; }
窗口坐标非常适合和 position:fixed
一起使用,文档坐标非常适合和 position:absolute
一起使用。
这两个坐标系统各有利弊。有时我们需要其中一个或另一个,就像 CSS position
的 absolute
和 fixed
一样。
事件
我们不能为一个事件分配多个处理程序。如何解决?
使用addEventListener
—Web 标准的开发者很早就了解到了这一点,并提出了一种使用特殊方法 addEventListener
和 removeEventListener
来管理处理程序的替代方法。它们没有这样的问题。
// 语法
element.addEventListener(event, handler[, options]);【event为事件名,handle为处理程序】
element.removeEventListener(event, handler[, options]);
但是请注意,在移除相同的函数的过程中,所传递的函数一定是要有定义函数名称的函数,例子:
function handler() {
alert( 'Thanks!' );
}
input.addEventListener("click", handler);
// ....
input.removeEventListener("click", handler);
这里有 3 种分配事件处理程序的方式:
- HTML 特性(attribute):
onclick="..."
。 - DOM 属性(property):
elem.onclick = function
。 - 方法(method):
elem.addEventListener(event, handler[, phase])
用于添加,removeEventListener
用于移除。
HTML 特性很少使用,因为 HTML 标签中的 JavaScript 看起来有些奇怪且陌生。而且也不能在里面写太多代码。
DOM 属性用起来还可以,但我们无法为特定事件分配多个处理程序。在许多场景中,这种限制并不严重。
最后一种方式是最灵活的,但也是写起来最长的。有少数事件只能使用这种方式。例如 transtionend
和 DOMContentLoaded
(上文中讲到了)。addEventListener
也支持对象和类作为事件处理程序。在这种情况下,如果发生事件,则会调用 handleEvent
方法。
<button id="elem">Click me</button>
<script>
class Menu {
handleEvent(event) {
switch(event.type) {
case 'mousedown':
elem.innerHTML = "Mouse button pressed";
break;
case 'mouseup':
elem.innerHTML += "...and released.";
break;
}
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
无论你如何分类处理程序 —— 它都会将获得一个事件对象作为第一个参数。该对象包含有关所发生事件的详细信息。
冒泡与捕获
当一个事件发生时 —— 发生该事件的嵌套最深的元素被标记为“目标元素”(event.target
)。
- 然后,事件从文档根节点向下移动到
event.target
,并在途中调用分配了addEventListener(..., true)
的处理程序(true
是{capture: true}
的一个简写形式)[capture设置为true意味着捕获阶段会进行事件监听]。 - 然后,在目标元素自身上调用处理程序。
- 然后,事件从
event.target
冒泡到根,调用使用on<event>
、HTML 特性(attribute)和没有第三个参数的,或者第三个参数为false/{capture:false}
的addEventListener
分配的处理程序。
每个处理程序都可以访问 event
对象的属性:
event.target
—— 引发事件的层级最深的元素。event.currentTarget
(=this
)—— 处理事件的当前元素(具有处理程序的元素)event.eventPhase
—— 当前阶段(capturing=1,target=2,bubbling=3)。
任何事件处理程序都可以通过调用 event.stopPropagation()
来停止事件,但不建议这样做,因为我们不确定是否确实不需要冒泡上来的事件,也许是用于完全不同的事情。
捕获阶段很少使用,通常我们会在冒泡时处理事件。这背后有一个逻辑。
在现实世界中,当事故发生时,当地警方会首先做出反应。他们最了解发生这件事的地方。然后,如果需要,上级主管部门再进行处理。
事件处理程序也是如此。在特定元素上设置处理程序的代码,了解有关该元素最详尽的信息。特定于 <td>
的处理程序可能恰好适合于该 <td>
,这个处理程序知道关于该元素的所有信息。所以该处理程序应该首先获得机会。然后,它的直接父元素也了解相关上下文,但了解的内容会少一些,以此类推,直到处理一般性概念并运行最后一个处理程序的最顶部的元素为止。
事件委托
它通常用于为许多相似的元素添加相同的处理,但不仅限于此。
算法:
- 在容器(container)上放一个处理程序。
- 在处理程序中 —— 检查源元素
event.target
。 - 如果事件发生在我们感兴趣的元素内,那么处理该事件。
但是也有问题:
- 首先,事件必须冒泡。而有些事件不会冒泡。此外,低级别的处理程序不应该使用
event.stopPropagation()
。 - 其次,委托可能会增加 CPU 负载,因为容器级别的处理程序会对容器中任意位置的事件做出反应,而不管我们是否对该事件感兴趣。但是,通常负载可以忽略不计,所以我们不考虑它
浏览器默认行为
有很多默认的浏览器行为:
mousedown
—— 开始选择(移动鼠标进行选择)。- 在
<input type="checkbox">
上的click
—— 选中/取消选中的input
。 submit
—— 点击<input type="submit">
或者在表单字段中按下 Enter 键会触发该事件,之后浏览器将提交表单。keydown
—— 按下一个按键会导致将字符添加到字段,或者触发其他行为。contextmenu
—— 事件发生在鼠标右键单击时,触发的行为是显示浏览器上下文菜单。- ……还有更多……
如果我们只想通过 JavaScript 来处理事件,那么所有默认行为都是可以被阻止的。
想要阻止默认行为 —— 可以使用 event.preventDefault()
或 return false
。第二个方法只适用于通过 on<event>
分配的处理程序。
addEventListener
的 passive: true
选项告诉浏览器该行为不会被阻止。这对于某些移动端的事件(像 touchstart
和 touchmove
)很有用,用以告诉浏览器在滚动之前不应等待所有处理程序完成。
如果默认行为被阻止,event.defaultPrevented
的值会变成 true
,否则为 false
。【这是个属性】–可以完成防止事件冒泡的需求。
<p>Right-click for the document menu (added a check for event.defaultPrevented)</p>
<button id="elem">Right-click for the button menu</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
if (event.defaultPrevented) return;
event.preventDefault();
alert("Document context menu");
};
</script>
鼠标事件
在事件的处理过程中是有先后顺序的:会遵循
mousedown
→mouseup
→click
的顺序调用处理程序。鼠标按钮
在click事件中指定了鼠标按钮为左键,contextmenu指定鼠标按钮为右键。但是对于mousedown和mouseup事件它不会判断是鼠标的左键还是右键,因此我们需要有的时候来进行判断,判断属性为:
event.button
组合键(鼠标事件也可能需要键盘配合才可以触发)
事件属性:
shiftKey
:ShiftaltKey
:Alt(或对于 Mac 是 Opt)ctrlKey
:CtrlmetaKey
:对于 Mac 是 Cmd<button id="button">Alt+Shift+Click on me!</button> // 需要同时按住alt和shift并点击才可以触发 <script> button.onclick = function(event) { if (event.altKey && event.shiftKey) { alert('Hooray!'); } }; </script>
坐标
所有的鼠标事件都提供了两种形式的坐标:
- 相对于窗口的坐标:
clientX
和clientY
。- 相对于文档的坐标:
pageX
和pageY
。防止复制(防止默认事件发生)
mousedown
的默认浏览器操作是文本选择,如果它对界面不利,则应避免它。如果我们想禁用选择以保护我们页面的内容不被复制粘贴,那么我们可以使用另一个事件:
oncopy
。<div oncopy="alert('Copying forbidden!');return false"> Dear user, The copying is forbidden for you. If you know JS or HTML, then you can get everything from the page source though. </div>
如果你试图在
<div>
中复制一段文本,这是行不通的,因为默认行为oncopy
被阻止了。鼠标移动
mouseover和mouseout这两个事件并不是很好用,因为mouseover具有冒泡特性,因此假如鼠标从父元素进入子元素时,父元素会出现两个处理程序(一个mouseout【因为移动到子元素了】,还有一个mouseover【冒泡产生】)
解决:1.使用relatedTarget进行判断 2. 使用
mouseenter\mouseleave
两个事件对于mouseenter\mouseleave:
与前两个事件差不多,鼠标移入和移出时触发,但是有两个重要的区别:
- 元素内部与后代之间的转换不会产生影响。
- 事件
mouseenter/mouseleave
不会冒泡。总结:
我们讲了
mouseover
,mouseout
,mousemove
,mouseenter
和mouseleave
事件。以下这些内容要注意:
- 快速移动鼠标可能会跳过中间元素。
mouseover/out
和mouseenter/leave
事件还有一个附加属性:relatedTarget
。这就是我们来自/到的元素,是对target
的补充。即使我们从父元素转到子元素时,也会触发
mouseover/out
事件。浏览器假定鼠标一次只会位于一个元素上 —— 最深的那个。
mouseenter/leave
事件在这方面不同:它们仅在鼠标进入和离开元素时才触发。并且它们不会冒泡。拖拽
关键部分:(ball就是一个元素)
- 事件流:
ball.mousedown
→document.mousemove
→ball.mouseup
(不要忘记取消原生ondragstart
)。- 在拖动开始时 —— 记住鼠标指针相对于元素的初始偏移(shift):
shiftX/shiftY
,并在拖动过程中保持它不变。- 使用
document.elementFromPoint
检测鼠标指针下的 “droppable” 的元素。// 实现一个拖拽
键盘事件
按一个按键总是会产生一个键盘事件,无论是符号键,还是例如 Shift 或 Ctrl 等特殊按键。唯一的例外是有时会出现在笔记本电脑的键盘上的 Fn 键。它没有键盘事件,因为它通常是被在比 OS 更低的级别上实现的。
键盘事件:
keydown
—— 在按下键时(如果长按按键,则将自动重复),【event对象的repeat属性设置为true了】keyup
—— 释放按键时。
键盘事件的主要属性:
code
—— “按键代码”("KeyA"
,"ArrowLeft"
等),特定于键盘上按键的物理位置。key
—— 字符("A"
,"a"
等),对于非字符(non-character)的按键,通常具有与code
相同的值。- 也就是说event.key是区分大小写的,但是event.code不区分大小写返回的是物理上的位置,比如‘KeyZ’
过去,键盘事件有时会被用于跟踪表单字段中的用户输入。这并不可靠,因为输入可能来自各种来源。我们有 input
和 change
事件来处理任何输入(稍后我们会在 事件:change,input,cut,copy,paste 一章中进行介绍)。它们在任何类型的输入(包括复制粘贴或语音识别)后触发。
滚动
- 滑到页面底部则添加内容至网页
function populate() {
while(true) {
// 文档末端
let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom;
// 如果用户将页面滚动的距离不够远(文档末端距窗口底部 >100px)
if (windowRelativeBottom > document.documentElement.clientHeight + 100) break;
// 让我们添加更多数据
document.body.insertAdjacentHTML("beforeend", `<p>Date: ${new Date()}</p>`);
}
}
表单
表单导航:
-
document.forms
一个表单元素可以通过
document.forms[name/index]
访问到。【请注意是表单控件里面的name属性】
<form name="my">
<input name="one" value="1">
<input name="two" value="2">
</form>
<script>
// 获取表单
let form = document.forms.my; // <form name="my"> 元素
// 获取表单中的元素
let elem = form.elements.one; // <input name="one"> 元素
alert(elem.value); // 1
</script>
-
form.elements
表单元素可以通过
form.elements[name/index]
的方式访问,或者也可以使用form[name/index]
。elements
属性也适用于<fieldset>
。 -
element.form
元素通过
form
属性来引用它们所属的表单。
value
可以被通过 input.value
,textarea.value
,select.value
等来获取到。(对于单选按钮(radio button)和复选框(checkbox),可以使用 input.checked
来确定是否选择了一个值。
对于 <select>
,我们可以通过索引 select.selectedIndex
来获取它的 value
,也可以通过 <option>
集合 select.options
来获取它的 value
。
一个 <select>
元素有 3 个重要的属性:
select.options
——<option>
的子元素的集合,select.value
—— 当前所选择的<option>
的 value,select.selectedIndex
—— 当前所选择的<option>
的编号。
radio
:
<script>
function getRadioValue(){
var radio = document.querySelector('input[name="sex"]:checked');
alert("您选择的是:" + radio.value);
}
</script>
<body>
<input type="radio" name="sex" value="男" checked>男
<input type="radio" name="sex" value="女">女
<br />
<input type="button" value="确定" onclick="getRadioValue()">
</body>
focus/ blur
在元素获得/失去焦点时会触发 focus
和 blur
事件。
它们的特点是:
-
它们不会冒泡。但是可以改为在捕获阶段触发,或者使用
focusin/focusout
。可以使用
focusin
和focusout
事件 —— 与focus/blur
事件完全一样,只是它们会冒泡。值得注意的是,必须使用elem.addEventListener
来分配它们,而不是on<event>
。 -
大多数元素默认不支持聚焦。使用
tabindex
可以使任何元素变成可聚焦的。
可以通过 document.activeElement
来获取当前所聚焦的元素。
事件更改
表单提交
-
事件submit(Enter也会触发,同时调用一次click事件)
<form onsubmit="alert('submit!');return false"> First: Enter in the input field <input type="text" value="text"><br> Second: Click "submit": <input type="submit" value="Submit"> </form>
-
方法submit
let form = document.createElement('form'); form.action = 'https://google.com/search'; form.method = 'GET'; form.innerHTML = '<input name="q" value="test">'; // 该表单必须在文档中才能提交 document.body.append(form); form.submit();
heckbox),可以使用 input.checked
来确定是否选择了一个值。
对于 <select>
,我们可以通过索引 select.selectedIndex
来获取它的 value
,也可以通过 <option>
集合 select.options
来获取它的 value
。
一个 <select>
元素有 3 个重要的属性:
select.options
——<option>
的子元素的集合,select.value
—— 当前所选择的<option>
的 value,select.selectedIndex
—— 当前所选择的<option>
的编号。
radio
:
<script>
function getRadioValue(){
var radio = document.querySelector('input[name="sex"]:checked');
alert("您选择的是:" + radio.value);
}
</script>
<body>
<input type="radio" name="sex" value="男" checked>男
<input type="radio" name="sex" value="女">女
<br />
<input type="button" value="确定" onclick="getRadioValue()">
</body>
focus/ blur
在元素获得/失去焦点时会触发 focus
和 blur
事件。
它们的特点是:
-
它们不会冒泡。但是可以改为在捕获阶段触发,或者使用
focusin/focusout
。可以使用
focusin
和focusout
事件 —— 与focus/blur
事件完全一样,只是它们会冒泡。值得注意的是,必须使用elem.addEventListener
来分配它们,而不是on<event>
。 -
大多数元素默认不支持聚焦。使用
tabindex
可以使任何元素变成可聚焦的。
可以通过 document.activeElement
来获取当前所聚焦的元素。
事件更改
[外链图片转存中…(img-WbYdbrI0-1710555725311)]
表单提交
-
事件submit(Enter也会触发,同时调用一次click事件)
<form onsubmit="alert('submit!');return false"> First: Enter in the input field <input type="text" value="text"><br> Second: Click "submit": <input type="submit" value="Submit"> </form>
-
方法submit
let form = document.createElement('form'); form.action = 'https://google.com/search'; form.method = 'GET'; form.innerHTML = '<input name="q" value="test">'; // 该表单必须在文档中才能提交 document.body.append(form); form.submit();