์ฐธ๊ณ ๋ธ๋ก๊ทธ ์
์ฐธ๊ณ ๋ธ๋ก๊ทธ ํฌ
UI๋ ๊ตณ์ด ์ด์๊ฒ ๋ง๋ค ์๊ฐ์ด ์์๊ณ
์ํ๋ ๊ธฐ๋ฅ๋ง ๋งํด๋ณด์๋ฉด..
๊ตฌํํ๊ณ ์ถ์๋ ๊ธฐ๋ฅ ์ค๋ช
- ์ฌ์ง ๊ฐฏ์์ ์ ํ ์๋๊ณ ์ฌ์ง์ ๋ณด์ฌ์ฃผ๊ณ ์ถ์ -> ์ฌ์ค ๋ช๊ฐ๊ฐ ๋ ์ง ๋ชจ๋ฆ
- ์ฌ์ง์ด๋
1.1. ๊ธฐ๋ณธ์ ์ฌ์ง์ด ์๋์ผ๋ก ๋์ด๊ฐ๊ฒ ํ ๊ฑด๋ฐ
1.2. ์ํ๋ฉด ์ฌ์ง์ ์ง์ ๋๊ธธ์๋ ์์ด์ผ ํ๋ค- ํญ ์ด๋ํ๋ฉด ์ง์ ํด๋ ์ฌ์ง ๋ฐ๋์ด์ผ ํ๋ค
๋ณ๊ฑฐ ์๋๋ผ๊ณ ์๊ฐํ๋๋ฐ ๋ง์ง๋ง๊ป ์ฒ์์ ๋ฐ๋ผ๋ณด๊ณ ์์ด์ผ ํ๋ฉฐ
๋ด๊ฐ ๊ตฌํํ๊ฑฐ๋ ์ ์์ ์ฌ์ง ๋์ฅ๋ ์ง์ํ๊ณ ๋ณด์ฌ์ฃผ๊ณ ์์ด์ผ ํ๋ฏ๋ก
๊ตฌํํด๋๊ณ ๋ ์ด๊ฒ ์ด๋ ๊ฒ ๋ณต์กํ ๊ฒ ๋ง๋ ์ถ์ ๋๋์ด๋ค.
๊ตฌํ ๊ธฐ์ ๊ณผ ๊ด๋ จ๋ ์ค๋ช
์ ์ฐธ๊ณ ๋ธ๋ก๊ทธ ์ ์์ ์ ๋ง ์์ธํ๊ฒ ์ ๋ค๋ค์ฃผ๊ณ ์๊ธฐ ๋๋ฌธ์
๋๋ ์๋ตํ๋๋ก ํ๊ฒ ๋ค. ๐
Code
์ฌ์ง์ /public/store_image ์ ํด๋๋ฅผ ๋๋์ด์ ์ ์ฅํด๋์๊ณ .
๋ค์ด๋ฐ์ด ์ด๊ฒ๋ฐ์ ์๋๋ ์ด์ ๋ ...๋๋ณด๊ธฐ ์ฐ๋ผ๋ผ ๐ต
๋์ถฉ src ๋ด์ ๊ฒฝ๋ก๋ ์ด๋ฐ ์์ผ๋ก ๋์ด ์๋ค.
๋ ์ธ ๊ณ
assets
css ๋ช๊ฐ๋ slide ์์ ์ฌ์ฉํ ์์ด์ฝ ์ข ์ ์ฅํด๋๋ ค๊ณ ํ๋ค.
1. main.js
/*[main.css]*/
@import "./reset.css";
html,body{
width: 100%;
height: 100%;
}
body{
font-size: 14px;
color: #333333;
}
*{
box-sizing: border-box;
padding: 0;
margin: 0;
}
*::before,*::after{
box-sizing: border-box;
}
a{
text-decoration: none;
color: inherit;
}
button{
background-color: transparent;
border: none;
}
:root{
--color_main: #3366FF;
}
2. reset.css
/*[reset.css]*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
img{
max-width: 100%;
max-height: 100%;
}
button,a{
cursor: pointer;
}
3. icons ํด๋
๊ทธ๋ฅ ์ด ํด๋๋ ์ฝ๋ ์ญ์ญ์ญ ๋์ดํ ๊ฒ์..
๋งํฌ๋ก ์์ด์ฝ ๊ฐ์ ธ์ค๋ ๋ถ๋ถ์ผ ๋ฟ์ด๋๊น...
ic_alert.svg
<svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="18" height="18" viewBox="0 0 18 18"><defs><path id="bpnpn3yn0a" d="M7.554 14.813h3.183a1.689 1.689 0 01-3.183 0zm1.592 2.25a2.813 2.813 0 002.812-2.813.563.563 0 00-.562-.563h-7.5c-.31 0-.541-.014-.699-.04.018-.036.04-.077.066-.123.036-.065.354-.605.46-.8.477-.875.735-1.676.735-2.599V6.75c0-2.656 2.057-4.688 4.688-4.688 2.63 0 4.687 2.032 4.687 4.688v3.375c0 .923.258 1.724.736 2.6.106.194.424.734.46.799.026.046.047.087.065.123-.157.026-.389.04-.698.04a.564.564 0 000 1.126c1.263 0 1.896-.221 1.896-1.002 0-.26-.092-.494-.28-.833-.045-.083-.361-.619-.456-.792-.395-.724-.598-1.355-.598-2.061V6.75c0-3.28-2.563-5.813-5.812-5.813S3.333 3.47 3.333 6.75v3.375c0 .706-.203 1.337-.598 2.06-.094.174-.41.71-.456.793-.188.339-.279.572-.279.833 0 .78.632 1.002 1.896 1.002H6.39a2.813 2.813 0 002.756 2.25z"></path></defs><g fill="none" fill-rule="evenodd"><g transform="translate(-1079 -16) translate(224 7) translate(855 9)"><mask id="1dencd96ob" fill="#fff"><use xlink:href="#bpnpn3yn0a"></use></mask><use fill-rule="nonzero" stroke="currentColor" stroke-width=".3" xlink:href="#bpnpn3yn0a"></use><g fill="currentColor" mask="url(#1dencd96ob)"><path d="M0 0H18V18H0z"></path></g></g></g></svg>
ic_arrow.svg
<svg class="SvgIcon_SvgIcon__root__svg__DKYBi" viewBox="0 0 18 18"><path d="m11.955 9-5.978 5.977a.563.563 0 0 0 .796.796l6.375-6.375a.563.563 0 0 0 0-.796L6.773 2.227a.562.562 0 1 0-.796.796L11.955 9z"></path></svg>
ic_new.svg
<svg class="" width="5" height="5" viewBox="0 0 6 6" style=""><g fill="#fff" fill-rule="nonzero"><path d="M6.647 11L6.647 7.259 6.688 7.259 9.158 11 11 11 11 5 9.353 5 9.353 8.357 9.322 8.357 7.089 5 5 5 5 11z" transform="translate(-123 -375) translate(20 365) translate(98 5)" style=""></path></g></svg>
ic_search.svg
<svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" width="18" height="18" viewBox="0 0 18 18"><defs><path id="qt2dnsql4a" d="M15.727 17.273a.563.563 0 10.796-.796l-4.875-4.875-.19-.165a.563.563 0 00-.764.028 5.063 5.063 0 111.261-2.068.562.562 0 101.073.338 6.188 6.188 0 10-1.943 2.894l4.642 4.644z"></path></defs><g fill="current" fill-rule="evenodd"><use fill="#333" fill-rule="nonzero" stroke="#333" stroke-width=".3" xlink:href="#qt2dnsql4a"></use></g></svg>
components/Slider
1. SlideButton.js
์ฌ๋ผ์ด๋ ๋ฒํผ ๋ง๋ค์ด์ฃผ๋ ๋ถ๋ถ
import { ReactComponent as ArrowIcon } from '../../assets/icons/ic_arrow.svg'
export default function SlideButton({ direction, onClick }) {
return (
<button onClick={onClick} className={`btn-slide-control btn-${direction}`}>
<ArrowIcon width="16" height="16" fill="#333" />
</button>
);
}
2. Slider.css
์ฌ๋ผ์ด๋ ๊ด๋ จ css ์
ํ์ฃผ๋ ๋ถ๋ถ
.slider-area{
position: relative;
overflow: hidden;
height: auto;
}
.slider{
position: relative;
display: block;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-ms-touch-action: pan-y;
touch-action: pan-y;
-webkit-tap-highlight-color: transparent;
}
.slider-list{
position: relative;
overflow: hidden;
display: block;
margin: 0;
}
.slider-track{
position: relative;
position: relative;
left: 50%;
top: 0;
display: flex;
flex-direction: row;
text-align: left;
width: fit-content;
/* transition: -webkit-transform 500ms ease 0s; */
/* transition: transform 500ms ease 0s; */
}
.slider-auto{
}
.slider-item{
position: relative;
height: 100%;
padding: 0 12px;
float: left;
-webkit-filter: brightness(50%);
filter: brightness(50%);
}
.btn-slide-control{
position: absolute;
top: calc(50% - 30px);
padding: 20px 4px;
z-index: 1;
background-color: white;
width: 30px;
height: 60px;
opacity: .5;
border-radius: 15px;
}
.btn-prev{
transform: rotate(180deg);
left : calc((100% - 1200px) / 2)
}
.btn-next{
right: calc((100% - 1200px) / 2);
}
.slider-item div{
display: flex;
flex-direction: column;
align-items: center;
height: 300px;
color: white;
justify-content: center;
font-size: 60px;
font-weight: bold;
}
.slider-item span{
font-size: 18px;
margin-bottom: 1rem ;
}
.current-slide{
-webkit-filter: none;
filter: none;
}
3. Slider.js
๋ด ์ฝ๋์์๋ ์ Slider1 Slider2 ๋ก ๋๋์ด์ ธ ์๋๋ฉด..
์ฌ์ง์ ๋ค๋ฅด๊ฒ ๋ฟ๋ ค์ฃผ๋ ๋ฐฉ๋ฒ์ ์์ง ๊ณ ๋ฏผ ์ํ๊ณ
ํด๋น ํญ ํด๋ฆญํ๋ฉด ๋ฐ๋ก ํด๋น slider๊ฐ ๋ฐ๋ผ๋ณด๋ ์ชฝ์ผ๋ก ์ด๋ํ๋๋ก ๋ง๋ค์๊ธฐ ๋๋ฌธ
๊ทธ๋ฅ Slider ๊ธฐ๋ฅ๋ง ์ดํดํด ์ฃผ์ธ์
import './Slider.css';
import './SliderItem.css';
import React, { useLayoutEffect, useRef, useEffect, useState } from "react";
import SlideButton from './SlideButton'
function useWindowSize() {
const [size, setSize] = useState([0, 0]);
useLayoutEffect(() => {
function updateSize() {
setSize([window.innerWidth, window.innerHeight]);
}
window.addEventListener('resize', updateSize);
updateSize();
return () => window.removeEventListener('resize', updateSize);
}, []);
return size;
}
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
function Slider() {
const [windowWidth, windowHeight] = useWindowSize();
const items = ['/store_image/WC01/์์
์ง์์1.jpg', '/store_image/WC01/์์
์ง์์2.jpg', '/store_image/WC01/์์
์ง์์3.jpg',]
const itemSize = items.length;
const sliderPadding = 40;
const sliderPaddingStyle = `0 ${sliderPadding}px`;
const newItemWidth = getNewItemWidth();
const transitionTime = 500;
const transitionStyle = `transform ${transitionTime}ms ease 0s`;
const ์๋์_์ถ๊ฐ๋ _๋ฐ์ดํฐ์ = 2;
const [currentIndex, setCurrentIndex] = useState(์๋์_์ถ๊ฐ๋ _๋ฐ์ดํฐ์)
const [slideTransition, setTransition] = useState(transitionStyle);
const [isSwiping, setIsSwiping] = useState(false);
const [slideX, setSlideX] = useState(null);
const [prevSlideX, setPrevSlideX] = useState(false);
let isResizing = useRef(false);
let slides = setSlides();
function setSlides() {
let addedFront = [];
let addedLast = [];
var index = 0;
while (index < ์๋์_์ถ๊ฐ๋ _๋ฐ์ดํฐ์) {
addedLast.push(items[index % items.length])
addedFront.unshift(items[items.length - 1 - index % items.length])
index++;
}
return [...addedFront, ...items, ...addedLast];
}
function getNewItemWidth() {
let itemWidth = windowWidth * 0.9 - (sliderPadding * 2)
itemWidth = itemWidth > 1060 ? 1060 : itemWidth;
return itemWidth;
}
useEffect(() => {
isResizing.current = true;
setIsSwiping(true);
setTransition('')
setTimeout(() => {
isResizing.current = false;
if (!isResizing.current)
setIsSwiping(false)
}, 1000);
}, [windowWidth])
useInterval(() => {
handleSlide(currentIndex + 1)
}, !isSwiping && !prevSlideX ? 2000 : null)
function replaceSlide(index) {
setTimeout(() => {
setTransition('');
setCurrentIndex(index);
}, transitionTime)
}
function handleSlide(index) {
setCurrentIndex(index);
if (index - ์๋์_์ถ๊ฐ๋ _๋ฐ์ดํฐ์ < 0) {
index += itemSize;
replaceSlide(index)
}
else if (index - ์๋์_์ถ๊ฐ๋ _๋ฐ์ดํฐ์ >= itemSize) {
index -= itemSize;
replaceSlide(index)
}
setTransition(transitionStyle);
}
function handleSwipe(direction) {
setIsSwiping(true);
handleSlide(currentIndex + direction)
}
function getItemIndex(index) {
index -= ์๋์_์ถ๊ฐ๋ _๋ฐ์ดํฐ์;
if (index < 0) {
index += itemSize;
}
else if (index >= itemSize) {
index -= itemSize;
}
return index;
}
function getClientX(event) {
return event._reactName == "onTouchStart" ? event.touches[0].clientX :
event._reactName == "onTouchMove" || event._reactName == "onTouchEnd" ? event.changedTouches[0].clientX : event.clientX;
}
function handleTouchStart(e) {
setPrevSlideX(prevSlideX => getClientX(e))
}
function handleTouchMove(e) {
if (prevSlideX) {
setSlideX(slideX => getClientX(e) - prevSlideX);
}
}
function handleMouseSwipe(e) {
if (slideX) {
const currentTouchX = getClientX(e);
if (prevSlideX > currentTouchX + 100) {
handleSlide(currentIndex + 1)
}
else if (prevSlideX < currentTouchX - 100) {
handleSlide(currentIndex - 1)
}
setSlideX(slideX => null)
}
setPrevSlideX(prevSlideX => null)
}
return (
<div className="slider-area">
<div className="slider">
<SlideButton direction="prev" onClick={() => handleSwipe(-1)} />
<SlideButton direction="next" onClick={() => handleSwipe(1)} />
<div className="slider-list" style={{ padding: sliderPaddingStyle }}>
<div className="slider-track"
onMouseOver={() => setIsSwiping(true)}
onMouseOut={() => setIsSwiping(false)}
style={{
transform: `translateX(calc(${(-100 / slides.length) * (0.5 + currentIndex)}% + ${slideX || 0}px))`,
transition: slideTransition
}}>
{
slides.map((slide, slideIndex) => {
const itemIndex = getItemIndex(slideIndex);
return (
<div key={slideIndex} className={`slider-item ${currentIndex === slideIndex ? 'current-slide' : ''}`}
style={{ width: newItemWidth || 'auto', height:'auto' }}
onMouseDown={handleTouchStart}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onMouseMove={handleTouchMove}
onMouseUp={handleMouseSwipe}
onTouchEnd={handleMouseSwipe}
onMouseLeave={handleMouseSwipe}
>
<a >
<img src={items[itemIndex]} alt={`banner${itemIndex}`} />
</a>
</div>
)
})
}
</div>
</div>
</div >
</div >
);
}
export default Slider;
4. SliderItem.css
.slider-item{
display: inline-block;
}
.slider-item img{
width: 100%;
height: 100%;
border-radius: 4px;
object-fit: cover;
max-height: 300px;
-webkit-user-drag: none;
-khtml-user-drag: none;
-moz-user-drag: none;
-o-user-drag: none;
user-select: none;
}
์ด๋ ๊ฒ ํ๊ณ ์
๋ด๊ฐ ์ฌ์ง carousel ์ํค๊ณ ์ถ์ ๊ณณ์์ ์ํฌํธ ๋ฐ์์
์จ์ฃผ๋ฉด ๋จ...
๊ทธ๋ฌ๋ฉด ์ํฌ์ผํฐ1 ์ ์ ํํ์ ๋๋
์ธ๋ค์ผ ์ฌ์ง์ฒ๋ผ ๋ฑ๋กํ ์์
์ง์์ ์ฌ์ง๋ค์ด ์ฃผ๋ฃจ๋ฃฉ ๋จ๊ฒ ๋๊ณ
์ํฌ์ผํฐ2๋ฅผ ์ ํํ๋ฉด
์ด๋ ๊ฒ ๋๊ทธ๋ฆฌ ์ฌ์ง์ด ๋จ๊ฒ ๋๋ค !
๐ฑ
์ฌ๊ธฐ์ ๋ณด์ํด์ผํ ์ ์ ๋ถ๋ช
๋ ์์ง๋ง
์ผ๋จ ์ฌ๊ธฐ์ ์ ์ ์คํํ๊ณ ...
์ ํํ ์๊ตฌ์ฌํญ์ด ๋ค์ด์ค๋ฉด ๊ธฐ๋ฅ ์ถ๊ฐํ๋ฉด์ ํฌ์คํ ์ ์ด์ด๋๊ฐ๋ณด๋๋ก ํ๊ฒ ๋ค.
์์์น๋ ๋ชปํ๋๋ฐ
๋นก์ธ๋ค ์ด๊ฑฐ... ๐ต