Last active
January 10, 2026 11:33
-
-
Save DevShahidul/76a3b3ae9d8b81aa18d52bc5bbc7c666 to your computer and use it in GitHub Desktop.
Dynamic Tab function to use on multiple sections
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function($) { | |
| 'use strict'; | |
| const DEFAULTS = { | |
| container: '.e-parent', | |
| trigger: '.tabs-nav-item', | |
| contentWrapper: '.tab-content-container', | |
| content: '.tab-content', | |
| eventType: 'click', | |
| speed: 300, | |
| hoverDelay: 150, | |
| isActiveFirstItem: true, | |
| activeClass: 'is-active', | |
| shownClass: 'shown', | |
| videoWrapper: '.video-wrapper', | |
| videoSelector: 'video' | |
| }; | |
| /** | |
| * Tab Manager Class | |
| */ | |
| class TabManager { | |
| constructor($container, settings) { | |
| this.$container = $container; | |
| this.settings = settings; | |
| this.$triggers = $container.find(settings.trigger); | |
| this.$wrappers = $container.find(settings.contentWrapper); | |
| this.hoverTimer = null; | |
| this.currentIndex = -1; | |
| this.$videos = null; | |
| this.hasVideos = false; | |
| if (!this.isValid()) { | |
| console.warn('TabManager: Missing required elements'); | |
| return; | |
| } | |
| this.init(); | |
| } | |
| isValid() { | |
| return this.$triggers.length > 0 && this.$wrappers.length > 0; | |
| } | |
| init() { | |
| // Check if this tab component has videos | |
| this.detectVideos(); | |
| this.bindEvents(); | |
| if (this.hasVideos) { | |
| this.initVideoProgression(); | |
| } | |
| if (this.settings.isActiveFirstItem) { | |
| this.activateTab(0); | |
| } | |
| } | |
| detectVideos() { | |
| // Check if container has video-tabs-wrap class or contains videos | |
| const isVideoTabsWrap = this.$container.hasClass('video-tabs-wrap'); | |
| const $videoWrappers = this.$container.find(this.settings.videoWrapper); | |
| const $videos = $videoWrappers.find(this.settings.videoSelector); | |
| if ((isVideoTabsWrap || $videos.length > 0) && $videos.length > 0) { | |
| this.hasVideos = true; | |
| this.$videos = $videos; | |
| } | |
| } | |
| initVideoProgression() { | |
| if (!this.$videos || this.$videos.length === 0) return; | |
| // Bind ended event to all videos | |
| this.$videos.each((index, video) => { | |
| $(video).on('ended', () => { | |
| this.onVideoEnded(index); | |
| }); | |
| }); | |
| } | |
| onVideoEnded(videoIndex) { | |
| // Calculate next index (loop back to 0 if at the end) | |
| const nextIndex = (videoIndex + 1) % this.$triggers.length; | |
| // Activate next tab | |
| this.activateTab(nextIndex); | |
| } | |
| activateTab(index) { | |
| if (index < 0 || index >= this.$triggers.length) return; | |
| if (this.currentIndex === index) return; | |
| const previousIndex = this.currentIndex; | |
| this.currentIndex = index; | |
| // Update triggers | |
| this.$triggers | |
| .removeClass(this.settings.activeClass) | |
| .eq(index) | |
| .addClass(this.settings.activeClass); | |
| // Update content | |
| const { speed, shownClass, content } = this.settings; | |
| this.$wrappers.each((_, wrapper) => { | |
| const $items = $(wrapper).find(content); | |
| if (!$items.eq(index).length) return; | |
| $items | |
| .stop(true, true) | |
| .hide() | |
| .removeClass(shownClass); | |
| $items | |
| .eq(index) | |
| .fadeIn(speed) | |
| .addClass(shownClass); | |
| }); | |
| // Handle video playback if videos exist | |
| if (this.hasVideos) { | |
| this.handleVideoPlayback(index, previousIndex); | |
| } | |
| } | |
| handleVideoPlayback(currentIndex, previousIndex) { | |
| if (!this.$videos) return; | |
| // Pause previous video if exists | |
| if (previousIndex >= 0 && previousIndex < this.$videos.length) { | |
| const prevVideo = this.$videos.get(previousIndex); | |
| if (prevVideo) { | |
| prevVideo.pause(); | |
| prevVideo.currentTime = 0; | |
| } | |
| } | |
| // Play current video | |
| if (currentIndex >= 0 && currentIndex < this.$videos.length) { | |
| const currentVideo = this.$videos.get(currentIndex); | |
| if (currentVideo) { | |
| currentVideo.currentTime = 0; | |
| // Play with promise handling | |
| const playPromise = currentVideo.play(); | |
| if (playPromise !== undefined) { | |
| playPromise.catch(error => { | |
| console.warn('Video autoplay prevented:', error); | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| bindEvents() { | |
| const { eventType, hoverDelay } = this.settings; | |
| this.$triggers.each((index, trigger) => { | |
| const $trigger = $(trigger); | |
| if (eventType === 'click') { | |
| $trigger.on('click', (e) => { | |
| e.preventDefault(); | |
| this.activateTab(index); | |
| }); | |
| } else if (eventType === 'mouseenter') { | |
| $trigger | |
| .on('mouseenter', () => { | |
| if (this.hoverTimer) clearTimeout(this.hoverTimer); | |
| this.hoverTimer = setTimeout(() => this.activateTab(index), hoverDelay); | |
| }) | |
| .on('mouseleave', () => { | |
| if (this.hoverTimer) clearTimeout(this.hoverTimer); | |
| }); | |
| } | |
| }); | |
| } | |
| } | |
| /** | |
| * Initialize tabs | |
| */ | |
| $.fn.initTabs = function(options) { | |
| const settings = $.extend({}, DEFAULTS, options); | |
| return this.each(function() { | |
| new TabManager($(this), settings); | |
| }); | |
| }; | |
| /** | |
| * Auto-initialize on DOM ready | |
| */ | |
| $(function() { | |
| // Click-based video tabs | |
| $('.video-tabs-wrap').initTabs({ | |
| trigger: '.tab-nav', | |
| contentWrapper: '.tabs-contents', | |
| content: '.tab-content', | |
| eventType: 'click', | |
| speed: 400, | |
| videoWrapper: '.video-wrapper', | |
| videoSelector: '.elementor-video' | |
| }); | |
| // Hover-based tabs | |
| $('.e-parent').has('.tabs-nav-item').initTabs({ | |
| eventType: 'mouseenter', | |
| speed: 350, | |
| isActiveFirstItem: false | |
| }); | |
| }); | |
| })(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment