-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c96cd4f
commit f92b9aa
Showing
1 changed file
with
280 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,94 +1,301 @@ | ||
--- | ||
title: Ch.13 簡易計算機 | ||
title: Ch.13 Observer | ||
tags: | ||
- JavaScript | ||
prev: ./ch12 | ||
next: ./ch14 | ||
--- | ||
製作簡易計算機,練習事件 | ||
Observer 監視器,可以觀察 DOM 元素的變化,當元素變化時執行指定的 function | ||
<!-- more --> | ||
## 計算機 | ||
:::danger | ||
`eval()` 是非常危險的語法,使用時須特別注意 | ||
## Mutation Observer | ||
當觀察的 DOM 元素變動時會觸發 | ||
::: demo [vanilla] | ||
```html | ||
<html> | ||
<input type="button" id="btn-mutation" value="點按鈕修改 div"> | ||
<div id="div-mutation"></div> | ||
</html> | ||
<style> | ||
#div-mutation{ | ||
width: 100px; | ||
height: 100px; | ||
background: black; | ||
color: white; | ||
} | ||
</style> | ||
<script> | ||
// 取得要觀察的元素 | ||
const div = document.getElementById('div-mutation') | ||
// 建立一個觀察者 | ||
// new MutationObserver(觀察到變更時執行的 function) | ||
const observer = new MutationObserver(mutations => { | ||
for (const mutation of mutations) { | ||
// type - 變動類型 | ||
// target - 變動的元素 | ||
// addedNodes - 被新增的節點 | ||
// removedNodes - 被移除的節點 | ||
// attributeName - 變動的屬性 | ||
// oldValue - 變動前的值 | ||
console.log(mutation) | ||
} | ||
}) | ||
// 設定觀察元素 | ||
observer.observe(div, { | ||
// 是否觀察下一層元素 | ||
childList: true, | ||
// 是否觀察所有內層 | ||
subtree: true, | ||
// 是否觀察屬性變動 | ||
attributes: true, | ||
// 是否觀察內容變動 | ||
characterData: true, | ||
// 是否紀錄舊屬性 | ||
attributeOldValue: true, | ||
// 是否紀錄舊內容 | ||
characterDataOldValue: true, | ||
// 指定觀察的屬性名稱,沒設定就是全部 | ||
// attributeFilter: ['href'] | ||
}) | ||
// 停止觀察 | ||
// observer.disconnect() | ||
const btn = document.getElementById('btn-mutation') | ||
btn.addEventListener('click', () => { | ||
// 修改元素,檢查是否觸發觀察者 | ||
div.innerText += 'a' | ||
}) | ||
</script> | ||
``` | ||
::: | ||
|
||
## Resize Observer | ||
當觀察的 DOM 元素縮放時會觸發 | ||
::: demo [vanilla] | ||
```html | ||
<html> | ||
<input type="button" id="btn-resize" value="點按鈕修改 div 邊框"> | ||
<div class="div-resize" id="div-resize1"></div> | ||
<div class="div-resize" id="div-resize2"></div> | ||
</html> | ||
<style> | ||
.div-resize{ | ||
width: 100px; | ||
height: 100px; | ||
background: gray; | ||
margin: 10px; | ||
} | ||
</style> | ||
<script> | ||
// 取得要觀察的元素 | ||
const div1 = document.getElementById('div-resize1') | ||
const div2 = document.getElementById('div-resize2') | ||
// 建立一個觀察者 | ||
// new ResizeObserver(觀察到變更時執行的 function) | ||
const observer = new ResizeObserver(mutations => { | ||
for (const mutation of mutations) { | ||
// target - 變動的元素 | ||
// contentRect - 元素的寬高與座標 | ||
// borderBoxSize.blockSize - 元素的高度 | ||
// borderBoxSize.inlineSize - 元素的寬度 | ||
// contentBoxSize.blockSize - 元素的高度 | ||
// contentBoxSize.inlineSize - 元素的寬度 | ||
console.log(mutation) | ||
} | ||
}) | ||
// 設定觀察元素 | ||
observer.observe(div1, { | ||
// 設定觀察元素的寬高計算方式 | ||
// content-box: 元素的寬高不包含邊框 | ||
// border-box: 元素的寬高包含邊框 | ||
box: 'border-box' | ||
}) | ||
observer.observe(div2, { | ||
box: 'content-box' | ||
}) | ||
// 停止觀察 | ||
// observer.disconnect() | ||
// 停止觀察某元素 | ||
// observer.unobserve(div1) | ||
// 修改元素,檢查是否觸發觀察者 | ||
// 如果有一連串尺寸變動,會合併成一次紀錄,且只記錄最終結果 | ||
const btn = document.getElementById('btn-resize') | ||
btn.addEventListener('click', () => { | ||
// 修改元素,檢查是否觸發觀察者 | ||
div1.style.border = '10px solid black' | ||
div1.style.border = '10px solid black' | ||
div2.style.border = '10px solid black' | ||
div2.style.border = '10px solid black' | ||
}) | ||
</script> | ||
``` | ||
::: | ||
|
||
## Intersection Observer | ||
當元素相交時會觸發 | ||
::: demo [vanilla] | ||
```html | ||
<html> | ||
<div id="content-out"> | ||
<div id="content-in"> | ||
<table> | ||
<tr> | ||
<td colspan="4" id="input">0</td> | ||
</tr> | ||
<tr> | ||
<td>C</td> | ||
<td>/</td> | ||
<td>*</td> | ||
<td>-</td> | ||
</tr> | ||
<tr> | ||
<td>7</td> | ||
<td>8</td> | ||
<td>9</td> | ||
<td rowspan="2">+</td> | ||
</tr> | ||
<tr> | ||
<td>4</td> | ||
<td>5</td> | ||
<td>6</td> | ||
</tr> | ||
<tr> | ||
<td>1</td> | ||
<td>2</td> | ||
<td>3</td> | ||
<td rowspan="2">=</td> | ||
</tr> | ||
<tr> | ||
<td colspan="2">0</td> | ||
<td>.</td> | ||
</tr> | ||
</table> | ||
</div> | ||
</div> | ||
<p id="intersection-info">isIntersecting = false</p> | ||
<div id="intersection-container"> | ||
<div class="intersection-pad"></div> | ||
<div id="intersection-target"></div> | ||
<div class="intersection-pad"></div> | ||
</div> | ||
</html> | ||
<style> | ||
#intersection-container { | ||
height: 300px; | ||
width: 100%; | ||
position: relative; | ||
overflow-y: scroll; | ||
background: white; | ||
} | ||
.intersection-pad { | ||
height: 1500px; | ||
width: 100%; | ||
} | ||
#intersection-target { | ||
background: rgb(237, 28, 36); | ||
height: 100px; | ||
outline: 50px solid rgba(0, 0, 0, 0.2); | ||
} | ||
</style> | ||
<script> | ||
const input = document.getElementById("input") | ||
const td = document.querySelectorAll("td:not(#input)") | ||
for (let i = 0; i < td.length; i++) { | ||
td[i].onclick = function () { | ||
console.log(this.innerText) | ||
if (this.innerText == "=") { | ||
input.innerText = eval(input.innerText) | ||
} else if (this.innerText == "C") { | ||
input.innerText = "0" | ||
} else { | ||
if(input.innerText == "0"){ | ||
input.innerText = this.innerText | ||
} | ||
else{ | ||
input.insertAdjacentHTML("beforeend", this.innerText) | ||
} | ||
} | ||
} | ||
} | ||
document.onkeydown = (event) => { | ||
if (event.key === 'Enter') input.innerText = eval(input.innerText) | ||
else if (!isNaN(parseInt(event.key)) || event.key === '+' || event.key === '-' || event.key === '*' || event.key === '/' || event.key === '.') { | ||
if (input.innerText == "0") { | ||
input.innerText = event.key | ||
} else { | ||
input.insertAdjacentHTML("beforeend", event.key) | ||
} | ||
} | ||
} | ||
// 取得要觀察的元素 | ||
const div = document.getElementById('intersection-target') | ||
const info = document.getElementById('intersection-info') | ||
// 建立一個觀察者 | ||
// new IntersectionObserver(觀察到變更時執行的 function, 設定) | ||
// entries - 相交的元素 | ||
// owner - IntersectionObserver 設定 | ||
const observer = new IntersectionObserver((entries, owner) => { | ||
console.log(owner) | ||
for (const entry of entries) { | ||
// target - 變動的元素 | ||
// isIntersecting - 是否與可視範圍相交 | ||
// intersectionRatio - 相交比例,相交面積 / 目標元素面積 | ||
// boundingClientRect - 目標元素的尺寸與座標 | ||
// rootBounds - root 的尺寸與座標 | ||
// intersectionRect - 相交的範圍 | ||
// time - 相交的時間,從 IntersectionObserver 被建立的時間開始計算,單位為毫秒 | ||
console.log(entry) | ||
info.innerText = 'isIntersecting = ' + entry.isIntersecting | ||
} | ||
}, { | ||
// 以哪個元素為依據,預設為 null,表示以瀏覽器的可視窗口作為依據 | ||
root: null, | ||
// 計算相交時的偏移量 | ||
rootMargin: "50px", | ||
// 設定觸發的比例門檻,若設定為 0.5,則元素 50% 出現和離開就會觸發,也可以設定為陣列 | ||
threshold: 0.5, | ||
}) | ||
// 停止觀察 | ||
// observer.disconnect() | ||
// 停止觀察某元素 | ||
// observer.unobserve(div1) | ||
observer.observe(div) | ||
</script> | ||
``` | ||
::: | ||
|
||
## 應用 | ||
圖片 Lazy load | ||
::: demo [vanilla] | ||
```html | ||
<html> | ||
<div id="lazyload-container"> | ||
<div class="lazyload-pad"></div> | ||
<img id="lazyload-target" src=""> | ||
<div class="lazyload-pad"></div> | ||
</div> | ||
</html> | ||
<style> | ||
#lazyload-container { | ||
height: 300px; | ||
width: 100%; | ||
position: relative; | ||
overflow-y: scroll; | ||
background: white; | ||
text-align: center; | ||
} | ||
.lazyload-pad { | ||
height: 1500px; | ||
width: 100%; | ||
} | ||
#lazyload-target { | ||
width: 200px; | ||
height: 200px; | ||
border: 1px solid black; | ||
} | ||
</style> | ||
<script> | ||
const imgTarget = document.getElementById('lazyload-target') | ||
const observer = new IntersectionObserver((entries, owner) => { | ||
for (const entry of entries) { | ||
if (entry.isIntersecting) { | ||
imgTarget.src = 'https://picsum.photos/200/200' | ||
} else { | ||
imgTarget.src = '' | ||
} | ||
} | ||
}, { | ||
root: null, | ||
rootMargin: "50px 0px 50px 0px", | ||
threshold: 0, | ||
}) | ||
observer.observe(imgTarget) | ||
</script> | ||
``` | ||
::: | ||
|
||
:::warning 作業 | ||
美化你的計算機,或加入更多的功能 | ||
無限滾動 Infinite scroll | ||
::: demo [vanilla] | ||
```html | ||
<html> | ||
<div id="infinite-container"> | ||
<ul> | ||
<li>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iste sequi voluptatem voluptate magni, sunt itaque assumenda ipsa odit porro, nobis ratione, quibusdam repellendus molestiae odio ipsam eum suscipit quos provident!</li> | ||
<li>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iste sequi voluptatem voluptate magni, sunt itaque assumenda ipsa odit porro, nobis ratione, quibusdam repellendus molestiae odio ipsam eum suscipit quos provident!</li> | ||
<li>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iste sequi voluptatem voluptate magni, sunt itaque assumenda ipsa odit porro, nobis ratione, quibusdam repellendus molestiae odio ipsam eum suscipit quos provident!</li> | ||
<li>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iste sequi voluptatem voluptate magni, sunt itaque assumenda ipsa odit porro, nobis ratione, quibusdam repellendus molestiae odio ipsam eum suscipit quos provident!</li> | ||
<li>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iste sequi voluptatem voluptate magni, sunt itaque assumenda ipsa odit porro, nobis ratione, quibusdam repellendus molestiae odio ipsam eum suscipit quos provident!</li> | ||
<li>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iste sequi voluptatem voluptate magni, sunt itaque assumenda ipsa odit porro, nobis ratione, quibusdam repellendus molestiae odio ipsam eum suscipit quos provident!</li> | ||
<li>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iste sequi voluptatem voluptate magni, sunt itaque assumenda ipsa odit porro, nobis ratione, quibusdam repellendus molestiae odio ipsam eum suscipit quos provident!</li> | ||
</ul> | ||
</div> | ||
</html> | ||
<style> | ||
#infinite-container { | ||
height: 300px; | ||
width: 100%; | ||
position: relative; | ||
overflow-y: scroll; | ||
background: white; | ||
background: gray; | ||
color: black; | ||
} | ||
</style> | ||
<script> | ||
const observer = new IntersectionObserver((entries, owner) => { | ||
if (entries[0].isIntersecting) { | ||
const html = '<li>Lorem ipsum dolor sit amet consectetur adipisicing elit. Iste sequi voluptatem voluptate magni, sunt itaque assumenda ipsa odit porro, nobis ratione, quibusdam repellendus molestiae odio ipsam eum suscipit quos provident!</li>' | ||
document.querySelector("#infinite-container ul").innerHTML += Array(10).fill(html).join(''); | ||
observer.unobserve(entries[0].target); | ||
observer.observe(document.querySelector("#infinite-container li:nth-last-child(2)")); | ||
} | ||
}) | ||
observer.observe(document.querySelector('#infinite-container li:nth-last-child(2)')) | ||
</script> | ||
``` | ||
::: |