swiper 따라하기 - loopCreate.mjs, utils.mjs

물음표살인마·2025년 5월 27일
post-thumbnail

swiper

깃허브 코드를 참고해서 따라하면서 학습해보는 시간을 가져보려고 한다.

왜냐?
swiper에 한이 맺힌게 있다.

// src/core/loop/loopCreate.mjs
import {createElement,  elementChildren , showWarning} from "../../shared/utils.mjs";

export default function loopCreate(slideRealIndex, initial) {
    const swiper = this;
    const { params, slidesEl} = swiper;
    if(!params.loop || (swiper.virtual && swiper.params.virtual.enabled)) return;

    const initSlides = () => {
        /**
         * params.slideClass 클래스명이거나 swiper-slide태그인 요소를 배열로 추출
         * 왜 > Swiper 같은 라이브러리는 사용자 정의 태그(<swiper-slide>)도 지원하고 클래스 기반 슬라이드(<div class="swiper-slide">)도 지원하기 때문에
         */
        const slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
        slides.forEach((el, index) => {
            el.setAttribute('data-swiper-slide-index', index)
        })
    };

    //grid 모드면 행 개수만큼 슬라이드 그룹당 슬라이드 개수를 곱해줌
    const gridEnabled = swiper.grid && params.grid && params.grid.row > 1;
    const slidesPerGroup = params.slidesPerGroup * (gridEnabled ? params.grid.rows : 1);

    //슬라이드 수가 group 단위로 안 맞으면 → group 보정 필요
    //grid 모드인데 행 수가 안 맞으면 → grid 보정 필요
    //무슨 소리인지 모르겠음
    const shouldFillGroup = swiper.slides.length % slidesPerGroup !== 0;
    const shouldFillGrid = gridEnabled && swiper.slides.length % params.grid.rows !== 0;

    //빈 슬라이드를 지정된 개수만큼 만들어 DOM에 붙임
    //swiper.isElement는 <swiper-container>를 사용하는지 여부
    const addBlankSlides = (amountOfSlides) => {
        for(let i = 0; i< amountOfSlides; i +=1){
            const slideEl = swiper.isElement ? 
            createElement('swiper-slide', [params.slideBlankClass]) :
            createElement('div', [params.slideClass, params.slideBlankClass]);
            swiper.slidesEl.append(slideEl)
        }
    }

    //빈 슬라이드를 채워 넣고, 슬라이드를 재계산
    if(shouldFillGroup){
        if(params.loopAddBlankSlides) {
            const slidesToAdd = slidesPerGroup - (swiper.slides.length % slidesPerGroup);
            addBlankSlides(slidesToAdd);
            swiper.recalcSlides();
            swiper.updateSlides();
        } else {
            showWarning(
                'Swiper Loop Warning: The number of slides is not even to slidesPerGroup, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)',
            )
        }
        initSlides();
    } else if(shouldFillGrid){
        if(params.loopAddBlankSlides){
            const slidesToAdd = params.grid.rows - (swiper.slides.length % params.grid.rows);
            addBlankSlides(slidesToAdd);
            swiper.recalcSlides();
            swiper.updateSlides();
        } else {
            showWarning( 'Swiper Loop Warning: The number of slides is not even to grid.rows, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)',)
        }
        initSlides();
    }
    else {
        initSlides();
    }
    //복제된 슬라이드까지 다 세팅된 뒤,
    //실제 슬라이드 위치를 올바르게 조정 (loopFix 함수가 loop offset 계산함)
    swiper.loopFix({
        slideRealIndex,
        direction : params.centeredSlides? undefined : 'next',
        initial
    })

}
if (!params.loop || (swiper.virtual && swiper.params.virtual.enabled)) return;
  • loop 모드가 꺼져있거나
  • virtual slides가 켜져있으면 루프가 필요없어서 함수종료
const initSlides = () => {
  const slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
  slides.forEach((el, index) => {
    el.setAttribute('data-swiper-slide-index', index);
  });
};
  • 슬라이드를 다시 수집해서
  • 각각의 슬라이드에 원래 인덱스를 부여함
  • 복제 슬라이드를 구분하거나, 슬라이드 위치를 추적할 때 사용
const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1;
const slidesPerGroup = params.slidesPerGroup * (gridEnabled ? params.grid.rows : 1);

grid, group부분은 잘 모르곘음. grid모드도 있었나.

const addBlankSlides = (amountOfSlides) => {
  for (...) {
    const slideEl = swiper.isElement
      ? createElement('swiper-slide', [params.slideBlankClass])
      : createElement('div', [params.slideClass, params.slideBlankClass]);
    swiper.slidesEl.append(slideEl);
  }
};
  • 빈 슬라이드를 지정된 개수만큼 만들어 DOM에 붙임
if (shouldFillGroup) {
  if (params.loopAddBlankSlides) {
    const slidesToAdd = slidesPerGroup - (swiper.slides.length % slidesPerGroup);
    addBlankSlides(slidesToAdd);
    swiper.recalcSlides();
    swiper.updateSlides();
  } else {
    showWarning(...);
  }
  initSlides();
}
  • 슬라이드가 부족할 시 보정하는 함수. 빈 슬라이드를 채워넣고 슬라이드를 재계산
  • 언제 부족한건가? 슬라이드 수가 slidesPerGroup 단위로 나누어떨어지지 않으면 부족하다고 판단한다.
const shouldFillGroup = swiper.slides.length % slidesPerGroup !== 0;
  • slidesPerGroup 은 한번에 넘기는 슬라이드 수이다.

슬라이드 수가 slidesPerGroup으로 나누어 떨어지지 않으면 마지막 그룹이 불완전한 슬라이드 묶음이 된다.
-> 루프 모드에서 마지막 남는 슬라이드 그룹이 불완전하다면, 복제할 때 문제가 생기므로 빈 슬라이드를 채워줘야 한다. 예를 들어서 slides의 길이가 10이고 slidesPerGroup이 3이면 마지막 슬라이드에서 1개만 남기 때문에 2개를 추가로 붙인다는 뜻.

swiper.loopFix({
  slideRealIndex,
  direction: params.centeredSlides ? undefined : 'next',
  initial,
});
  • loop 위치 조정
  • loopFix의 목적 : 슬라이더가 현재 복제된 슬라이드에 있다면 눈에 띄지 않게 원래 슬라이드로 jump하여 자연스럽게 루프가 계속 되도록 만들어주는 함수.
  • 이 함수는 다음에!
//src/shared/utils.mjs
import { getWindow, getDocument } from 'ssr-window';

function showWarning(text){
    try {
        console.warn(text);
        return;
    } catch(err){
        //err
    }
}

function createElement(tag, classes = []){
    const el = document.createElement(tag);
    el.classList.add(...(Array.isArray(classes) ? classes : classesToToken(classes)));
    return el;
}

/**
 * 브라우저 환경(ssr대응 포함)에서 DOM요소의 자식 요소들을 선택적으로 가져오는 함수
 * 얘( elementChildren(div, '.my-class') // → div 자식 중 .my-class만 반환
 */
function elementChildren(element, selector=''){
    const window = getWindow(); //브라우저 환경에서는 실제 window, 아니면 모킹 객체
    const children = [...element.children]; //HTML요소중에서 Element노드만 가져온다. 
    if(window.HTMLSlotElement && element instanceof HTMLSlotElement){ //해당 요소가 <slot></slot>일때 처리
        children.push(...element.assignedElements()); //실제 렌더링되는 자식들은 assignedElements()로 접근해야함
    }

    if(!selector){
        //선택자가 없다면 전체 자식 배열 반환
        return children;
    }
    // 있다면 .matches로 해당 셀렉터에 일치하는 요소만 필터링

    return children.filter((el)=> el.matches(selector));
}

export {
    showWarning,
    createElement,
    elementChildren
}
import { getWindow, getDocument } from 'ssr-window';
  • swiper는 ssr환경에서도 동작해야하므로 안전하게 ssr-window를 사용
function showWarning(text){
    try {
        console.warn(text);
        return;
    } catch(err){
        //err
    }
}
  • 콘솔에 경고 메세지 노출
function createElement(tag, classes = []) {
  const el = document.createElement(tag);
  el.classList.add(...(Array.isArray(classes) ? classes : classesToToken(classes)));
  return el;
}
  • 지정한 태그 이름으로 DOM요소 생성
  • 클래스도 함께 붙여줌
function elementChildren(element, selector = '') {
  const window = getWindow();
  const children = [...element.children];

  if (window.HTMLSlotElement && element instanceof HTMLSlotElement) {
    children.push(...element.assignedElements());
  }

  if (!selector) return children;

  return children.filter((el) => el.matches(selector));
}
  • element의 자식 요소들 중 지정된 selector와 일치하는 요소만 반환
  • 예) elementChildren(div, 'my-class')일 경우 div의 자식 중 .my-class만 반환

오늘 배운 것

  • HTMLSlotElement
  • Element.matches()
  • ssr-window
  • mjs
profile
웹 프론트엔드 개발자

0개의 댓글