Ajax 비동기 요청 발생시 로딩 이미지 (로딩 바) 만들기.

작성: 2021.06.13

수정: 2021.06.13

읽는시간: 00 분

Development/Projects-DDIT

반응형

Ajax 비동기 요청 발생시 로딩 바  만들기.


지금 만들고 있는 프로젝트를 SPA(Single Page Application ) 으로 만들고 있습니다.

동기 방식의 페이지 이동을 하는 웹 어플리케이션을 만들었을때와는 다르게 세세히 신경써야 하는 부분이 굉장히 많은데요,

그 중에는 수업시간에서 따로 다룬 적 없는 부분도 종종 있었습니다. 

몇가지 예를 들자면,

1. 페이지 이동시 url 변경 시키기

-> history.pushState 함수를 이용해 해결 했습니다.

 

2. 뒤로가기 이벤트 발생시 처리

-> pushState 발생시 data에 기록해둔 데이터를 바탕으로 $(window).bind("popstate", function(event){} 로 뒤로가기에 대한 바인딩을 해 해결 했습니다.

 

3. 페이지 이동시 url 을 변경 시켜 뒀는데, 그 url 상태에서 새로 고침시 페이지 해당 페이지 그대로 보여주기

package best.gaia.project.controller;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;

import best.gaia.project.dao.ProjectDao;
import best.gaia.project.service.ProjectService;
import best.gaia.utils.CookieUtil;
import best.gaia.utils.exception.ResourceNotFoundException;
import static best.gaia.utils.SessionUtil.*;
@Controller
@RequestMapping("{manager_id:^(?:(?!admin$|view$|restapi$).)*$}/{project_title:^(?:(?!new$|overview$|help$|setting$|activity$).)*$}")
public class ProjectUrlMapper {
	
	@Inject
	private ProjectService service;
	@Inject
	private ProjectDao dao;
	@Inject
	private WebApplicationContext container;
	private ServletContext application;
	
	@PostConstruct
	public void init() {
		application = container.getServletContext();
	}
	
	private static final Logger logger = LoggerFactory.getLogger(ProjectUrlMapper.class);
	
	@GetMapping({""
		,"{pageParam}"
		,"issue/{issue_no}"
		,"milestone/{milest_no}"
		})
	public String projectMenuOverview(
			@PathVariable String manager_id
			,@PathVariable String project_title
			,@PathVariable Optional<String> pageParam 
			,@PathVariable Optional<String> issue_no 
			,@PathVariable Optional<String> milest_no 
			,Authentication authentication
			,HttpSession session
			,Model model
			,HttpServletResponse resp
			) throws UnsupportedEncodingException {
		
		// manager_id랑 project_title로 proj_no 찾아 내기
		Map<String, Object> map = new HashMap<>();
		map.put("manager_id", manager_id);
		map.put("project_title", project_title);
		Integer proj_no = dao.getProjNoFromIdAndTitle(map);
		
		// 존재하는 프로젝트 인지 검사 후 존재하지 않으면 404 에러 응답.
		if(proj_no == null)
			throw new ResourceNotFoundException();
		
		// 접속중인 유저가 해당 proj_no에 대해 조회할 수 있는 권한이 있는지 체크
		int mem_no = getMemberNoFromAuthentication(authentication);
		/* 코드 작성 필요*/
		
		// 조회중인 프로젝트의 proj_no 를 세션에 저장하기
		session.setAttribute("proj_no", proj_no);
		
		// Cookie 에 접속중인 회원의 proj 내에서의 닉네임을 쿠키에 저장하기
		String proj_user_nick = service.getProjectNick(proj_no, mem_no);
		CookieUtil.addCookie("proj_user_nick", proj_user_nick, resp);
		
		// pageParam 없는 요소들은 수동으로 pageParam 넣어주기. 
		// 매핑 패턴을 {pageParam}/{paramNo}하고 paramNo도 Optional로 받으면 하드코딩 하지 않아도 될듯.
		if(issue_no.isPresent()) {
			if("new".equals(issue_no.get())) {
				pageParam = Optional.of("issue/new");
			}else {
				pageParam = Optional.of("issueview");
			}
		}else if(milest_no.isPresent()) {
			if("new".equals(milest_no.get())) {
				pageParam = Optional.of("milestone/new");
			}else {
				pageParam = Optional.of("milestoneview");
			}
		}
		
		if(!pageParam.isPresent())
			pageParam = Optional.of("code");
		
		model.addAttribute("manager_id", manager_id);
		model.addAttribute("project_title", project_title);
		model.addAttribute("pageParam", pageParam.get());
		model.addAttribute("issue_no", issue_no.isPresent() ? issue_no.get() : null);
		model.addAttribute("milest_no", milest_no.isPresent() ? milest_no.get() : null);
		return "view/template/project";
	}
	
}

> 위에 작성한 컨트롤러가 모두 잡아낸다음 요청을 분석해 보내도록 해서 해결 했습니다. issue/ 와 milestone/ 쪽은 {pageParam}/{numParam} 이런식으로 해결하면 조금 더 코드가 간결해질텐데 워낙 일이 밀려 아직 변경하지 못했습니다.

 

이 외에도 여러가지 고려 사항과 고민이 많았는데요, 그 중 하나인 페이지 이동시 로딩 바를 보여주는 기능에 대해 기술 해 보도록 하겠습니다. 

 

위의 영상에서 보이는 것 처럼, 비동기 이동시에는 사용자 입장에서 화면에 변화가 즉각 즉각 일어나지 않으면, 본인의 요청에 대해 응답이 없다고 느껴질 수 있는데요. 영상에서는 그나마 빠릿 빠릿 했는데, 요청에 대한 응답 데이터가 크거나, WAS 서버와 DB 서버 둘중 하나라도 느려지는 상황이 발생하면 UX 적인 측면에서 굉장히 불편함을 느낄 수 있습니다.

 

1.  http://www.ajaxload.info/ 에서 원하는 로딩 이미지를 선택해 생성 하고 다운 받습니다. License는 WTFPL 라이센스를 따르고 있으니 편하게 사용하시면 됩니다.

 

Ajaxload - Ajax loading gif generator

Ajaxload (Beta) <- Hey ! This service is Web 2.0 Preview Create easily your own ajax loader icon : Select the type of indicator you want Enter the background code color you want (tick "Transparent background" if you don't want one Enter the foreground code

www.ajaxload.info

Indicator type 을 정하고 색상을 정한 뒤, Generate it 을 해서 Preview를 확인 한 뒤 downlaod it 을 눌러 다운 받습니다.

 

다운 받은 이미지를 적당한 위치에 저장해둡니다.

 

2.  document가 준비 되면 로딩 화면 돔을 생성 하고 body 에 넣도록 지정을 해 줍니다.

jquery를 사용한다면 , $function(){} 안에 넣어야 준비가 되었는지를 알 수 있습니다.

vanilla javascript를 사용한다면

document.addEventListener("DOMContentLoaded", function() {
  // code...
});

를 이용 하시면 됩니다.

$(function(){
    
	let loading = $('<div id="loading" class="loading"><img id="loading_img" alt="loading" src="/resources/images/loading/ajax-loader.gif" /></div>')
			.appendTo(document.body).hide();
	
	$(window).ajaxStart(function(){
		loading.show();
	}).ajaxStop(function(){
		loading.hide();
	});
})

loading 이라는 돔을 생성 한 뒤, document.body 에 append 시키고 hide 합니다.

그 후에 ajaxStart와 ajaxStop 함수에 .show와 .hide 를 걸어줍니다.

 

이번에는 css 설정도 해줍니다. loading_img의 height 를 조절하면 로딩이미지 크기를 조절 할 수 있습니다. margin-top 와 left에는 해당 크기를 그대로 넣어줘야 중앙에 위치하도록 할 수 있습니다.

@charset "utf-8";

/* 로딩 관관 css */
#loading {
	height: 100%;
	left: 0px;
	position: fixed;
	_position:absolute; 
	top: 0px;
	width: 100%;
	filter:alpha(opacity=50);
	-moz-opacity:0.5;
	opacity : 0.5;
}
.loading {
	z-index: 100;
}
#loading_img{
	position:absolute; 
	top:50%;
	left:50%;
	height:100px;
	margin-top:-100px;	
	margin-left:-100px;	
	z-index: 200;
}

 

이후 실행을 시켜보았습니다.

 

비동기 요청으로 ajax 함수가 실행되는 동안에는 중간에서 로딩 바가 돌기 시작했습니다.

.loading에 background-color 를 주면 로딩 시에 화면을 어둡게 하는 효과가 가능한데요, 그러면 저희는 싱글 페이지의 정체성이 줄어들게 되어서 그렇게 하지 않았습니다.

 

로딩 바 적용 후 영상입니다.

이상입니다.

반응형