Skip to content

Commit 87f3912

Browse files
committed
add: project 6 이미지 캐러셀 UI 추가
1 parent 67240fb commit 87f3912

File tree

12 files changed

+378
-0
lines changed

12 files changed

+378
-0
lines changed

project6-ImageCarousel/index.html

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html lang="ko">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>이미지 캐러셀</title>
8+
<link
9+
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap"
10+
rel="stylesheet"
11+
/>
12+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/reset.min.css" />
13+
<link rel="stylesheet" href="../src/styles/style.css" />
14+
<link rel="stylesheet" href="./src/style.css" />
15+
</head>
16+
<body>
17+
<main class="wrap"></main>
18+
<script src="/src/main.js" type="module"></script>
19+
</body>
20+
</html>

project6-ImageCarousel/src/App.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import Controller from './Controller.js';
2+
import Dots from './Dots.js';
3+
import Slide from './Slide.js';
4+
5+
export default function App({ $target }) {
6+
const $container = document.createElement('div');
7+
const $slideContainer = document.createElement('div');
8+
$container.className = 'container';
9+
$slideContainer.className = 'slideContainer';
10+
$target.append($container);
11+
$container.append($slideContainer);
12+
13+
this.state = {
14+
imgLength: 7,
15+
slideIndex: 1,
16+
};
17+
18+
const { imgLength } = this.state;
19+
let { slideIndex } = this.state;
20+
21+
for (let i = 1; i <= imgLength; i++) {
22+
new Slide({
23+
$target: $slideContainer,
24+
initialState: {
25+
curIndex: i,
26+
imgLength,
27+
},
28+
});
29+
}
30+
31+
new Dots({ $target: $container, initialState: { imgLength } });
32+
33+
const showSlides = (n) => {
34+
const slides = document.querySelectorAll('.slide');
35+
const dots = document.querySelectorAll('.dot');
36+
37+
if (n > imgLength) {
38+
slideIndex = 1;
39+
}
40+
if (n < 1) {
41+
slideIndex = imgLength;
42+
}
43+
slides.forEach((slide) => (slide.style.display = 'none'));
44+
dots.forEach((dot) => (dot.className = dot.className.replace(' active', '')));
45+
46+
const curSlide = slides[slideIndex - 1];
47+
const curdot = dots[slideIndex - 1];
48+
curSlide.style.display = 'block';
49+
curdot.className += ' active';
50+
};
51+
52+
// slide가 렌더되기 전에 showSlides()가 실행되는 문제가 있어서
53+
// wep API를 이용해 slide 렌더 이후에 showSlides()를 처리할 수 있게끔 함
54+
setTimeout(() => showSlides(slideIndex), 0);
55+
56+
new Controller({ $target: $slideContainer, onClick: (n) => showSlides((slideIndex += n)) });
57+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export default function Controller({ $target, onClick }) {
2+
const $prevButton = document.createElement('a');
3+
const $nextButton = document.createElement('a');
4+
$prevButton.className = 'prev controlButton';
5+
$prevButton.textContent = '❮';
6+
$nextButton.className = 'next controlButton';
7+
$nextButton.textContent = '❯';
8+
$target.append($prevButton, $nextButton);
9+
10+
window.addEventListener('click', (e) => {
11+
if (e.target.classList.contains('prev')) {
12+
onClick(-1);
13+
}
14+
15+
if (e.target.classList.contains('next')) {
16+
onClick(1);
17+
}
18+
});
19+
}

project6-ImageCarousel/src/Dots.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default function Dots({ $target, initialState }) {
2+
const $dotsContainer = document.createElement('div');
3+
$dotsContainer.className = 'dotsContainer';
4+
$target.append($dotsContainer);
5+
6+
this.state = initialState;
7+
8+
$dotsContainer.innerHTML = /* html */ `
9+
${`<span class="dot"></span>`.repeat(this.state.imgLength)}
10+
`;
11+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { IMG_API_END_POINT, KEYWORDS_FOR_IMAGE } from './constants.js';
2+
3+
export default function Slide({ $target, initialState }) {
4+
const $slide = document.createElement('div');
5+
$slide.className = 'slide fade';
6+
$target.append($slide);
7+
8+
this.state = initialState;
9+
10+
const { curIndex, imgLength } = this.state;
11+
12+
fetch(`${IMG_API_END_POINT}${KEYWORDS_FOR_IMAGE[curIndex - 1]}`).then((res) => {
13+
$slide.innerHTML = /* html */ `
14+
<div class="numberText">${curIndex} / ${imgLength}</div>
15+
<img class="slideImg" src="${res.url}" alt="slide image"/>
16+
`;
17+
});
18+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const IMG_API_END_POINT = `https://source.unsplash.com/1600x900/?`;
2+
3+
// 같은 api를 한 번에 여러 번 호출하면 같은 이미지를 불러오는 문제가 있어
4+
// 각각 검색 키워드를 달리 지정해줌
5+
export const KEYWORDS_FOR_IMAGE = ['nature', 'water', 'beach', 'sea', 'park', 'mountain', 'star'];

project6-ImageCarousel/src/main.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import App from './App.js';
2+
3+
const $target = document.querySelector('main');
4+
const $page = document.createElement('div');
5+
$target.append($page);
6+
$page.className = 'project6 page';
7+
8+
new App({ $target: $page });
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
.wrap {
2+
width: 100vw;
3+
height: 100vh;
4+
}
5+
6+
.page {
7+
width: 100%;
8+
height: 100%;
9+
display: flex;
10+
justify-content: center;
11+
align-items: center;
12+
}
13+
14+
.project6 {
15+
box-sizing: border-box;
16+
padding: 1rem;
17+
background-image: var(--bg-color);
18+
font-weight: bold;
19+
}
20+
21+
.project6 .container {
22+
width: 70%;
23+
position: relative;
24+
overflow: hidden;
25+
}
26+
27+
.project6 .slideContainer {
28+
position: relative;
29+
}
30+
31+
.project6 .slideContainer .slide {
32+
display: none;
33+
}
34+
35+
.project6 .slideContainer .slideImg {
36+
width: 100%;
37+
}
38+
39+
.project6 .slideContainer .numberText {
40+
color: #f2f2f2;
41+
font-size: 16px;
42+
padding: 8px 12px;
43+
position: absolute;
44+
top: 0;
45+
background-color: rgba(0, 0, 0, 0.4);
46+
border-radius: 1rem;
47+
margin-top: 10px;
48+
margin-left: 10px;
49+
}
50+
51+
.project6 .fade {
52+
animation-name: fade;
53+
animation-duration: 1.5s;
54+
}
55+
56+
.project6 .controlButton {
57+
cursor: pointer;
58+
position: absolute;
59+
top: 50%;
60+
margin-top: -22px;
61+
padding: 16px;
62+
color: white;
63+
font-weight: bold;
64+
font-size: 18px;
65+
transition: 0.6s ease;
66+
border-radius: 0 3px 3px 0;
67+
}
68+
69+
.project6 .controlButton:hover {
70+
background-color: rgba(0, 0, 0, 0.8);
71+
}
72+
73+
.project6 .controlButton.next {
74+
right: 0;
75+
border-radius: 3px 0 0 3px;
76+
}
77+
78+
.project6 .dotsContainer {
79+
padding: 1rem;
80+
text-align: center;
81+
}
82+
83+
.project6 .dotsContainer .dot {
84+
height: 10px;
85+
width: 10px;
86+
margin: 0 5px;
87+
background-color: #bbb;
88+
border-radius: 50%;
89+
display: inline-block;
90+
transition: background-color 0.6s ease;
91+
}
92+
93+
.project6 .dotsContainer .dot.active {
94+
background-color: #717171;
95+
}
96+
97+
@keyframes fade {
98+
from {
99+
opacity: 0.4;
100+
}
101+
to {
102+
opacity: 1;
103+
}
104+
}
105+
106+
@media (max-width: 800px) {
107+
.project6 .container {
108+
width: 100%;
109+
}
110+
}
111+
112+
@media (min-width: 1400px) {
113+
.project6 .container {
114+
width: 60%;
115+
}
116+
}

src/components/App.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Project2Page from '../../project2-HexColorsGradient/src/Project2Page.js';
77
import Project3Page from '../../project3-RandomQuoteGenerator/src/Project3Page.js';
88
import Project4Page from '../pages/Project4Page.js';
99
import Project5Page from '../pages/Project5Page.js';
10+
import Project6Page from '../pages/Project6Page.js';
1011

1112
export default function App({ $target }) {
1213
new Header({ $target });
@@ -21,6 +22,7 @@ export default function App({ $target }) {
2122
const project3Page = new Project3Page({ $target: $main });
2223
const project4Page = new Project4Page({ $target: $main });
2324
const project5Page = new Project5Page({ $target: $main });
25+
const project6Page = new Project6Page({ $target: $main });
2426

2527
this.route = () => {
2628
$main.innerHTML = ``;
@@ -46,6 +48,9 @@ export default function App({ $target }) {
4648
case 5:
4749
project5Page.render();
4850
break;
51+
case 6:
52+
project6Page.render();
53+
break;
4954
}
5055
} else {
5156
notFoundPage.render();

src/pages/Project6Page.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import App from '../../project6-ImageCarousel/src/App.js';
2+
3+
export default function Project6Page({ $target }) {
4+
const $page = document.createElement('div');
5+
6+
$page.className = 'project6 page';
7+
8+
new App({ $target: $page });
9+
10+
this.render = () => {
11+
$target.append($page);
12+
};
13+
}

0 commit comments

Comments
 (0)