JavaScript 20 Projects - 2 Infinity Scroll
前言
這系列是課程 JavaScript Web Projects: 20 Projects to Build Your Portfolio 的筆記,學習利用 Javascript 做出各種互動網站。
以下是這系列的文章:
Project 1 | Quote Generator |
---|---|
Project 2 | Infinity Scroll |
目標
要實作出一個可以無限往下滑的網頁。
下面是這次要實作的畫面。範例的連結。
建立 html 內容與 CSS 設定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Infinity Scroll</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Titile -->
<h1 class="titleContent">Unsplash API - Infinite Scroll</h1>
<!-- Loader -->
<div class="loader" id="loader" hidden>
<img src="loader.svg" alt="Loading">
</div>
<!-- Image Container -->
<div class="image-container" id="image-container"></div>
<!-- Script -->
<script src="script.js"></script>
</body>
</html>
思考 javascript 所需要的動作
- 從哪裡取得圖片?
- 怎麼展示取得的圖片?
- 要如何判定捲動到畫面的底部?
- 如何持續載入?
動作解讀
從哪裡取得圖片?
- 從 Unsplash 的 Unsplash Developers 申請一組 API key。
- 設定一個 empty array 來裝取得的圖片資料。
- 設定一個
getPhoto()
function 來取得圖片。
const count = 30; //設定載入圖片數量
const apiKey = "YOUR API KEY";
const apiUrl = `https://api.unsplash.com/photos/random?client_id=${apiKey}&count=${count}`;
let photoArray = [];
async function getPhoto() {
try {
const response = await fetch(apiUrl);
photoArray = await response.json();
} catch (error) {
console.log("I am suck !");
}
}
getPhoto();
若 console.log(photoArray);
我們會得到以下的資訊。綠框的內容會用來設定引入圖片的 attributes。
怎麼展示取得的圖片?
- 先設定來操作 HTML 元素的 DOM。
- 建立 function
displayPhotos()
來展示圖片。 - 利用
forEach()
的方式讀取每一張圖片。 - 每一批載入的圖片我們都遇一個 <a> tag 來包全部的 <img>。
- 並利用
setAttribute
為 <a> 以及 <img> 設定 attributes。 - 最後利用
appendChild
來加入 DOM 中。
const imageContainer = document.getElementById('image-container');
function displayPhotos() {
// Run function for each object in photosArray
photosArray.forEach((photo) => {
// Create <a> to link to full photo
const item = document.createElement("a");
item.setAttribute('href', photo.links.html);
item.setAttribute('target', '_blank');
// Create <img> for photo
const img = document.createElement("img");
img.setAttribute('src', photo.urls.regular);
img.setAttribute('alt', alt_description);
img.setAttribute('title', alt_description);
// Put <img> inside <a>, then put both inside imageContainer Element
item.appendChild(img);
imageContainer.appendChild(item);
});
}
並把 displayPhotos()
設置在 getPhoto()
中。
async function getPhoto() {
try {
const response = await fetch(apiUrl);
photoArray = await response.json();
displayPhotos()
} catch (error) {
console.log("I am suck !");
}
}
優化:
目前在 displayPhotos()
裡面 setAttributes
的動作有點重複,為了簡化所以把這部分的功能再獨立出來做一個function setAttributes()
,並把 displayPhotos()
裡的部分做修改。
// Helper Function to Set Attributes on DOM Elements
function setAttributes(element, attributes) {
for (const key in attributes) {
element.setAttribute(key, attributes[key]);
}
}
// Create Elements For Links & Photos, Add to DOM
function displayPhotos() {
// Run function for each object in photosArray
photosArray.forEach((photo) => {
// Create <a> to link to full photo
const item = document.createElement("a");
setAttributes(item, {
href: photo.links.html,
target: "_blank",
});
// Create <img> for photo
const img = document.createElement("img");
setAttributes(img, {
src: photo.urls.regular,
alt: photo.alt_description,
title: photo.alt_description,
});
// Put <img> inside <a>, then put both inside imageContainer Element
item.appendChild(img);
imageContainer.appendChild(item);
});
}
要如何判定捲動到畫面的底部?
下面是課程提供的說明圖。這邊設定觸發載入圖片的條件是
視窗的高度(window.innerHeight)+滑鼠捲動的高度(window.scrollY)大於圖片們內容的高度(document.body.offsetHeight),而更近一步地要在還沒到底之前就觸發,所以在圖片內容的這一邊再多減1000px。若此條件達成,就載入更多的圖片。
from JavaScript Web Projects: 20 Projects to Build Your Portfolio
// Check to see if scrolling near bottom of page, Load More Photos
window.addEventListener("scroll", () => {
if (
window.innerHeight + window.scrollY >= document.body.offsetHeight - 1000
) {
getPhotos();
}
});
如何持續載入?
目前的狀況就是捲動觸發的載入判定只會發生一次,還需要設計一個條件是判定他什麼時候可以再次載入?
這部分需要再思考的幾個點
- 捲動的觸發可能需要跟一個開關條件綁在一起,當捲動條件達成且開關條件也成立時,才能載入圖片。
- 如何知道什麼時候載入會完成來把開關關掉?
解決方法
- 捲動的觸發可能需要跟一個開關條件綁在一起,當捲動條件達成且開關條件也成立時,才能載入圖片。
- 這邊設立一個變數 ready,初始值為 false,且跟捲動觸發條件做 and 運算,當條件達成時才設為 true,才會載入圖片。
let ready = false;
// Check to see if scrolling near bottom of page, Load More Photos
window.addEventListener("scroll", () => {
if (
window.innerHeight + window.scrollY >= document.body.offsetHeight - 1000 &&
ready
) {
ready = false;
getPhotos();
}
});
- 如何知道什麼時候載入會完成來把開關關掉?
就是當每次30張圖片載完之後。
- 利用onload Event來觸發一個
imageLoaded()
function 來計算載入了多少張圖片。- 設定變數 imagesLoaded 以及 totalImages 來計算數量。
-
img.addEventListener("load", imageLoaded);
當有載入就觸發imageLoaded()
。
- 在
imageLoaded()
中設定imagesLoaded++;
計算數量。- 當載入圖片到達預定數量時,把 ready 設為 true 並把 loader 關掉。
const loader = document.getElementById("loader");
let ready = false;
let imagesLoaded = 0;
let totalImages = 0;
let photosArray = [];
// Check if all images were loaded
function imageLoaded() {
imagesLoaded++;
if (imagesLoaded === totalImages) {
ready = true;
loader.hidden = true;
}
}
// Create Elements For Links & Photos, Add to DOM
function displayPhotos() {
imagesLoaded = 0;
totalImages = photosArray.length;
// Run function for each object in photosArray
photosArray.forEach((photo) => {
// Create <a> to link to full photo
const item = document.createElement("a");
setAttributes(item, {
href: photo.links.html,
target: "_blank",
});
// Create <img> for photo
const img = document.createElement("img");
setAttributes(img, {
src: photo.urls.regular,
alt: photo.alt_description,
title: photo.alt_description,
});
// Event Listener, check when each is finished loading
img.addEventListener("load", imageLoaded);
// Put <img> inside <a>, then put both inside imageContainer Element
item.appendChild(img);
imageContainer.appendChild(item);
});
}
最後的Javascript程式碼
const imageContainer = document.getElementById("image-container");
const loader = document.getElementById("loader");
let ready = false;
let imagesLoaded = 0;
let totalImages = 0;
let photosArray = [];
// Unsplash API
const count = 30;
const apiKey = "YOUR API KEY";
const apiUrl = `https://api.unsplash.com/photos/random?client_id=${apiKey}&count=${count}`;
// Check if all images were loaded
function imageLoaded() {
imagesLoaded++;
if (imagesLoaded === totalImages) {
ready = true;
loader.hidden = true;
}
}
// Helper Function to Set Attributes on DOM Elements
function setAttributes(element, attributes) {
for (const key in attributes) {
element.setAttribute(key, attributes[key]);
}
}
// Create Elements For Links & Photos, Add to DOM
function displayPhotos() {
imagesLoaded = 0;
totalImages = photosArray.length;
// Run function for each object in photosArray
photosArray.forEach((photo) => {
// Create <a> to link to full photo
const item = document.createElement("a");
setAttributes(item, {
href: photo.links.html,
target: "_blank",
});
// Create <img> for photo
const img = document.createElement("img");
setAttributes(img, {
src: photo.urls.regular,
alt: photo.alt_description,
title: photo.alt_description,
});
// Event Listener, check when each is finished loading
img.addEventListener("load", imageLoaded);
// Put <img> inside <a>, then put both inside imageContainer Element
item.appendChild(img);
imageContainer.appendChild(item);
});
}
// Get photos from Unsplash API
async function getPhotos() {
try {
const response = await fetch(apiUrl);
photosArray = await response.json();
// console.log(photosArray);
displayPhotos();
} catch (error) {
// Catch Error Here
}
}
// Check to see if scrolling near bottom of page, Load More Photos
window.addEventListener("scroll", () => {
if (
window.innerHeight + window.scrollY >= document.body.offsetHeight - 1000 &&
ready
) {
ready = false;
getPhotos();
}
});
// On Load
getPhotos();
Code Review
可以利用 Chrome 的 Developer Tool 來觀察,當我們每次打開頁面或是重新整理時,載入的時間都會很久,一開始就會讓體驗變差。
為了解決最初載入時時間過久的問題,可以直接去調低最初載入的圖片數量,等需要再載入圖片時,再把需求調回原本的30張。
// Unsplash API
let count = 5;
const apiKey = "YOUR API KEY";
const apiUrl = `https://api.unsplash.com/photos/random?client_id=${apiKey}&count=${count}`;
// Check if all images were loaded
function imageLoaded() {
imagesLoaded++;
if (imagesLoaded === totalImages) {
ready = true;
loader.hidden = true;
count = 30;
}
}
Discussion