newstoday-logo
newstoday-logo
30
July, 2024
NEWS TODAY
엘리멘터

엘리멘터 슬라이드
배경에 따라 헤더 스타일 바꾸기

태그
슬라이드엘리멘터캐러셀
조회
댓글
guest
0 Comments
Inline Feedbacks
View all comments

엘리멘터 프로에서 제공하는 슬라이드 위젯은 각 슬라이드별로 컨텐츠의 색상과 위치를 변경하는 옵션을 제공합니다.
하지만 사이트 헤더가 슬라이드 위에 부유하는 경우, 슬라이드 배경과 헤더 요소(로고나 메뉴)의 명도가 비슷하면 시각적으로 문제가 생기는데, 이 글에서는 엘리멘터 슬라이드 배경에 따라 헤더 요소의 스타일을 변경하는 한가지 방안을 소개합니다.

문제가 없는 슬라이드
Lorem ipsum dolor sit amet consectetur adipiscing elit dolor
Click Here
문제가 발생한 슬라이드
Lorem ipsum dolor sit amet consectetur adipiscing elit dolor
Click Here
Previous slide
Next slide
헤더는 슬라이드 위젯의 일부가 아니므로 서로 스타일을 동기할 수 없다
예시 슬라이드와 헤더의 생성

사실 이 기능은 SwiperrealIndexChange 이벤트를 쓰면 손쉽게 구현할 수 있는데, 문제는 엘리멘터 위젯에서 자동으로 생성한 Swiper 인스턴스에 접근하기가 까다롭다.

엘리멘터가 제공하는 블로그 문서도 오래됐고 불친절하며, 실제로 jQuerySwiper 인스턴스를 생성하기전에 사용자의 코드가 먼저 초기화를 시도하는 문제가 있다.

이 글에서는 완전한 Step-by-step으로 설명하지는 않지만, 문제를 해결하는 최종 코드와 예시 템플릿 파일을 제공한다. 새로운 템플릿에서 실제 기능을 구현해 보고자 한다면 아래 내용을 준비한다.

  1. 새로운 템플릿에 슬라이드와 헤더, HTML Widget을 모두 감싸는 wrapper용 컨테이너를 생성한다.
  2. 실제로는 글로벌 헤더 템플릿을 사용하더라도 지금은 예시용 헤더 컨테이너를 생성한다.  sync-header 아이디를 추가한다.
  3. 헤더 컨테이너의 PositionAbsolute로 변경한다.
  4. 엘리멘터 Slides 위젯을 추가하고 sync-slider 아이디를 추가한다.
  5. 슬라이드 위젯의 z-index0으로 변경한다.
  6. 엘리멘터 HTML 위젯을 추가하고 문서의 마지막에 로드 되도록 Navigator 패널을 이용해 최대한 하단으로 이동시킨다.
엘리멘터 HTML Widget으로 자바스크립트 작성

스크립트는 실시간으로 결과를 확인할 수 있도록 HTML Widget을 사용해 페이지 내에서 Inline으로 작성한다.

스크립트는 즉시실행 함수로 캡슐화하고 인스턴스를 생성할 때 3개의 옵션을 받아 처리한다.

  1. 슬라이드의 요소
  2. 슬라이드와 동기화 할 헤더 요소
  3. 동기화가 필요한 슬라이드 인덱스 번호

총 4개의 슬라이드가 있다면, 그 중 두 번째와 세 번째 슬라이드가 활성화 상태일 때는 헤더의 글자색을 바꾸기 위해 클래스를 추가하는 형식이다.

기본 골격은 인스턴스 호출, 생성자 함수, Swiper 초기화 함수, 요소 업데이트를 위한 함수로 나누어 볼 수 있겠다. 클래스나 프로토타입 체이닝 없이 생성자 함수에서 initSwiper.call(this)를 사용해 초기화 함수에서 this를 사용하도록 하였다.

당연하지만 indexForUpdate: [1, 2]는 슬라이드의 두 번째와 세 번째를 선택한다. (0이 첫 번째 슬라이드이므로)

				
					<script>
(function() {
    // Make slide instaces
    const slideHero = new SyncSlides({
        slideEl: '#sync-slider',
        headerEl: '#sync-header',
        indexForUpdate: [1, 2]
    });
    
    // Counstructor
    function SyncSlides(options) {
        this.options = options;
        initSwiper.call(this);
    }
    
    // Main logic
    function initSwiper() {
        console.log(this.options.slideEl); // #sync-slider
    }
    
    // Update elements from selected index
    function updateElements(swiper, options) {

    }
})();
</script>
				
			
Swiper 인스턴스로의 접근

엘리멘터는 내부적으로 Swiper를 사용하는 캐러셀 위젯의 래퍼 요소에 클래스를 추가하고, data() 메서드의 키로 해당 위젯의 인스턴스를 조회힌다.

하지만 엘리멘터 공식 블로그에서 제공하는 All Swiper.js Instances in Elementor are Now Exposed 문서가 너무 오래되었고 그동안 클래스와 변수 이름에도 변화가 생겨 접근이 쉽지 않다.

스크립트를 어디에 작성하고 실행할지에 따라 다르지만, 문서에서 제공하는 방법으로 접근하면  undefined로 나오는 경우가 많다.

				
					<script>
(function() {
    // Code omitted...
    
    // Main logic
    function initSwiper() {
        const slideRef = document.querySelector(this.options.slideEl);
        if(!slideRef) return;
        
        // Swiper instance
        const swiperInstance = jQuery(`${this.options.slideEl} .swiper`).data('swiper-container');

        console.log(swiperInstance); // undefined
    }
    
    // Update elements from selected index
    function updateElements(swiper, options) {

    }
})();
</script>
				
			

이 문제는 엘리멘터 버전 3.11부터 도입 된 Swiper 라이브러리 업그레이드 기능의 추가와, jQuerySwiper 인스턴스를 생성하는데 필요한 지연시간으로 인한 영향으로, 다음과 같이 추가 설정한다.

  1. 워드프레스 관리자 화면 →  Elementor Settings → Features →  Upgrade Swiper Library에서 Default 혹은 Active로 설정
  2. 아래 코드를 추가하여 Swiper 인스턴스를 비동기적으로 획득
				
					<script>
(function() {
    // Make slide instaces
    const slideHero = new SyncSlides({
        slideEl: '#sync-slider',
        headerEl: '#sync-header',
        indexForUpdate: [1, 2]
    });
    
    // Counstructor
    function SyncSlides(options) {
        this.options = options;
        initSwiper.call(this);
    }
    
    // Main logic
    async function initSwiper() {
        const slideRef = document.querySelector(this.options.slideEl);
        if(!slideRef) return;
        
        // Swiper instance
        const swiperInstance = await waitForSwiper(`${this.options.slideEl} .swiper`);

        console.log(swiperInstance); // V{__swiper__: true, support: {…} ...
    }
    
    // Update elements from selected index
    function updateElements(swiper, options) {

    }
    
    // Check Swiper instance
    async function waitForSwiper(selector, timeout = 3000) {
        const startTime = Date.now();
        
        while (Date.now() - startTime < timeout) {
            const swiperElement = jQuery(selector);
            const swiperInstance = swiperElement.data('swiper');
            
            if (swiperInstance) {
                return swiperInstance;
            }
            
            await new Promise(resolve => setTimeout(resolve, 10));
        }
        
        throw new Error('Timeout occurred when finding swiper instance.');
    }
})();
</script>
				
			

전체 코드가 확장되었다. 32-48 라인은 jQuerySwiper 인스턴스를 생성하고 나면 접근하기 위한 비동기 로직이다. 이 로직 없이 addEventListener 혹은 엘리멘터에서 제공하는 elementorFrontend.hooks.addAction 훅을 사용해도 인스턴스 생성 시기와 호출의 간극으로 인한 undefiend가 발생할 수 있다.

17, 22 라인 역시 비동기 로직에 맞춰 수정하였다.

엘리멘터 슬라이드 배경에 따라 원격 요소 업데이트

사용자가 선택한 슬라이드 번호를 통해 슬라이더(swiper)와 헤더 요소에 active 클래스를 토글하는 함수를 추가했다.

  1. 비동기 로직에 따른 try-catch문 추가
  2. active 클래스를 토글하는 업데이트 함수 추가
  3. 슬라이드가 변경 될 때 마다 업데이트 함수를 호출하기 위한 realIndexChange 이벤트 추가
  4. 클로저(closure)를 활용하여 클래스의 멤버 변수처럼 Swiper 인스턴스를 관리

이 시점에서 사용자가 지정한 슬라이드가 활성화 되면 슬라이더(swiper)와 헤더 요소에 active 클래스가 토글되고, CSS 스타일을 추가할 수 있다.

				
					<script>
(function() {
    // Code omitted...
    
    // Counstructor
    function SyncSlides(options) {
        this.options = options;
        this.swiper = null;
        this.initPromise = initSwiper.call(this);
    }
    
    // Main logic
    async function initSwiper() {
        const slideRef = document.querySelector(this.options.slideEl);
        if(!slideRef) return;
        
        try {
            // Swiper instance
            this.swiper = await waitForSwiper(`${this.options.slideEl} .swiper`);
            
            updateElements(this.swiper, this.options);
            
            this.swiper.on('realIndexChange', function() {
                updateElements(this.swiper, this.options);
            }.bind(this));
            
        } catch (error) {
            console.error('Failed to find swiper instance', error);
        }
    }
    
    // Update class from selected index
    function updateElements(swiper, options) {
        const activeSlide = swiper.slides[swiper.activeIndex];
        const slideIndex = parseInt(activeSlide.getAttribute('data-swiper-slide-index'));
        const header = options.headerEl ? document.querySelector(options.headerEl) : null;
        
        if (options.indexForUpdate.includes(slideIndex)) {
            swiper.el.classList.add('active');
            header?.classList.add('active');
        } else {
            swiper.el.classList.remove('active');
            header?.classList.remove('active');
        }
    }
    
    // Code omitted...

})();
</script>
				
			
엘리멘터 슬라이드 배경에 따른 클래스 추가
슬라이더와 헤더 요소 모두 active 클래스가 추가되었다
사용자가 지정한 슬라이드일 경우 헤더 상태 스타일링

헤더(#sync-header) 요소의 Custom CSS 필드에 스타일을 추가한다 .active 클래스가 활성화 되었을 때를 표현하면 된다.

				
					/* 
 * For sync with slides
 */

selector svg,
selector .elementor-widget-icon-list .elementor-icon-list-text {
    transition: all .5s;
}

/* When selectd on a slider */
selector.active svg {
    fill: #404040 !important;
}
selector.active .elementor-widget-icon-list .elementor-icon-list-text {
    color: #404040;
}
				
			
사용자가 지정한 슬라이드일 경우 네비게이션과 페이지네이션 스타일링

엘리멘터 슬라이드 위젯은 콘트롤 패널에서 슬라이드 컨텐츠 스타일을 개별적으로 수정할 수 있지만 네비게이션과 페이지네이션은 공통 스타일을 사용하므로 이것도 스타일링이 필요하다.

슬라이더(#sync-slider) 요소의 Custom CSS 필드에 스타일을 추가한다 .active 클래스가 활성화 되었을 때를 표현하면 된다.

				
					/* Navigation customization */
selector .elementor-swiper-button i,
selector .elementor-swiper-button svg {
    visibility: hidden;
}
selector .elementor-swiper-button {
    border: 2px solid #fff;
    border-right-width: 0;
    border-bottom-width: 0;
    transition: border .5s;
    overflow: hidden;
}
selector .elementor-swiper-button-prev {
    left: 30px;
    transform: rotate(-45deg);
}
selector .elementor-swiper-button-next {
    right: 30px;
    transform: rotate(135deg);
}

/* Pagination customization */
selector .swiper .swiper-pagination-bullet {
    background: #fff;
    transition: border .5s;
}


/* 
 * For sync with slides
 */
 
/* Navigation when selectd on a slider */
selector .swiper.active .elementor-swiper-button {
    border-color: #404040; 
}

/* Pagination when selectd on a slider */
selector .swiper.active .swiper-pagination-bullet {
    background: #404040;
    opacity: .3;
}
selector .swiper.active .swiper-pagination-bullet-active {
    opacity: 1;
}
				
			

네비게이션의 화살표 아이콘은 사용자가 변경할 수 있는 옵션을 제공하지 않는다. 기본 아이콘 모양새가 트렌드에 맞지 않으니 감추고, 단순하게 border만으로 표현하도록 변경하였다.

슬라이드 위젯의 모든 기본 값은 흰 색인데 페이지네이션만 검은 색이다. 이 부분도 통일성을 주어 수정하였다.

슬라이드 인스턴스에서 모든 Swiper API 사용

엘리멘터 위젯에서 생성된 인스턴스를 성공적으로 가져왔으니 이제 모든 Swiper API를 사용할 수 있다. Swiper 인스턴스는 비동기적으로 초기화되어 Promise를 통해 접근한다.

동기 기능이 필요한 슬라이드의 수 만큼 SyncSlides 함수의 인스턴스를 만들면 한 페이지 내에서 여러 슬라이드가 각각 독립적으로 다른 요소들과 동기할 수 있다.

리팩토링을 한다면, initSwiper() 함수에 위치한 realIndexChange 이벤트 부분을 새로 추가한 10번 라인의 Promiose 비동기 로직 안쪽으로 이동시킬 수 있겠다.

				
					<script>
(function() {
    // Make slide instaces
    const slideHero = new SyncSlides({
        slideEl: '#sync-slider',
        headerEl: '#sync-header',
        indexForUpdate: [1, 2]
    });
    
    // Use any Swiper API methods & events
    slideHero.initPromise.then(() => {
        if (slideHero.swiper) {
            slideHero.swiper.on('realIndexChange', function() {
                console.log(slideHero.swiper.realIndex);
            });
        }
    });
    
    // Counstructor
    // Code omitted...
</script>
				
			
문제가 없는 슬라이드
Lorem ipsum dolor sit amet consectetur adipiscing elit dolor
Click Here
문제가 해결된 슬라이드
Lorem ipsum dolor sit amet consectetur adipiscing elit dolor
Click Here
문제가 해결된 슬라이드
Lorem ipsum dolor sit amet consectetur adipiscing elit dolor
Click Here
Previous slide
Next slide
전체 코드
마치며

네비게이션과 페이지네이션을 완전히 커스텀하고 싶은 경우도 있다. 그러려면 슬라이드 위젯에서 자동으로 생성한 인스턴스의 파라미터와 이벤트를 모두 해제하고 추가 작업을 마친 후 인스턴스를 업데이트 해야하는데, 이 과정을 거치고 나면 위젯 콘트롤러와의 모든 이벤트 연결이 끊어져 사실상 위젯으로의 가치를 상실한다.

				
					// Make slide instaces
const slideHero = new SyncSlides({
    slideEl: '#sync-slider',
    headerEl: '#sync-header',
    indexForUpdate: [1],
    
    prevEl: '.syncSlide-swiper-button-prev',
    nextEl: '.syncSlide-swiper-button-next'
});

// Use any Swiper API methods & events
slideHero.initPromise.then(() => {
    if (slideHero.swiper) {
        // Override navigation elements
        slideHero.swiper.params.navigation.prevEl = slideHero.options.prevEl;
        slideHero.swiper.params.navigation.nextEl = slideHero.options.nextEl;
        
        // Re-init navigation
        slideHero.swiper.navigation.destroy();
        slideHero.swiper.navigation.init();
        slideHero.swiper.navigation.update();
    }
});
				
			

대략 이러한 과정을 거쳐 기존의 인스턴스를 업데이트 한다

개인이 사용하는 용도라면 지금까지의 코드를 적당히 다듬어 사용해도 문제가 없지만 외부에 공개되기에는 많이 부족하다. 코드를 개선한 실제 엘리멘터 템플릿은 아래 링크를 참고한다.

목록으로 돌아가려면 화면 하단 X 버튼 클릭