[實做] 瀑布流

學習到的知識點

CSS

  • position: absolute
  • position: fixed
  • :nth-of-type(2)

JS

  • Asynchronous 非同步
  • await
  • promise
  • jQuery.each()
  • scrollTop,clientHeight,scrollHeight
  • :contains() Selector

簡介

滾動到畫面底部時,會不停出現文章。

HTML

HTML Code

結構

標題與搜尋框

1
2
3
4
5
6
7
8
9
<h1>My Blog</h1>
<div class="filter-container">
<input
type="text"
id="filter"
class="filter"
placeholder="Filter posts..."
/>
</div>

JS 動態產的區域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- dynamin with javascipt -->
<div id="posts-container">
<div class="post">
<div class="number">1</div>
<div class="post-info">
<h2 class="post-title">Post One</h2>
<p class="post-body">
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Tempore,
repellat! Eveniet consequuntur quasi voluptas pariatur cupiditate
fuga voluptatem doloremque dolor sed illo. Laudantium itaque
voluptas fugiat unde placeat quam consectetur?
</p>
</div>
</div>
</div>

Loading 動畫

1
2
3
4
5
<div class="loader">
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
</div>

CSS

每篇文章產生的樣式

數字的絕對定位需要記住,往左上偏移所以是(-15px,-15px),再使用 flex 定位在圓圈中間。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
.post {
/* post要使用相對定位,number才可以定位在這個元素上 */
position: relative;
background-color: #4992d3;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
border-radius: 3px;
padding: 20px;
margin: 40px 0;
display: flex;
width: 80vw;
max-width: 800px;
}

.post .post-title {
margin: 0;
}

.post .post-body {
margin: 15px 0 0;
line-height: 1.3;
}

.post .post-info {
margin-left: 20px;
}

.post .number {
/* 絕對定位 */
position: absolute;
top: -15px;
left: -15px;
font-size: 15px;
/* 圓圈的寬高為40px,radius為50% */
width: 40px;
height: 40px;
border-radius: 50%;
background: #fff;
color: #296ca8;
/* 絕對定位到中間 */
display: flex;
align-items: center;
justify-content: center;
padding: 7px 10px;
}

Loading 動畫

一開始設定opacity: 0不要出現,.show用 Js 控制。

position: fixed,是針對視窗做定位,和absolute很像,運用在蓋板廣告較多。

:nth-of-type(2),選擇第二個元素,它與第三個元素秒數不同,形成動畫效果。

每一個圈圈都加上動畫,再依據時間delay呈現 loading 效果ㄡ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
.loader {
opacity: 0;
display: flex;
position: fixed;
bottom: 50px;
transition: opacity 0.3s ease-in;
}

.loader.show {
opacity: 1;
}

.circle {
background-color: #fff;
width: 10px;
height: 10px;
border-radius: 50%;
margin: 5px;
animation: bounce 0.5s ease-in infinite;
}

.circle:nth-of-type(2) {
animation-delay: 0.1s;
}

.circle:nth-of-type(3) {
animation-delay: 0.2s;
}

@keyframes bounce {
0%,
100% {
transform: translateY(0);
}

50% {
transform: translateY(-10px);
}
}

JS

JS Code

Jquery Code

宣告變數

原生 JS

1
2
3
4
5
6
7
const postsContainer = document.getElementById("posts-container");
const loading = document.querySelector(".loader");
const filter = document.getElementById("filter");

//設定全域變數 global vaiable
let limit = 3; //一次顯示幾篇文章
let page = 1; //預設第一頁

Jquery

1
2
3
4
5
6
$("#posts-container")
$("#loader")
$("#filter")

let limit = 3;
let page = 1;

取得假文章

到 jsonplaceholder 獲取 json api

利用網址的方式取得,再利用網址分析。

https://jsonplaceholder.typicode.com/posts

?_limit=3

一次三篇

?_limit=3&_page=2

換到第二頁面,所以出現的會是 4,5,6

解說 getPosts() showPosts()

async 非同步

在 ES7 裡頭 async 的本質是 promise 的語法,只要 function 標記為 async,就表示裡頭可以撰寫 await 的同步語法,而 await 顧名思義就是「等待」。

因為 fetch 最後回傳的是promise,透過 async 和 await 操作是最適合的。

取得 JSON 格式的連結,透過fetchjson()方法處理檔案,就可以顯示出我們要的內容。

posts 裡面存放了我們取得的使我們要的資料,而 post 是取出來的一筆筆資料。

posts

post

可以利用開發者工具看出來他們資料的差異。

原生 JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Fetch posts from API
async function getPosts() {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts?_limit=${limit}&_page=${page}`
);

const data = await res.json();

return data;
}


//Show posts in DOM
//async採取非同步,把取得的posts接在DOM裡的post區域
async function showPosts() {
const posts = await getPosts();
//取得資料後使用forEach,讀取每一筆資料
posts.forEach((post) => {
const postEl = document.createElement("div");
postEl.classList.add("post");
postEl.innerHTML = `
<div class="number">${post.id}</div>
<div class="post-info">
<h2 class="post-title">${post.title}</h2>
<p class="post-body">${post.body}</p>
</div>
`;
//將讀取到的資料接在postsContainer裡
postsContainer.appendChild(postEl);
});
}

//Show initial posts
//呼叫函式
showPosts();

Jquery

使用$.ajax,使用get方法,再利用$.each讀取每一個值。

  • async function getPosts() {} 轉成 $.ajax({})
  • posts.forEach((post) => {} 轉成 $.each(data, function (index, post) {}

jQuery.each()

.each()舉例來說

.each()是用來遍歷選擇的元素們,可以用在陣列與物件。陣列裡,它會回傳indexarray value

陣列(這邊使用到的方法)
1
2
3
4
5
6
$.each([ 52, 97 ], function( index, value ) {
alert( index + ": " + value );
});

// 0: 52
// 1: 97
物件
1
2
3
4
5
6
7
8
9
10
var obj = {
"flammable": "inflammable",
"duh": "no duh"
};
$.each( obj, function( key, value ) {
alert( key + ": " + value );
});

// flammable: inflammable
// duh: no duh

這部分的 Jquery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let limit = 5;
let page = 1;

function showPosts() {
$.ajax({
url: `https://jsonplaceholder.typicode.com/posts?_limit=${limit}&_page=${page}`,
method: "get",
dataType: "json",
success: function (data) {
console.log(data);
$.each(data, function (index, post) {
const postEl = $("<div>").addClass("post");
postEl.html(`
<div class="number">${post.id}</div>
<div class="post-info">
<h2 class="post-title">${post.title}</h2>
<p class="post-body">${post.body}</p>
</div>
`);
$("#posts-container").append(postEl);
});
},
});
}

畫面到底時,增加文章

解說 showLoading() addEventListener

這邊利用了setTimeout()非同步方法,Loading 動畫先跑 1 秒,跑完後花 0.3 秒跑出文章。

window.addEventListener,這邊監聽的是整個網頁的滾動,所以要使用到 window 的 scroll。

關於如何計算內容底端距離捲軸底端的距離,在另外一篇筆記解說

原生 JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Show loader & fetch more posts
function showLoading() {
loading.classList.add("show");
//持續一千毫秒(一秒)後,去除.show(loading動畫)
setTimeout(() => {
loading.classList.remove("show");
// 300毫秒(0.3秒)後page+1(下一頁產生五個posts)
setTimeout(() => {
page++;
showPosts();
}, 300);
}, 1000);
}

window.addEventListener("scroll", () => {
//將三種方法做變數指派
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
//scrollTop是顯示從最上方到滑到的位置的距離
//clientHeight回傳元素內部高度
//scrollHeight是posts的height
if (scrollTop + clientHeight >= scrollHeight - 5) {
showLoading();
}
});

Jquery

  • loading.classList.add("show")轉成$(".loader").addClass("show")
  • loading.classList.remove("show")轉成$(".loader").removeClass("show")
  • window.addEventListener("scroll", () => {}轉成$(window).scroll(function () {}
  • scrollTop轉成$(document).scrollTop()
  • clientHeight轉成$(window).height()
  • scrollHeight轉成$(document).height()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function showLoading() {
$(".loader").addClass("show");
setTimeout(() => {
$(".loader").removeClass("show");
setTimeout(() => {
page++;
showPosts();
}, 300);
}, 1000);
}

$(window).scroll(function () {
if ($(document).scrollTop() + $(window).height() > $(document).height() - 5) {
showLoading();
}
});

內文搜尋

解說 filterPosts()

使用者輸入的內容轉換成大寫或全部小寫判斷都可以,然後取的每一個 post,再來驗證 title 跟 body 有沒有符合的,如果有 indexOf 就會從 0 開始遞增,然後執行flex,所以判斷才會是>-1。

addEventListener裡的input,只要一輸入就會執行filterPosts,不需要等到按下 Enter。

原生 JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Filter posts by input
function filterPosts(e) {
// 將使用者輸入的內容轉換成大寫
const term = e.target.value.toUpperCase();
// 取得每一個.post
const posts = document.querySelectorAll(".post");

posts.forEach((post) => {
//標題與內容都要被搜尋到
const title = post.querySelector(".post-title").innerText.toUpperCase();
const body = post.querySelector(".post-body").innerText.toUpperCase();
//判斷有無找到內容
//如果輸入的內容(term)在title以及body裡大於-1代表有找到,則顯示(flex)
//沒有則不顯示(none)
if (title.indexOf(term) > -1 || body.indexOf(term) > -1) {
post.style.display = "flex";
} else {
post.style.display = "none";
}
});
}

// input裡有輸入文字,就開始過濾,不需要
filter.addEventListener("input", filterPosts);

Jquery

  • const term = e.target.value.toUpperCase()轉成 const term = $("input").val().toLowerCase()
  • $(.post:contains(‘${term}’)).css("display", "flex")
  • filter.addEventListener("input", filterPosts)轉成 $("input").on("input", function () { filterPosts() });

$(.post:contains(’${term}’)),這行的 Jquery 轉換的寫法是參考別人的寫法,因為原生的寫法太複雜,研究了許久還是無法正確轉換,後來發現 Jquery 有:contains這個功能,簡化了許多原生 JS 步驟,值得紀錄。

:contains() Selector

1
2
3
4
5
6
7
8
9
10
function filterPosts() {
const term = $("input").val().toLowerCase();
$(".post").css("display", "none");
$(`.post:contains('${term}')`).css("display", "flex");
}

$("input").on("input", function () {
filterPosts();
});

參考文章

JS

async function

JavaScript 中的同步與非同步(上):先成為 callback 大師吧!

簡單理解 JavaScript Async 和 Await


[實做] 瀑布流
https://phoebeho.com/Portfolio/20210126/3988292087/
作者
Phoebe
發布於
2021年1月26日
許可協議