JSON 비동기 통신과 javascript의 fetch
API를 사용하여 외부 JSON 파일에서 데이터를 동적으로 로드하고 화면에 표시하는 방법을 구현하였다.
function designerData() {
fetch("assets/json/designerData.json") // fetch를 사용하여 비동기적으로 json 파일 로드
.then((res) => res.json()) // json 형태로 파싱
.then((json) => {
data = json.resultData; // 실제 데이터 추출
let html = ``;
data.forEach((element) => {
const formattedPrize = formatPrize(element.userInfo.totalPrice);
const label1 = element.label ? element.label[0] : "";
const label2 = element.label ? (element.label[1] ? element.label[1] : "") : "";
html += `
<li>
... 생략
</li>
`;
});
const designerList = document.querySelector(".designer-list");
designerList.innerHTML = html;
designerList.innerHTML += html;
designerList.style.width = "200%";
});
}
designerData();
디자이너 관련 탭이 4개로 구성되어 있었고, 각 탭마다 필요한 디자이너 관련 데이터의 양이 상당히 많았다. 처음에는 모든 데이터를 한 파일에서 관리하려 했으나, 데이터 로딩 속도가 현저히 느려지는 문제가 발생했다. 이에 대한 해결책으로 JSON 파일을 여러개로 나누고, 필요한 데이터만을 선택적으로 로드할 수 있도록 수정하였다.
$(".sc-designer .tab-list li").click(function () {
const tabIndex = $(this).index(); // 클릭한 탭의 인덱스를 가져온다.
const element = $(this).closest(".header").find("strong");
const text = $(this).text();
designerData(tabIndex, text); // 탭 인덱스를 파라미터로 하여 designerData 함수 호출
element.text(text);
$(this).addClass("active").siblings().removeClass("active");
});
function designerData(tabIndex = 0, text) {
const jsonFiles = [
"assets/json/designerData1.json", // 각 탭마다 JSON 파일 별도로 분리
"assets/json/designerData2.json",
"assets/json/designerData3.json",
"assets/json/designerData4.json",
];
const jsonData = jsonFiles[tabIndex]; // 클릭한 탭 인덱스에 해당하는 파일을 변수에 할당
fetch(jsonData)
.then((res) => res.json())
.then((json) => {
const data = json.resultData;
.
.
.
생략
fetch(jsonData)
👉 fetch는 promise를 반환한다. 네트워크 요청이 성공적으로 완료된 경우 .then()
메소드가 호출된다.
.then((res) => res.json())
👉 .then()
메소드는 응답 객체 (res)
를 인자로 받는다. res.json()
메소드는 본문을 JSON 형태로 파싱(변환)한다. 이 메소드 역시 promise를 반환한다.
.then((json) => { ... }
👉 promise가 성공적으로 반환되면, 다음 then()
메소드가 호출된다. 이 때 파싱된 JSON 객체 (json)
을 인자로 받는다. 이 JSON 객체는 실제로 필요한 데이터를 담고 있다.
💡fetch란?
fetch
는 비동기적으로 네트워크 통신을 수행하여 데이터를 가져오는 함수이다. 또한, HTTP 요청을 쉽게 할 수 있게 해주며,Promise
기반의 API로 비동기 작업을 간결하고 효율적으로 처리할 수 있도록 해준다.
그럼 Promise는?
비동기 작업의 최종 성공 또는 실패를 나타내는 객체로, 비동기 작업이 완료되면 그 결과에 따라 resolve(성공) 또는 reject(실패) 상태가 된다.
gsap과 ScrollTrigger를 사용하여 숫자가 증가하는 애니메이션을 구현하였는데, 이 과정에서 소수점 값을 처리하는 로직에 개선이 필요했다. 우선 처음의 코드는 아래와 같다.
처음에는 counter 값이 목표값보다 작을 때 Math.round()
를 사용해서 정수로 표시하고, counter 값과 목표값이 같을 때는 다시 원래의 목표값을 표시하는 방식으로 구현했었다. 이 방식의 경우 업데이트 되는 과정에서 소수점 값을 유지해야 하는 특정 숫자("98.7%") 표시에 문제가 있었다.
const counter = { counter: 0 };
function countUp(element, number, unit) {
gsap.to(counter, {
counter: number,
duration: 2,
scrollTrigger: {
trigger: ".sc-count",
start: "0% 100%",
end: "100% 0%",
toggleActions: "play none none reverse",
// markers: true,
},
onUpdate: () => {
if (counter.counter < number) {
document.querySelector(element).innerHTML = Math.round(counter.counter) + unit;
} else {
document.querySelector(element).innerHTML = counter.counter + unit;
}
},
});
}
countUp(".count-designer", 24, "만 명+");
countUp(".count-request", 4, "만 건+");
countUp(".count-satisfaction", 98.7, "%");
countUp(".count-domestic", 80, "%");
Number.isInteger(value)
를 사용하여 목표값이 정수인지 소수인지를 판단하고, 소수인 경우 소수점 한자리까지 표시하는 방식으로 수정하였다. 이렇게 수정함으로써 모든 숫자 카운터가 목표값에 도달할 때까지 정확한 형식으로 표시될 수 있도록 하였고, 사용자에게도 정확한 정보를 전달할 수 있게 되었다.
const counter = { counter: 0 };
function countUp(selector, value, unit) {
const element = document.querySelector(selector);
const isInteger = Number.isInteger(value);
gsap.to(counter, {
counter: value,
duration: 2,
scrollTrigger: {
trigger: ".sc-count",
start: "0% 100%",
end: "100% 0%",
// markers: true,
},
onUpdate: () => {
element.innerHTML = isInteger
? counter.counter.toFixed() + unit
: counter.counter.toFixed(1) + unit;
},
});
}
countUp(".count-designer", 24, "만 명+");
countUp(".count-request", 4, "만 건+");
countUp(".count-satisfaction", 98.7, "%");
countUp(".count-domestic", 80, "%");