header-img
Info :
728x90

 

์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ ์›
์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ ํˆฌ

UI๋Š” ๊ตณ์ด ์ด์˜๊ฒŒ ๋งŒ๋“ค ์ƒ๊ฐ์ด ์—†์—ˆ๊ณ 
์›ํ•˜๋˜ ๊ธฐ๋Šฅ๋งŒ ๋งํ•ด๋ณด์ž๋ฉด..

๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์—ˆ๋˜ ๊ธฐ๋Šฅ ์„ค๋ช…

  1. ์‚ฌ์ง„ ๊ฐฏ์ˆ˜์— ์ œํ•œ ์•ˆ๋‘๊ณ  ์‚ฌ์ง„์„ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์Œ -> ์‚ฌ์‹ค ๋ช‡๊ฐœ๊ฐ€ ๋ ์ง€ ๋ชจ๋ฆ„
  2. ์‚ฌ์ง„์ด๋™
    1.1. ๊ธฐ๋ณธ์€ ์‚ฌ์ง„์ด ์ž๋™์œผ๋กœ ๋„˜์–ด๊ฐ€๊ฒŒ ํ• ๊ฑด๋ฐ
    1.2. ์›ํ•˜๋ฉด ์‚ฌ์ง„์„ ์ง์ ‘ ๋„˜๊ธธ์ˆ˜๋„ ์žˆ์–ด์•ผ ํ•œ๋‹ค
  3. ํƒญ ์ด๋™ํ•˜๋ฉด ์ง€์ •ํ•ด๋‘” ์‚ฌ์ง„ ๋ฐ”๋€Œ์–ด์•ผ ํ•œ๋‹ค

๋ณ„๊ฑฐ ์•„๋‹ˆ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ ๋งˆ์ง€๋ง‰๊ป˜ ์ฒ˜์Œ์„ ๋ฐ”๋ผ๋ณด๊ณ  ์žˆ์–ด์•ผ ํ•˜๋ฉฐ
๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•œ๊ฑฐ๋Š” ์–‘ ์˜†์— ์‚ฌ์ง„ ๋‘์žฅ๋„ ์งœ์ž”ํ•˜๊ณ  ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ์–ด์•ผ ํ•˜๋ฏ€๋กœ

๊ตฌํ˜„ํ•ด๋‘๊ณ ๋„ ์ด๊ฒŒ ์ด๋ ‡๊ฒŒ ๋ณต์žกํ•œ ๊ฒŒ ๋งž๋‚˜ ์‹ถ์€ ๋Š๋‚Œ์ด๋‹ค.

๊ตฌํ˜„ ๊ธฐ์ˆ ๊ณผ ๊ด€๋ จ๋œ ์„ค๋ช…์€ ์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ ์› ์—์„œ ์ •๋ง ์ƒ์„ธํ•˜๊ฒŒ ์ž˜ ๋‹ค๋ค„์ฃผ๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—
๋‚˜๋Š” ์ƒ๋žตํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค. ๐Ÿ˜‡

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๋ฅผ ์„ ํƒํ•˜๋ฉด


์ด๋ ‡๊ฒŒ ๋™๊ทธ๋ฆฌ ์‚ฌ์ง„์ด ๋œจ๊ฒŒ ๋œ๋‹ค !
๐Ÿฑ

์—ฌ๊ธฐ์„œ ๋ณด์™„ํ•ด์•ผํ•  ์ ์€ ๋ถ„๋ช… ๋” ์žˆ์ง€๋งŒ
์ผ๋‹จ ์—ฌ๊ธฐ์„œ ์ž ์‹œ ์Šคํƒ‘ํ•˜๊ณ ...

์ •ํ™•ํ•œ ์š”๊ตฌ์‚ฌํ•ญ์ด ๋“ค์–ด์˜ค๋ฉด ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ํฌ์ŠคํŒ…์„ ์ด์–ด๋‚˜๊ฐ€๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

์˜ˆ์ƒ์น˜๋„ ๋ชปํ–ˆ๋Š”๋ฐ
๋นก์„ธ๋„ค ์ด๊ฑฐ... ๐Ÿต

728x90
๋”๋ณด๊ธฐ
FRONTEND/React