[Day 28] 阿嬷都看得懂的怎麽操纵 DOM

阿嬷都看得懂的怎麽操纵 DOM

前两天我们介绍了厉害的神灯精灵,以及 JavaScript 这个神灯精灵语的方言。既然我们知道, JavaScript 是用来编写网页与使用者互动的脚本;接下来,我们就要开始让神灯精灵帮助我们操纵网页中的元素罗!

还记得我们说过,整个网页中的元素,可以看成一棵横放的树,而且我们把这棵树叫做 "DOM" 吗?

我们都知道,虽然神灯精灵很万能,但是它必须接受很明确的指示。我们没办法指着网页上的某个元素,告诉它就是操纵这个元素就对了!因为神灯精灵没有眼睛,看不到我们手指哪里。我们必须使用文字描述,告诉它我们希望改变的元素是哪一个。

虾米,那我们应该怎麽使用 JavaScript 向神灯精灵指出特定元素呢?

我们使用的是 document 这个物件。说是物件听起来有点吓人,我们就理解为神秘关键字咒语就可以了。当我们对神灯精灵喊 "document" 的时候,它就会知道我们要说的事情和网页元素有关。事实上,document 也是整棵 DOM 树的树根,因此,我们用这个树根的名称,来告诉神灯精灵,我们要对网页元素做些什麽罗!

让我们先来找到目标元素。找目标元素的方法,和 CSS 选择器非常类似。我们可能从元素的分堆方式 (id, class, tagname) 来找,也可以从元素的从属关系来找。例如,让我们回顾之前的贴纸簿练习:
https://codepen.io/LogosChen/pen/jOwzgpz

在这个练习中,我们可以怎麽找到「粉红贴纸」的字样呢?这个字样被包在 .pink 的元素中;因此,只要我们能够找到 .pink,就能够找到这个字样。找到 .pink 元素的方式非常简单,先喊出 document 以後,再使用 .getElementsByClassName(类别名称的文字串) 这个功能,就可以得到属於这个类别的所有元素构成的阵列。

为什麽我们会获得阵列呢?因为在网页中,可能会有许多元素共享同个类别名称。在我们得到这个元素构成的阵列以後,就可以用阵列项目的方式,把特定的元素挑选出来。也因为这样,我们这个功能的名称是 getElement"s"ByClassName。如果是用 id 取得元素,功能名称就是 getElementById,中间没有复数的 "s",获得的结果也是单一元素。

因此,我们要找到「粉红贴纸」的字样,就可以使用类别名称 .pink,来获得同属 .pink 类别中的元素所构成的清单,再将这个标签挑选出来。那麽,我们要怎麽挑出子元素呢?在 JavaScript 中,每个网页元素都有底下这些属性:

  • childNodes: 子元素构成的阵列
  • firstChild: 第一个子元素
  • lastChild: 最後一个子元素
  • parentNode: 父元素 (每个子元素只会有 1 个父元素,反之不然)
  • previousSibling: 同层前一个元素
  • nextSibling: 同层後一个元素

因此,聪明的阿嬷应该知道怎麽抓出「粉红元素」这个元素了!我们可以用 getElementsByClassName 这个方法用 .pink 找出属於 .pink 这个类别的元素阵列,然後选取第 0 项,再挑出 firstChild 就可以了!用 JavaScript 写看起来会像这样:

document.getElementsByClassName("pink")[0].firstChild

不过,当我们使用 console.log() 试着印出这个元素时,却发现完全没东西!这是怎麽回事呢?原来,浏览器会把我们的换行符号也当作 1 个元素。因此,我们必须使用 childNodes[1] 才有办法找到当中的 p 元素。

document.getElementsByClassName("pink")[0].childNodes[1]

找到特定的元素以後,我们就可以侦测这些元素发生的事件,并且操纵这些元素了。

侦测元素发生事件的方式,是在选定的元素後,加上事件监听器方法 .addEventListener(事件型态文字串, 执行功能)。事件型态文字串的值非常多,可以参考这份文件。我们这边只列举比较常见的:

  • "click": 点下滑鼠
  • "dblclick": 双击滑鼠
  • "mouseover": 滑鼠移上
  • "mouseout": 滑鼠移出
  • "mousedown": 按下滑鼠
  • "mouseup": 放开滑鼠
  • "drag": 拖曳滑鼠
  • "drop": 拖曳滑鼠至该元素上释放

而执行功能就是任意功能,同样用 function 字样带出。不过,由於这个功能只存在於这个事件监听器当中,所以不用给它任何名称。只需要写成

function(){
}

就可以了喔!

让我们试试看写出这个脚本:当使用者点击「粉红贴纸」字样的时候,字样後方要加上 " Clicked" 字样。

我们上面已经知道怎麽找到「粉红贴纸」字样这个元素,不过由於神灯精灵的记性很差,所以必须也给个变数来储存这个元素。因此,我们在加上事件监听器後,整个结构会长这样:

let pinkText = document.getElementsByClassName("pink")[0].childNodes[1]
pinkText.addEventListener("click",function(){
 「粉红贴纸」字样加上 "Clicked" 字样
})

那麽,我们要怎麽把「粉红贴纸」字样加上 "Clicked" 字样呢?我们可能会想,既然我用 console.log(pinkText) 会得到 <p>粉红贴纸</p> 这个标签,我们应该就直接写

pinkText = pinkText + " Clicked"

就可以了吧?奇怪,怎麽完全没变化呢?因为我们获得的网页元素本身是个物件,具备很多属性,其中的文字内容只是其中一项。当我们想修改其中的文字内容,我们必须修改「文字内容」这个属性。文字内容这个属性我们是使用

  • .innerText 找出或修改;另外我们还可以用
  • .innerHTML 找出或修改该元素的整个 HTML 标签;甚至可以用
  • .id 找出或修改该元素的 id;同理也可以用
  • .className 找出或修改该元素所属的 class;
  • .style.属性 找出或修改该元素的属性。

因此,聪明的阿嬷们应该会知道,我们这边应该就是需要写下

  pinkText.innerText = pinkText.innerText + " Clicked" 

来改变「粉红贴纸」这个字样。

是说,各位阿嬷会不会觉得上面这段程序码也很不 DRY 呢?没错,这整个 pinkText.innerText 都重复啦!因此,懒惰注重效率的工程师当然会想节省笔墨罗!每当我们想要写

变数 = 变数 + 某个值

的时候,我们都可以直接写成

变数 += 这个值

另外,不只 + 这个运算方式,任何运算方式, -, *, /, %, &&, || 都可以使用这种写法。因此,当我们想写

	x = x/2

的时候,也可以写成

	x /= 2

甚至,懒惰注重效率的工程师连

	x += 1

都懒得写,而改写成

	x++

;减法也以此类推。

因此,我们就可以使用下面这段 JavaScript,来达成使用者点选「粉红贴纸」字样後,再其後加上 " Clicked" 字样的目标。

let pinkText = document.getElementsByClassName("pink")[0].childNodes[1]
pinkText.addEventListener("click",function(){
 pinkText.innerText = pinkText.innerText + " Clicked" 
})

最後实现的贴纸簿长这样:
https://codepen.io/LogosChen/pen/oNeXGRM

各位阿嬷可以自己 fork 回去玩喔!

最後,我们应该怎麽引入我们写好的 JavaScript 档呢?让我们来把在 CodePen 上写好的档案抓下来看看吧!我们会发现 index.html 这个档案的结构长这样:

<html>
	<body>
	.
	.
	.
		<script src="./script.js"></script>
	</body>
</html>

我们会发现

  1. JacaScript 脚本是存成 .js 档;
  2. 这个 .js 档是使用 script 标签引入;
  3. 这个档案引入的地方是在 body 标签最後。

引入 .js 的位置和引入 .css 的位置不同,是因为 .js 档通常必须对 DOM 元素做些操作,所以必须等到 DOM 元素都被渲染出来以後, .js 档当中的脚本才有办法抓到那些元素,并且加以监听或操纵。


<<:  Day 28 : 应用篇 — 如何透过 Obsidian 帮助知识工作者写作 ? 分享我的 Obsidian 写作流程

>>:  [D28] 资料增强

网路的小技巧-2

//兴趣记录一下~希望退休以後可以回味,各位别嫌弃,感谢各位!! //// //VLAN重要性,实作...

[11] 建立进入页面和流程控制

这边你需要自己制作一个流程控制 不了解的话建可以画个图来确认现在在哪个流程 基本上都会回到主要操控介...

Day 18 - 产业研究分析浅谈

转换一下, 来谈谈PM的日常, 还有其他工作类型, 例如像是产业研究分析的任务, 尤其是针对想要选...

[ 卡卡 DAY 8 ] - React Native 跨平台装置判断

还记得 React Native 可以同时完成 iOS / Android 装置吧? 跨平台装置如...

[DAY21]跟 Vue.js 认识的30天 - Vue 过滤器(`filter`)

这是一个在 Vue 3 被拿掉的功能,但还是来了解一下。 filter 用途及用法 filter 是...