class相當於筆記軟體裡的標籤
,可以有很多個,識別代號為.
id就是ID
,只能有一個,識別代號為#
比較:
這兩者最大的不同,是在於 ID 選擇器在一個 HTML 文件中只能被使用一次,而 Class 選擇器在一個 HTML 文件中可以被使用多次。第二個不同的地方,是 ID 選擇器可以被 Javascript 中的 GetElementByID 函數所運用,而 Class 選擇器無法被 Javascript 運用到。
並沒有什麼固定的規則,來決定什麼時候要用 ID 及什麼時候要用 Class。我們的建議是盡量用 Class,因為這樣子最靈活 (同一個 HTML 文件可以利用這類的選擇器多次)。唯一的例外,是當你要用 Javascript 的 GetElementByID 函數時。在這個情況下,你就應該要用 ID。
Class 名稱及 ID 名稱都是對大小寫敏感的。舉例來說,.classone 及 .ClassOne 是代表兩個不同的 Class 選擇器。
class範例:
<p class='abc'> test </p>
css:
.abc{
color: #00ff00;
}
id範例:
<p id='footer'>
test
</p>
css:
#footer{
color: #00ff00;
}
利用 el.classList.add/remove/toggle
來為元素添加/移除/切換 class:
const block = document.querySelector('.block')
// 為 block 這個元素添加/移除/切換 active 這個 class
block.classList.add('active')
block.classList.remove('active')
block.classList.toggle('active')
利用transitionend
事件,監聽所有 block 上有 transition 的 CSS 屬性,並且透過 propertyName
來判斷是哪個 css 屬性:
const block = document.querySelector('.block')
// 監聽所有 block 上有 transition 的 CSS 屬性
block.addEventListener('transitionend', e => {
console.log(e.propertyName)
})
關於更多 JavaScript 時間函式
let now = new Date()
let hour = now.getHours()
let minute = now.getMinutes()
let second = now.getSeconds()
利用 :root{--<varName>: <varValue>}
來建立 CSS 的變數,並且在 CSS 屬性值中使用 var(--<varName>)
來代入變數的值:
/* 建立 css 變數 */
:root {
--base: #ffc600;
--spacing: 10px;
}
/* 使用 css 變數 */
img {
display:block;
box-sizing: border-box;
margin: 0 auto;
padding: var(--spacing);
background: var(--base);
}
我們可以利用 document.documentElement.style.setProperty('--<varName>', '<varValue>')
來動態修改 CSS 變數的值,但要記得要加上單位(例如,px):
document.documentElement.style.setProperty(`--${this.name}`, this.value + this.dataset.unit)
我們可以在 html 中利用 data attribute 設值
更多關於 html5 data attribute @ pjchender
<div class="tooltip" data-content="hello"></div>
在 JS 中我們可以使用 el.dataset
來取得 data-attribute 的值
const block = document.querySelector('.block')
console.log(block.dataset.content)
可以利用 new RegExp(regex, flag)
來建立正規式:
flag
g
表示 global search,也就是會去找整份文件,而不是找到就停i
表示 case insensitive,也就是不去區分大小寫let regex = new RegExp(wordToMatch, 'gi')
這裡我們用 input
事件
input event
會在任何元素值改變的時候被出發(例如每打一個字都會觸發一次)change event
則是會在有元素值改變,且該元素脫離 focus 狀態時才觸發searchInput.addEventListener('input', e => {
displayMatch(e)
})
在原本的內容中,作者使用的是 Array.prototype.splice
這個方法,但 splice 一般來說是在刪除或添加陣列內容時使用,因此個人認為使用單純擷取陣列元素的 Array.prototype.slice
應該更為適合(我想作者有它的考量)。
arr.slice(begin, end)
- 從 begin 開始,不包含 end
- 最後一個元素的 index 是 -1
/**
* arr.slice(begin, end),從 begin 開始,不包含 end
**/
let arr = [0,1,2,3,4,5,6,7,8,9,0]
arr.slice(0,3) // [0,1,2]
arr.slice(3,7) // [3,4,5,6]
// 最後一個是 -1
arr.slice(-3, -1) // [8,9]
arr.slice(-5,-3) // [6,7]
arr.slice(-3,-5) // []
// 如果要擷取到最後一個
arr.slice(-5, arr.length) // [6,7,8,9,0]
arr.join(separator) // separator 預設是 ','
window.scrollY // 取的瀏覽器視窗捲軸 Y 的高度(捲軸在最上方時是 0)
window.innerHeight // 瀏覽器內視窗的高度
element.offsetTop // 元素距離外層容器上方的距離
element.height // 元素的高度
這裡有一個要留意的地方是, offset
指的就是相對於 offsetParent
的距離。一般來說offsetParent
都會是 body
,但是如果這個元素的外層有另外設定 position
的話,那麼 offsetParent 就可能改變。
因此,再使用 offset
屬性前,盡量先用 offsetParent
來查看該元素的父層對應到的是哪個元素。
因為 JS 再操作 DOM 的時候很方便,因此有些時候沒有真的要把表單送出(submit)的話,就都沒有把 <input>
外面在包一層 <form>
。但是使用 <form>
包住有幾個好處,一個是我們在監聽的時候可以監聽 submit
事件。更重要的是 HTMLFormElement 提供了我們 reset()
這個方法,讓我們可以一次把表單的內容全部清空。
但是使用 <form>
的話要記得把 button 的 type 設成 submit (<button type="submit">
);另外,要記得在按鈕點擊時要 preventDefault()
,否則會真的把表單送出(<form>
沒有設定location
的話變成重新整理)。
另外,通常一個 form 裡面只會有一個 submit button ,如果你有多個 button 要做不同功能的話,form + submit 的方式或許就不適合。
/**
* STEP1: 添加 item 項目
* 這裡監聽的是 submit 事件,好處是當使用者按 enter 也會觸發
**/
addItems.addEventListener('submit', addItem)
function addItem (e) {
e.preventDefault() // 使用 preventDefault 避免真的 submit
let text = document.querySelector('[name="item"]').value
let item = {
text,
done: false
}
plateItems.push(item)
/**
* HTMLFormElement 可以使用 reset() 方法來清空表單
**/
e.target.reset()
}
<!-- 只有出現 checked attribute 則 checkbox 都會被勾選 -->
<input type="checkbox" checked>
<input type="checkbox" checked="false"> <!-- 一樣會勾選 -->
在 element.addEventListener
中使用 this
和 e.target
的差異:
this
總是會拿到被監聽的對象本身,也就是 element.addEventListener(<eventHandler>
) 的 element。e.target
則是指事件被觸發時的對象,有可能不是 element 本身。const hero = document.querySelector('.hero')
hero.addEventListener('mousemove', moveShadow)
function moveShadow (e) {
console.log('this', this) // 回傳的一定是 ".hero"
console.log('e', e.target) // 回傳的可能是 ".hero" 也可能是 "h1"
}
/**
* 等同於
* const heroWidth = hero.offsetWidth
* const heroHeight = hero.offsetHeight
* let offsetXAfterAdjust = e.offsetX
* let offsetYAfterAdjust = e.offsetY
**/
const {offsetWidth: heroWidth, offsetHeight: heroHeight} = hero;
let {offsetX: offsetXAfterAdjust, offsetY: offsetYAfterAdjust} = e;
要特別留意的是 offsetLeft
, offsetTop
, offsetWidth
, offsetHeight
是 Element 的屬性;而 offsetX
和 offsetY
是 Event 的屬性。
offsetLeft
, offsetTop
指該 element 到 offsetParent 的距離。offsetWidth
, offsetHeight
指該 element 的寬高。offsetX
, offsetY
,指滑鼠到外層容器的距離。<h1>
的外層沒有設定 position,所以它的 offsetParent 一樣會是 <body>
,因此 e.target.offsetLeft
指的就是 <h1>
左側到 <body>
左側的距離(h1 <-> body)。offsetX
, offsetY
是 event 的屬性,所以當它進入到 <h1>
時,offsetX 指的是該滑鼠到容器(h1)外層的距離(滑鼠 <-> h1)。if (this !== e.target) {
// 當 this !== e.target ,意思就是指 e.target === <h1> 的時候
offsetXAfterAdjust = offsetXAfterAdjust + e.target.offsetLeft;
offsetYAfterAdjust = offsetYAfterAdjust + e.target.offsetTop;
}
一般情況下陣列的排序可以使用 Array.prototype.sort(callback<a, b>)
:
const sortedBands = bands.sort((a, b) => a > b ? 1 : -1)
如果我們希望排序時可以過濾掉某些字元,可以使用正規式:
function strip (word) {
let regex = new RegExp('^(a |the |an )', 'i')
return word.replace(regex, '').trim()
}
// 讓排序的依據是被 strip 過的字元
const sortedBands = bands.sort((a, b) => (strip(a) > strip(b)) ? 1 : -1)
我們取得的 Node Element 它是 Array-like
的物件,而不是真的 Array,因此他沒有 Array.prototype.map
這個 function 可以使用,因此我們需要先把它轉成 Array:
...
)const timeNodes = [...document.querySelectorAll('[data-time]')]
const timeNodes = Array.from(document.querySelectorAll('[data-time]'))
使用 Element.dataset.<attribute>
可以把 data-time
的值取出來。