지난 포스팅에서 만들었던 슬라이드를 수정하게되었습니다.

포스팅에서도 말했었는데,

기존 방식은 지정된 갯수의 이미지만으로 작업을 한 부분이었기에 문제가 없는데,

이미지 갯수가 동적으로 들어오게되면 스타일이 깨지게됩니다.

그래서 실제로.. 작업하다가 이미지 갯수를 늘리려하니 깨지게되고 이런게 너무 거슬려서 수정하게되었습니다.

 

다만 지난 포스팅과는 방식이 다릅니다.

지난 포스팅은 customPaging 옵션 방식으로 제작한 것이고

이번엔 AsNavFor 방식으로 제작하게되었습니다.

 

 

AsNavFor 옵션 example 확인해보기

react-slick.neostack.com/docs/example/as-nav-for

 

Neostack

The last react carousel you will ever need

react-slick.neostack.com

 

 

끊어서 코드를 확인해보겠습니다.

import 

import React, { useCallback, useEffect, useRef, useState } from 'react';
import Slick from 'react-slick';
import styled, { css } from 'styled-components';

import { LeftOutlined, RightOutlined } from '@ant-design/icons';

Slick이 필수나머지는 꾸미기 위해 import해온 부분들입니다.

 

 

Style

const Wrap = styled.div`
    overflow: hidden;

    & > div + div {
        margin-top: 20px;
    }
`;

const Inner = styled.div`
    position: relative;

    .paging_items {
        filter: grayscale(1);

        &:hover {
            filter: none;
        }
    }

    .slick-current .paging_items {
        filter: none;
    }
`;

const defaultItemStyle = css`
    width: 100%;    
    text-align: center;

    img {
        height: 100%;
        vertical-align: top;
    }
`;

const MainSlickItems = styled.div`
    ${defaultItemStyle}    
    height: 350px;

    img {
        max-width: 100%;
    }
`;

const PagingItems = styled.div`
    ${defaultItemStyle}    
    height: 80px;
    cursor: pointer;
    
    img {
        width: 100%;
    }
`;

const defaultButtonStyle = css`
    position: absolute;
    top: 50%;
    padding: 0;
    width: 30px;
    height: 30px;
    line-height: 1;
    border: none;
    border-radius: 50%;
    background: none;
    outline: none;
    transform:translateY(-50%);
    cursor: pointer;
`;

const PrevButton = styled.button`
    ${defaultButtonStyle}
    left: 0;
`;

const NextButton = styled.button`
    ${defaultButtonStyle}
    right: 0;
`;

const defaultIconStyle = css`
    font-size: 22px;
    color: #dedede;

    &:focus,
    &:hover { 
        color: #666;
    }
`;

const PrevIcon = styled(LeftOutlined)`
    ${defaultIconStyle}
`;

const NextIcon = styled(RightOutlined)`
    ${defaultIconStyle}
`;

스타일은 꾸미기 나름이고 여기서 설명할 부분은 슬릭이 부여하는 클래스들인데요. 

선택된 아이템에는 slick-current가 노출되고 있는 아이템들에는 slick-active가..

그리고 centerMode가 true로 되어있어서 선택 후 가운데에 위치하게되는 객체에 .slick-current 클래스가 붙습니다.

저는 선택된 객체에 스타일을 주기위해 해당 .slick-current 클래스를 사용했고, 

hover를 추가해주기 위해서 Slick을 감싸고 있는 엘리먼트에 스타일을 추가하는 방식으로 진행했습니다.

 

 

Component_1

return 영역을 제외하고 먼저 보겠습니다.

const Slide = ({ images }) => {

	// 1.
    const [mainSlick, setMainSlick] = useState(null);
    const [pagingSlick, setPagingSlick] = useState(null);
    const mainSlickRef = useRef(null);
    const pagingSlickRef = useRef(null);

    useEffect(() => {
        setMainSlick(mainSlickRef.current);
        setPagingSlick(pagingSlickRef.current);
    }, []);
	
    // 2.
    const mainSettings = {
        dots: false,
        arrows: false,
        infinite: true,
        slidesToShow: 1,
        slidesToScroll: 1,
    };
    
	// 2.
    const pagingSettings = {
        dots: false,
        arrows: false,
        centerMode: true,
        slidesToShow: 8,
        swipeToSlide: true,
        focusOnSelect: true,
    };
	
    // 3.
    const onClickPrev = useCallback((ref) => () => ref.current.slickPrev(), []);
    const onClickNext = useCallback((ref) => () => ref.current.slickNext(), []);

    return (
    	//...
    );
};

export default Slide;

1.

ref만 저장해서 작업을 끝내고 싶었지만.. 

state를 사용하지않으면 동작하지않아서 state를 추가했습니다. 

아마 비동기때문인 것 같은데, 좋은 방법이있다면 댓글로 짚어주시면 감사합니다.

컴포넌트 마운트 시에 state 부분에 ref.current를 넣어줍니다.

 

2.

메인이 될 슬라이드와 페이지 번호를 담당할 슬라이드의 셋팅입니다.

 

3.

클릭 함수 하나로 해결하기 위해 대상이 될 ref를 인자로 받아 처리합니다.

 

 

Component_2

return (
  <Wrap>
    <Inner>
      <Slick 
      
      	{/* 1 */}
        ref={mainSlickRef} 
        asNavFor={pagingSlick}
        {...mainSettings}
      >
      
      	{/* 2 */}
        {images.map((v, i) => {
          return (
            <MainSlickItems key={`${v.title}_${i}`}>
            	<img src={v.src} />
            </MainSlickItems>
          )
        })}
      </Slick>

	{/* 3 */}
    <>
      <PrevButton onClick={onClickPrev(mainSlickRef)}>
        <PrevIcon />
      </PrevButton>

      <NextButton onClick={onClickNext(mainSlickRef)}>
        <NextIcon />
      </NextButton>
    </>
    </Inner>

    <Inner>
      <Slick
        ref={pagingSlickRef}
        asNavFor={mainSlick}
        {...pagingSettings}
      >
        {images.map((v, i) => {
          return (
            <PagingItems 
              key={`${v.title}_${i}`}
              className="paging_items"
            >
            	<img src={v.src} />
            </PagingItems>
          )
        })}
      </Slick>

      <>
        <PrevButton onClick={onClickPrev(pagingSlickRef)}>
          <PrevIcon />
        </PrevButton>

        <NextButton onClick={onClickNext(pagingSlickRef)}>
          <NextIcon />
        </NextButton>
      </>
    </Inner>
  </Wrap>
);

1.

ref에 해당 객체를 담아줍니다.

asNavFor에 같이 동작할 객체를 저장한 state를 넣어줍니다.

세팅도 넣어줍니다.

 

2.

슬라이드에 표현할 이미지를 반복시킵니다. (당연히 메인이랑 페이지번호부분이 동일한 이미지여야겠죠)

 

3. 

슬라이드 이전, 다음 버튼입니다.

이 부분에서 ref를 넣어줘야 prev, next 함수 실행 시에 어떤 객체를 대상으로 할 지 설정할 수 있습니다.

 

 

메인 슬라이드와 페이지번호 슬라이드는 매우 비슷합니다.

하지만 스타일이나 조금씩 다른 부분이 있어서 하나로 사용하지 못했습니다.

(따로 컴포넌트로 분리하면 가능할 법도 합니다만...)

 

 

완성코드입니다.

import React, { useCallback, useEffect, useRef, useState } from 'react';
import Slick from 'react-slick';
import styled, { css } from 'styled-components';

import { LeftOutlined, RightOutlined } from '@ant-design/icons';

const Wrap = styled.div`
    overflow: hidden;

    & > div + div {
        margin-top: 20px;
    }
`;

const Inner = styled.div`
    position: relative;

    .paging_items {
        filter: grayscale(1);

        &:hover {
            filter: none;
        }
    }

    .slick-current .paging_items {
        filter: none;
    }
`;

const defaultItemStyle = css`
    width: 100%;    
    text-align: center;

    img {
        height: 100%;
        vertical-align: top;
    }
`;

const MainSlickItems = styled.div`
    ${defaultItemStyle}    
    height: 350px;

    img {
        max-width: 100%;
    }
`;

const PagingItems = styled.div`
    ${defaultItemStyle}    
    height: 80px;
    cursor: pointer;
    
    img {
        width: 100%;
    }
`;

const defaultButtonStyle = css`
    position: absolute;
    top: 50%;
    padding: 0;
    width: 30px;
    height: 30px;
    line-height: 1;
    border: none;
    border-radius: 50%;
    background: none;
    outline: none;
    transform:translateY(-50%);
    cursor: pointer;
`;

const PrevButton = styled.button`
    ${defaultButtonStyle}
    left: 0;
`;

const NextButton = styled.button`
    ${defaultButtonStyle}
    right: 0;
`;

const defaultIconStyle = css`
    font-size: 22px;
    color: #dedede;

    &:focus,
    &:hover { 
        color: #666;
    }
`;

const PrevIcon = styled(LeftOutlined)`
    ${defaultIconStyle}
`;

const NextIcon = styled(RightOutlined)`
    ${defaultIconStyle}
`;

const Slide = ({ images }) => {
    const [mainSlick, setMainSlick] = useState(null);
    const [pagingSlick, setPagingSlick] = useState(null);
    const mainSlickRef = useRef(null);
    const pagingSlickRef = useRef(null);

    useEffect(() => {
        setMainSlick(mainSlickRef.current);
        setPagingSlick(pagingSlickRef.current);
    }, []);

    const mainSettings = {
        dots: false,
        arrows: false,
        infinite: true,
        slidesToShow: 1,
        slidesToScroll: 1,
    };

    const pagingSettings = {
        dots: false,
        arrows: false,
        centerMode: true,
        slidesToShow: 8,
        swipeToSlide: true,
        focusOnSelect: true,
    };

    const onClickPrev = useCallback((ref) => () => ref.current.slickPrev(), []);
    const onClickNext = useCallback((ref) => () => ref.current.slickNext(), []);

    return (
        <Wrap>
            <Inner>
                <Slick 
                    ref={mainSlickRef} 
                    asNavFor={pagingSlick}
                    {...mainSettings}
                >
                    {images.map((v, i) => {
                        return (
                            <MainSlickItems key={`${v.title}_${i}`}>
                                <img src={v.src} />
                            </MainSlickItems>
                        )
                    })}
                </Slick>

                <>
                    <PrevButton onClick={onClickPrev(mainSlickRef)}>
                        <PrevIcon />
                    </PrevButton>

                    <NextButton onClick={onClickNext(mainSlickRef)}>
                        <NextIcon />
                    </NextButton>
                </>
            </Inner>

            <Inner>
                <Slick
                    ref={pagingSlickRef}
                    asNavFor={mainSlick}
                    {...pagingSettings}
                >
                    {images.map((v, i) => {
                        return (
                            <PagingItems 
                                key={`${v.title}_${i}`}
                                className="paging_items"
                            >
                                <img src={v.src} />
                            </PagingItems>
                        )
                    })}
                </Slick>

                <>
                    <PrevButton onClick={onClickPrev(pagingSlickRef)}>
                        <PrevIcon />
                    </PrevButton>

                    <NextButton onClick={onClickNext(pagingSlickRef)}>
                        <NextIcon />
                    </NextButton>
                </>
            </Inner>
        </Wrap>
    );
};

export default Slide;

 

슬라이드 완성 이미지

이미지 출처: pixabay 

 

 

동작도 확인해보겠습니다.

슬라이드 완성 이미지 gif

 

 

이렇게 하면 많은 이미지로도 깨지지않고 작업할 수 있습니다~

 

 

+ Recent posts