ASP.NET Core MVC에서 AzCarousel 뷰 컴포넌트 만들기 실습
이 실습에서는 이미지 슬라이드 카루셀을 ViewComponent로 제작하여, 메인 페이지에 조건적으로 표시하는 방법을 학습합니다. 슬라이드는 자동으로 넘겨지며, 사용자가 직접 탐색하거나 일시정지할 수도 있습니다.
전체 소스 코드는 다음 링크를 참고하세요.
https://github.com/VisualAcademy/DotNetNote
🧱 Step 1: 프로젝트 구조 확인 및 정리
카루셀 관련 파일은 다음 위치에 배치합니다:
📁 wwwroot
├── 📁 css
│ └── az-carousel.css
├── 📁 js
│ └── az-carousel.js
📁 Views
└── 📁 Shared
└── 📁 Components
└── 📁 AzCarousel
└── Default.cshtml
📁 ViewComponents
└── AzCarouselViewComponent.cs
🎨 Step 2: CSS 스타일 추가
wwwroot/css/az-carousel.css
파일을 만들고 다음 코드를 추가합니다:
.az-carousel-section {
position: relative;
width: 100%;
height: 500px;
overflow: hidden;
}
.az-slides {
position: relative;
width: 100%;
height: 100%;
}
.az-slide {
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.8s ease-in-out;
}
.az-slide.az-active {
opacity: 1;
z-index: 1;
}
.az-slide img {
width: 100%;
height: 100%;
object-fit: cover;
}
.az-slide-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.5);
padding: 20px;
color: #fff;
text-align: center;
border-radius: 8px;
max-width: 80%;
}
.az-slide-content h2 {
font-size: 1.8rem;
margin-bottom: 10px;
}
.az-slide-content p {
font-size: 1rem;
margin-bottom: 15px;
}
.az-slide-content a {
color: white;
background-color: #007bff;
padding: 8px 16px;
text-decoration: none;
border-radius: 4px;
}
.az-slide-content a:hover {
background-color: #0056b3;
}
.az-nav-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
font-size: 1.8rem;
color: white;
background: rgba(0, 0, 0, 0.5);
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
cursor: pointer;
z-index: 10;
}
.az-nav-arrow.az-left {
left: 20px;
}
.az-nav-arrow.az-right {
right: 20px;
}
.az-progress-indicators {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
align-items: center;
z-index: 20;
}
.az-progress-dot {
width: 40px;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 2px;
overflow: hidden;
position: relative;
cursor: pointer;
}
.az-progress-fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0%;
background: #007aff;
}
.az-pause-button {
margin-left: 12px;
font-size: 1.1rem;
cursor: pointer;
color: white;
background: rgba(0,0,0,0.5);
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.az-pause-button:hover {
background: rgba(0,0,0,0.7);
}
⚙️ Step 3: 자바스크립트 추가
wwwroot/js/az-carousel.js
파일을 만들고 다음 코드를 작성합니다:
const azSlides = document.querySelectorAll('.az-slide');
const azTotalSlides = azSlides.length;
const azProgressIndicators = document.getElementById('az-progress-indicators');
const azSlideDuration = 5000;
let azCurrentIndex = 0;
let azIsPaused = false;
let azTimeout;
let azStartTime;
let azElapsed = 0;
const azProgressDots = [];
for (let i = 0; i < azTotalSlides; i++) {
const dot = document.createElement('div');
dot.className = 'az-progress-dot';
dot.dataset.index = i;
const fill = document.createElement('div');
fill.className = 'az-progress-fill';
dot.appendChild(fill);
dot.addEventListener('click', () => {
clearTimeout(azTimeout);
azElapsed = 0;
azUpdateSlide(i);
});
azProgressIndicators.appendChild(dot);
azProgressDots.push(fill);
}
const azPauseBtn = document.createElement('div');
azPauseBtn.className = 'az-pause-button';
azPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
azProgressIndicators.appendChild(azPauseBtn);
azPauseBtn.addEventListener('click', () => {
azIsPaused = !azIsPaused;
azPauseBtn.innerHTML = `<i class="fas fa-${azIsPaused ? 'play' : 'pause'}"></i>`;
if (azIsPaused) {
azPauseProgress();
} else {
azResumeProgress();
}
});
function azUpdateSlide(index) {
azCurrentIndex = (index + azTotalSlides) % azTotalSlides;
azSlides.forEach((slide, i) => {
slide.classList.toggle('az-active', i === azCurrentIndex);
});
azProgressDots.forEach(dot => {
dot.style.transition = 'none';
dot.style.width = '0%';
});
azElapsed = 0;
if (!azIsPaused) azStartProgress(azSlideDuration);
}
function azStartProgress(duration) {
const dot = azProgressDots[azCurrentIndex];
dot.style.transition = 'none';
dot.style.width = '0%';
requestAnimationFrame(() => {
dot.style.transition = `width ${duration}ms linear`;
dot.style.width = '100%';
});
azStartTime = Date.now();
azTimeout = setTimeout(() => {
azElapsed = 0;
azUpdateSlide(azCurrentIndex + 1);
}, duration);
}
function azPauseProgress() {
const dot = azProgressDots[azCurrentIndex];
const computed = parseFloat(getComputedStyle(dot).width);
const parentWidth = dot.parentElement.offsetWidth;
const percent = (computed / parentWidth) * 100;
dot.style.transition = 'none';
dot.style.width = percent + '%';
azElapsed += Date.now() - azStartTime;
clearTimeout(azTimeout);
}
function azResumeProgress() {
const remaining = azSlideDuration - azElapsed;
azStartProgress(remaining);
}
document.getElementById('az-next-btn').addEventListener('click', () => {
clearTimeout(azTimeout);
azElapsed = 0;
azUpdateSlide(azCurrentIndex + 1);
});
document.getElementById('az-prev-btn').addEventListener('click', () => {
clearTimeout(azTimeout);
azElapsed = 0;
azUpdateSlide(azCurrentIndex - 1);
});
const azCarousel = document.getElementById('az-carousel');
let azTouchStartX = 0;
let azTouchEndX = 0;
azCarousel.addEventListener('touchstart', e => {
azTouchStartX = e.changedTouches[0].screenX;
});
azCarousel.addEventListener('touchend', e => {
azTouchEndX = e.changedTouches[0].screenX;
azHandleSwipe();
});
function azHandleSwipe() {
if (azTouchEndX < azTouchStartX - 30) {
clearTimeout(azTimeout);
azElapsed = 0;
azUpdateSlide(azCurrentIndex + 1);
}
if (azTouchEndX > azTouchStartX + 30) {
clearTimeout(azTimeout);
azElapsed = 0;
azUpdateSlide(azCurrentIndex - 1);
}
}
document.addEventListener('DOMContentLoaded', () => {
azUpdateSlide(0);
});
이 스크립트는 자동 진행, 터치 슬라이드, 이전/다음 버튼 및 일시정지 기능을 포함합니다.
🧩 Step 4: ViewComponent 생성
ViewComponents/AzCarouselViewComponent.cs
파일 생성:
namespace DotNetNote.ViewComponents;
public class AzCarouselViewComponent : ViewComponent
{
public IViewComponentResult Invoke() => View();
}
AzCarousel
이라는 이름으로 호출할 수 있는 뷰 컴포넌트입니다.
🖼️ Step 5: ViewComponent 뷰 작성
Views/Shared/Components/AzCarousel/Default.cshtml
파일 생성 후 다음을 작성합니다:
@* AzCarousel View *@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
<link href="/css/az-carousel.css" rel="stylesheet" />
<div class="container px-0">
<section class="az-carousel-section" id="az-carousel">
<div class="az-slides" id="az-slide-container">
<div class="az-slide az-active">
<img src="https://images.pexels.com/photos/1103970/pexels-photo-1103970.jpeg?auto=compress&cs=tinysrgb&w=1600" alt="Slide 1" />
<div class="az-slide-content">
<h2>Blazor Server Part 1</h2>
<p>회사 홈페이지와 관리자 페이지를 Blazor로 만드는 실전 웹개발 과정</p>
<a href="http://www.devlec.com/?_pageVariable=courseDetail&code=PT001TB4369" target="_blank">강의 보기</a>
</div>
</div>
<div class="az-slide">
<img src="https://images.pexels.com/photos/18105/pexels-photo.jpg?auto=compress&cs=tinysrgb&w=1600" alt="Slide 2" />
<div class="az-slide-content">
<h2>Blazor 게시판 프로젝트 Part 2</h2>
<p>공지사항, 자료실, 답변형 게시판을 Blazor로 직접 구현해보세요</p>
<a href="http://www.devlec.com/?_pageVariable=courseDetail&code=PT001TB4370" target="_blank">강의 보기</a>
</div>
</div>
<div class="az-slide">
<img src="https://images.pexels.com/photos/414612/pexels-photo-414612.jpeg?auto=compress&cs=tinysrgb&w=1600" alt="Slide 3" />
<div class="az-slide-content">
<h2>Blazor 실전 프로젝트 Part 3</h2>
<p>hawaso.com 사이트를 직접 구현하며 실무 핵심 기능을 익히는 강의</p>
<a href="http://www.devlec.com/?_pageVariable=courseDetail&code=PT001TB4371" target="_blank">강의 보기</a>
</div>
</div>
</div>
<div class="az-nav-arrow az-left" id="az-prev-btn"><i class="fas fa-chevron-left"></i></div>
<div class="az-nav-arrow az-right" id="az-next-btn"><i class="fas fa-chevron-right"></i></div>
<div class="az-progress-indicators" id="az-progress-indicators"></div>
</section>
</div>
<script src="/js/az-carousel.js"></script>
필요한 만큼
.az-slide
를 추가해 자유롭게 슬라이드를 구성하세요.
🏠 Step 6: 메인 페이지에 적용
Views/Home/Index.cshtml
에서 카루셀을 조건부로 출력합니다:
@if (DateTime.Now.Second % 2 == 0)
{
@await Component.InvokeAsync("AzCarousel")
}
else
{
<!-- 짝수가 아닌 경우 아무것도 출력하지 않음 -->
}
DateTime.Now.Second % 2 == 0
조건을 통해 짝수 초일 때만 표시되도록 했습니다. 테스트용 조건입니다. 실전에서는 사용자 정보, 로그인 여부 등으로 제어하세요.
🚀 실행 결과
프로젝트를 실행하면 다음과 같은 기능을 갖춘 카루셀이 메인 페이지에 표시됩니다:
- 이미지 슬라이드 자동 전환
- 이전/다음 화살표 버튼
- 슬라이드 진행 상태 표시
- 슬라이드 클릭으로 직접 이동
- 일시정지/재개 버튼
- 모바일 터치 스와이프 지원
✅ 마무리
이제 ASP.NET Core MVC 프로젝트에서 커스터마이즈 가능한 ViewComponent 기반 카루셀을 적용할 수 있게 되었습니다. 컴포넌트 구조로 제작했기 때문에 유지보수와 재사용도 간편합니다.