Widget:Carousel: Difference between revisions

From ChaosZeroNightmareWiki
Jump to navigation Jump to search
Created page with "<div id="carousel-container"></div> <style> ===== 轮播容器 =====: #carousel-container { position: relative; width: 100%; max-width: 960px; margin: 0 auto; overflow: hidden; border-radius: 8px; background: #1a1a1a; } #carousel-container .carousel-wrapper { position: relative; width: 100%; padding-top: 56.25%; 16:9: } #carousel-container .carousel-slide { position: absolute; top: 0; left: 0; width: 10..."
 
mNo edit summary
 
Line 2: Line 2:


<style>
<style>
/* ===== 轮播容器 ===== */
#carousel-container {
#carousel-container {
     position: relative;
     position: relative;
Line 9: Line 8:
     margin: 0 auto;
     margin: 0 auto;
     overflow: hidden;
     overflow: hidden;
     border-radius: 8px;
     border-radius: 0;
     background: #1a1a1a;
     background: #1a1a1a;
}
}
Line 16: Line 15:
     position: relative;
     position: relative;
     width: 100%;
     width: 100%;
     padding-top: 56.25%; /* 16:9 */
     padding-top: 56.25%;
}
}


Line 102: Line 101:
}
}


/* ===== 指示器 ===== */
#carousel-container .carousel-indicators {
#carousel-container .carousel-indicators {
     display: flex;
     display: flex;
Line 114: Line 112:
     background: rgba(255, 255, 255, 0.35);
     background: rgba(255, 255, 255, 0.35);
     border: none;
     border: none;
     border-radius: 2px;
     border-radius: 0;
     cursor: pointer;
     cursor: pointer;
     padding: 0;
     padding: 0;
Line 130: Line 128:
     width: 0%;
     width: 0%;
     background: #fb5711;
     background: #fb5711;
     border-radius: 2px;
     border-radius: 0;
     transition: none;
     transition: none;
}
}
Line 153: Line 151:
}
}


/* ===== 左右箭头 ===== */
#carousel-container .carousel-arrow {
#carousel-container .carousel-arrow {
     position: absolute;
     position: absolute;
Line 164: Line 161:
     width: 40px;
     width: 40px;
     height: 40px;
     height: 40px;
     border-radius: 50%;
     border-radius: 0;
     cursor: pointer;
     cursor: pointer;
     font-size: 18px;
     font-size: 18px;
Line 190: Line 187:
}
}


/* ===== 单张时隐藏控件 ===== */
#carousel-container.single-slide .carousel-arrow,
#carousel-container.single-slide .carousel-arrow,
#carousel-container.single-slide .carousel-indicators {
#carousel-container.single-slide .carousel-indicators {
Line 196: Line 192:
}
}


/* ===== 响应式 ===== */
@media (max-width: 600px) {
@media (max-width: 600px) {
     #carousel-container .slide-content {
     #carousel-container .slide-content {
Line 239: Line 234:
         if (slides.length === 1) container.classList.add('single-slide');
         if (slides.length === 1) container.classList.add('single-slide');


         // Build HTML
         // 构建页面跳转链接,不依赖 mw 对象
        function buildHref(pageName) {
            if (!pageName) return '#';
            // 尝试使用 mw.util.getUrl,若不可用则手动拼接
            if (typeof mw !== 'undefined' && mw.util && mw.util.getUrl) {
                return mw.util.getUrl(pageName);
            }
            if (typeof mw !== 'undefined' && mw.config && mw.config.get) {
                var path = mw.config.get('wgArticlePath');
                if (path) return path.replace('$1', encodeURIComponent(pageName));
            }
            // 最终回退:直接拼 /wiki/ 路径
            return '/wiki/' + encodeURIComponent(pageName.replace(/ /g, '_'));
        }
 
         var html = '<div class="carousel-wrapper">';
         var html = '<div class="carousel-wrapper">';


        // Slides
         for (var i = 0; i < slides.length; i++) {
         for (var i = 0; i < slides.length; i++) {
             var s = slides[i];
             var s = slides[i];
             var href = s.jump ? (mw.config.get('wgArticlePath').replace('$1', encodeURIComponent(s.jump))) : '#';
             var href = buildHref(s.jump);
             html += '<a class="carousel-slide' + (i === 0 ? ' active' : '') + '" href="' + href + '">';
             html += '<a class="carousel-slide' + (i === 0 ? ' active' : '') + '" href="' + href + '">';
             html += '<div class="slide-bg" style="background-image:url(\'' + s.image.replace(/'/g, "\\'") + '\')"></div>';
             html += '<div class="slide-bg" style="background-image:url(\'' + s.image.replace(/'/g, "\\'") + '\')"></div>';
Line 259: Line 267:
                 html += '<p class="slide-desc">' + s.desc + '</p>';
                 html += '<p class="slide-desc">' + s.desc + '</p>';
             }
             }
            // Indicators inside content area
             if (slides.length > 1) {
             if (slides.length > 1) {
                 html += '<div class="carousel-indicators">';
                 html += '<div class="carousel-indicators">';
Line 270: Line 277:
         }
         }


        // Arrows
         if (slides.length > 1) {
         if (slides.length > 1) {
             html += '<button class="carousel-arrow carousel-arrow-left">&#10094;</button>';
             html += '<button class="carousel-arrow carousel-arrow-left">&#10094;</button>';
Line 281: Line 287:
         if (slides.length <= 1) return;
         if (slides.length <= 1) return;


        // Carousel logic
         var current = 0;
         var current = 0;
         var slideEls = container.querySelectorAll('.carousel-slide');
         var slideEls = container.querySelectorAll('.carousel-slide');
        var allIndicators = container.querySelectorAll('.carousel-indicator');
         var timer = null;
         var timer = null;
         var INTERVAL = 5000;
         var INTERVAL = 5000;


        function getIndicatorsForSlide(idx) {
         function goTo(idx) {
            // Each slide has its own set of indicators; get them
            var sets = [];
            for (var s = 0; s < slideEls.length; s++) {
                var inds = slideEls[s].querySelectorAll('.carousel-indicator');
                sets.push(inds);
            }
            return sets;
        }
 
         function goTo(idx, fromAuto) {
             if (idx === current) return;
             if (idx === current) return;
             slideEls[current].classList.remove('active');
             slideEls[current].classList.remove('active');
Line 308: Line 302:


         function updateIndicators() {
         function updateIndicators() {
            // Update indicators in ALL slides (so the active slide shows correct state)
             var allSets = container.querySelectorAll('.carousel-slide');
             var allSets = container.querySelectorAll('.carousel-slide');
             for (var s = 0; s < allSets.length; s++) {
             for (var s = 0; s < allSets.length; s++) {
Line 317: Line 310:
                         inds[j].classList.add('visited');
                         inds[j].classList.add('visited');
                     } else if (j === current) {
                     } else if (j === current) {
                        // Force reflow for animation restart
                        inds[j].classList.add('active');
                         void inds[j].offsetWidth;
                         void inds[j].offsetWidth;
                     }
                     }
                 }
                 }
             }
             }
            // Re-trigger the animation by removing and re-adding
             setTimeout(function() {
             setTimeout(function() {
                 var activeInds = container.querySelectorAll('.carousel-indicator.active');
                 var allSets2 = container.querySelectorAll('.carousel-slide');
                 for (var k = 0; k < activeInds.length; k++) {
                 for (var s = 0; s < allSets2.length; s++) {
                     activeInds[k].classList.remove('active');
                     var inds = allSets2[s].querySelectorAll('.carousel-indicator');
                     void activeInds[k].offsetWidth;
                     if (inds[current]) {
                    activeInds[k].classList.add('active');
                        inds[current].classList.add('active');
                    }
                 }
                 }
             }, 10);
             }, 20);
         }
         }


Line 347: Line 338:
         }
         }


        // Arrow events
         container.querySelector('.carousel-arrow-left').addEventListener('click', function(e) {
         container.querySelector('.carousel-arrow-left').addEventListener('click', function(e) {
             e.preventDefault();
             e.preventDefault();
Line 359: Line 349:
         });
         });


        // Indicator events (delegate)
         container.addEventListener('click', function(e) {
         container.addEventListener('click', function(e) {
             var btn = e.target.closest('.carousel-indicator');
             var btn = e.target;
            while (btn && !btn.classList.contains('carousel-indicator')) {
                if (btn === container) { btn = null; break; }
                btn = btn.parentElement;
            }
             if (btn) {
             if (btn) {
                 e.preventDefault();
                 e.preventDefault();
Line 370: Line 363:
         });
         });


        // Pause on hover
         container.addEventListener('mouseenter', function() { clearInterval(timer); });
         container.addEventListener('mouseenter', function() { clearInterval(timer); });
         container.addEventListener('mouseleave', function() { resetTimer(); });
         container.addEventListener('mouseleave', function() { resetTimer(); });

Latest revision as of 16:42, 17 April 2026

<style>

  1. carousel-container {
   position: relative;
   width: 100%;
   max-width: 960px;
   margin: 0 auto;
   overflow: hidden;
   border-radius: 0;
   background: #1a1a1a;

}

  1. carousel-container .carousel-wrapper {
   position: relative;
   width: 100%;
   padding-top: 56.25%;

}

  1. carousel-container .carousel-slide {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
   opacity: 0;
   transition: opacity 0.6s ease-in-out;
   cursor: pointer;
   display: block;
   text-decoration: none;
   color: inherit;

}

  1. carousel-container .carousel-slide.active {
   opacity: 1;
   z-index: 1;

}

  1. carousel-container .carousel-slide .slide-bg {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
   background-size: cover;
   background-position: center;
   background-repeat: no-repeat;

}

  1. carousel-container .carousel-slide .slide-overlay {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
   background: linear-gradient(
       to top,
       rgba(0, 0, 0, 0.85) 0%,
       rgba(0, 0, 0, 0.5) 40%,
       rgba(0, 0, 0, 0.2) 70%,
       rgba(0, 0, 0, 0.1) 100%
   );

}

  1. carousel-container .slide-content {
   position: absolute;
   bottom: 0;
   left: 0;
   right: 0;
   padding: 24px 32px;
   z-index: 2;

}

  1. carousel-container .slide-category {
   display: inline-block;
   border: 1.5px solid #ffffff;
   color: #ffffff;
   font-size: 11px;
   font-weight: 700;
   letter-spacing: 1.5px;
   padding: 3px 10px;
   margin-bottom: 16px;
   text-transform: uppercase;
   background: transparent;

}

  1. carousel-container .slide-title {
   font-size: 24px;
   font-weight: 800;
   color: #ffffff;
   margin: 0 0 8px 0;
   line-height: 1.3;

}

  1. carousel-container .slide-desc {
   font-size: 14px;
   color: rgba(255, 255, 255, 0.85);
   margin: 0 0 16px 0;
   font-weight: 500;
   line-height: 1.5;

}

  1. carousel-container .carousel-indicators {
   display: flex;
   gap: 6px;
   align-items: center;

}

  1. carousel-container .carousel-indicator {
   width: 24px;
   height: 3px;
   background: rgba(255, 255, 255, 0.35);
   border: none;
   border-radius: 0;
   cursor: pointer;
   padding: 0;
   transition: all 0.3s ease;
   position: relative;
   overflow: hidden;

}

  1. carousel-container .carousel-indicator::after {
   content: ;
   position: absolute;
   top: 0;
   left: 0;
   height: 100%;
   width: 0%;
   background: #fb5711;
   border-radius: 0;
   transition: none;

}

  1. carousel-container .carousel-indicator.active::after {
   width: 100%;
   transition: width 5s linear;

}

  1. carousel-container .carousel-indicator.active {
   width: 32px;
   background: rgba(255, 255, 255, 0.2);

}

  1. carousel-container .carousel-indicator.visited {
   background: #fb5711;

}

  1. carousel-container .carousel-indicator.visited::after {
   width: 100%;
   transition: none;

}

  1. carousel-container .carousel-arrow {
   position: absolute;
   top: 50%;
   transform: translateY(-50%);
   z-index: 10;
   background: rgba(0, 0, 0, 0.45);
   border: none;
   color: #fff;
   width: 40px;
   height: 40px;
   border-radius: 0;
   cursor: pointer;
   font-size: 18px;
   display: flex;
   align-items: center;
   justify-content: center;
   opacity: 0;
   transition: opacity 0.3s ease, background 0.3s ease;

}

  1. carousel-container:hover .carousel-arrow {
   opacity: 1;

}

  1. carousel-container .carousel-arrow:hover {
   background: #fb5711;

}

  1. carousel-container .carousel-arrow-left {
   left: 12px;

}

  1. carousel-container .carousel-arrow-right {
   right: 12px;

}

  1. carousel-container.single-slide .carousel-arrow,
  2. carousel-container.single-slide .carousel-indicators {
   display: none !important;

}

@media (max-width: 600px) {

   #carousel-container .slide-content {
       padding: 16px 20px;
   }
   #carousel-container .slide-title {
       font-size: 18px;
   }
   #carousel-container .slide-desc {
       font-size: 12px;
   }
   #carousel-container .carousel-arrow {
       width: 32px;
       height: 32px;
       font-size: 14px;
   }

} </style>

<script> (function() {

   function initCarousel() {
       var dataEl = document.getElementById('carousel-data');
       var container = document.getElementById('carousel-container');
       if (!dataEl || !container) return;
       var count = parseInt(dataEl.getAttribute('data-count')) || 1;
       if (count > 4) count = 4;
       var slides = [];
       for (var i = 1; i <= count; i++) {
           slides.push({
               image: dataEl.getAttribute('data-banner' + i) || ,
               category: dataEl.getAttribute('data-banner' + i + '-category') || ,
               title: dataEl.getAttribute('data-banner' + i + '-title') || ,
               desc: dataEl.getAttribute('data-banner' + i + '-desc') || ,
               jump: dataEl.getAttribute('data-banner' + i + '-jump') || 
           });
       }
       if (slides.length === 0) return;
       if (slides.length === 1) container.classList.add('single-slide');
       // 构建页面跳转链接,不依赖 mw 对象
       function buildHref(pageName) {
           if (!pageName) return '#';
           // 尝试使用 mw.util.getUrl,若不可用则手动拼接
           if (typeof mw !== 'undefined' && mw.util && mw.util.getUrl) {
               return mw.util.getUrl(pageName);
           }
           if (typeof mw !== 'undefined' && mw.config && mw.config.get) {
               var path = mw.config.get('wgArticlePath');
               if (path) return path.replace('$1', encodeURIComponent(pageName));
           }
           // 最终回退:直接拼 /wiki/ 路径
           return '/wiki/' + encodeURIComponent(pageName.replace(/ /g, '_'));
       }

var html = '

';

       container.innerHTML = html;
       if (slides.length <= 1) return;
       var current = 0;
       var slideEls = container.querySelectorAll('.carousel-slide');
       var timer = null;
       var INTERVAL = 5000;
       function goTo(idx) {
           if (idx === current) return;
           slideEls[current].classList.remove('active');
           current = idx;
           slideEls[current].classList.add('active');
           updateIndicators();
           resetTimer();
       }
       function updateIndicators() {
           var allSets = container.querySelectorAll('.carousel-slide');
           for (var s = 0; s < allSets.length; s++) {
               var inds = allSets[s].querySelectorAll('.carousel-indicator');
               for (var j = 0; j < inds.length; j++) {
                   inds[j].classList.remove('active', 'visited');
                   if (j < current) {
                       inds[j].classList.add('visited');
                   } else if (j === current) {
                       void inds[j].offsetWidth;
                   }
               }
           }
           setTimeout(function() {
               var allSets2 = container.querySelectorAll('.carousel-slide');
               for (var s = 0; s < allSets2.length; s++) {
                   var inds = allSets2[s].querySelectorAll('.carousel-indicator');
                   if (inds[current]) {
                       inds[current].classList.add('active');
                   }
               }
           }, 20);
       }
       function next() {
           goTo((current + 1) % slides.length);
       }
       function prev() {
           goTo((current - 1 + slides.length) % slides.length);
       }
       function resetTimer() {
           clearInterval(timer);
           timer = setInterval(next, INTERVAL);
       }
       container.querySelector('.carousel-arrow-left').addEventListener('click', function(e) {
           e.preventDefault();
           e.stopPropagation();
           prev();
       });
       container.querySelector('.carousel-arrow-right').addEventListener('click', function(e) {
           e.preventDefault();
           e.stopPropagation();
           next();
       });
       container.addEventListener('click', function(e) {
           var btn = e.target;
           while (btn && !btn.classList.contains('carousel-indicator')) {
               if (btn === container) { btn = null; break; }
               btn = btn.parentElement;
           }
           if (btn) {
               e.preventDefault();
               e.stopPropagation();
               var idx = parseInt(btn.getAttribute('data-index'));
               if (!isNaN(idx)) goTo(idx);
           }
       });
       container.addEventListener('mouseenter', function() { clearInterval(timer); });
       container.addEventListener('mouseleave', function() { resetTimer(); });
       updateIndicators();
       resetTimer();
   }
   if (document.readyState === 'loading') {
       document.addEventListener('DOMContentLoaded', initCarousel);
   } else {
       initCarousel();
   }

})(); </script>