|
|
| 第1行: |
第1行: |
| <script type="text/javascript"> | | <noinclude> |
| document.addEventListener('DOMContentLoaded', function () {
| | 本Widget为少女前线系列多游戏首页提供切换交互逻辑。 |
| var switcher = document.querySelector('.game-switcher'); | | 不接受参数,直接在首页调用:<code><nowiki>{{#widget:GFHomepageSwitcher}}</nowiki></code> |
| if (!switcher) return;
| | </noinclude><onlyinclude><script> |
| | (function () { |
| | 'use strict'; |
|
| |
|
| var heroWrap = document.getElementById('gs-hero-wrap'); | | function init() { |
| var heroImg = null; | | var tabs = document.querySelectorAll('#gf-homepage .gf-tab'); |
| | var heroSlides = document.querySelectorAll('#gf-homepage .gf-hero-slide'); |
| | var contentPanels = document.querySelectorAll('#gf-homepage .gf-content-panel'); |
|
| |
|
| if (heroWrap && firstBtn) { | | if (!tabs.length) return; |
| heroImg = document.createElement('img');
| |
| heroImg.id = 'gs-hero-img';
| |
| heroImg.src = firstBtn.dataset.hero;
| |
| heroWrap.appendChild(heroImg);
| |
| }
| |
| var loaded = {};
| |
| var prefetching = {};
| |
| var storageKey = 'gs-cache-';
| |
|
| |
|
| // ============ 初始化双层头图 ============
| | tabs.forEach(function (tab) { |
| var layerStyle = 'position:absolute;top:0;left:0;width:100%;height:100%;background-size:cover;background-position:center;transition:opacity 0.4s ease';
| | tab.addEventListener('click', function () { |
| | var game = this.getAttribute('data-game'); |
|
| |
|
| var layerA = document.createElement('div');
| | tabs.forEach(function (t) { |
| var layerB = document.createElement('div');
| | t.classList.remove('active'); |
| layerA.className = 'gs-hero-layer';
| | t.setAttribute('aria-selected', 'false'); |
| layerB.className = 'gs-hero-layer';
| | }); |
| layerA.style.cssText = layerStyle + ';opacity:1';
| | this.classList.add('active'); |
| layerB.style.cssText = layerStyle + ';opacity:0';
| | this.setAttribute('aria-selected', 'true'); |
|
| |
|
| if (heroWrap) {
| | heroSlides.forEach(function (s) { |
| heroWrap.appendChild(layerA);
| | s.classList.remove('active'); |
| heroWrap.appendChild(layerB);
| | if (s.id === 'hero-' + game) { |
| }
| | s.classList.add('active'); |
| | } |
| | }); |
|
| |
|
| var activeLayer = layerA;
| | contentPanels.forEach(function (p) { |
| var inactiveLayer = layerB;
| | p.classList.remove('active'); |
| | | if (p.id === 'content-' + game) { |
| // ============ 初始化按钮和头图 ============
| | p.classList.add('active'); |
| var firstBtn = switcher.querySelector('.gs-tab-btn');
| | } |
| if (firstBtn) {
| | }); |
| firstBtn.classList.add('gs-active');
| | }); |
| firstBtn.setAttribute('aria-selected', 'true');
| |
| if (heroWrap) {
| |
| activeLayer.style.backgroundImage = 'url(' + firstBtn.dataset.hero + ')';
| |
| }
| |
| }
| |
| | |
| // 默认面板直接可见,不走入场动画
| |
| var defaultPanel = switcher.querySelector('.gs-content-panel.gs-active');
| |
| if (defaultPanel) {
| |
| defaultPanel.classList.add('gs-visible');
| |
| }
| |
| | |
| // ============ 点击 ============
| |
| switcher.addEventListener('click', function (e) {
| |
| var btn = e.target.closest('.gs-tab-btn');
| |
| if (btn) activate(btn);
| |
| });
| |
| | |
| // ============ 键盘 ============
| |
| switcher.addEventListener('keydown', function (e) {
| |
| var btn = e.target.closest('.gs-tab-btn');
| |
| if (!btn) return;
| |
| if (e.key === 'Enter' || e.key === ' ') {
| |
| e.preventDefault(); | |
| activate(btn);
| |
| }
| |
| if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
| |
| e.preventDefault();
| |
| var btns = Array.from(switcher.querySelectorAll('.gs-tab-btn'));
| |
| var idx = btns.indexOf(btn);
| |
| var next = e.key === 'ArrowRight' ? idx + 1 : idx - 1;
| |
| if (next >= 0 && next < btns.length) btns[next].focus();
| |
| }
| |
| });
| |
| | |
| // ============ 悬停预加载 ============
| |
| switcher.addEventListener('mouseenter', function (e) {
| |
| var btn = e.target.closest('.gs-tab-btn');
| |
| if (!btn) return;
| |
| var panelId = btn.dataset.panel;
| |
| var pageName = btn.dataset.page;
| |
| if (pageName && !loaded[panelId] && !prefetching[panelId]) {
| |
| prefetch(panelId, pageName);
| |
| }
| |
| }, true);
| |
| | |
| // ============ 激活面板 ============
| |
| function activate(btn) {
| |
| if (btn.classList.contains('gs-active')) return;
| |
| | |
| var panelId = btn.dataset.panel;
| |
| var heroSrc = btn.dataset.hero;
| |
| var pageName = btn.dataset.page;
| |
| | |
| switchHero(heroSrc);
| |
| | |
| // 按钮高亮
| |
| switcher.querySelectorAll('.gs-tab-btn').forEach(function (b) {
| |
| b.classList.remove('gs-active');
| |
| b.setAttribute('aria-selected', 'false');
| |
| }); | | }); |
| btn.classList.add('gs-active');
| |
| btn.setAttribute('aria-selected', 'true');
| |
|
| |
| // 隐藏当前面板
| |
| switcher.querySelectorAll('.gs-content-panel').forEach(function (p) {
| |
| p.classList.remove('gs-visible');
| |
| p.classList.remove('gs-active');
| |
| });
| |
|
| |
| // 找到或创建目标面板
| |
| var panel = document.getElementById(panelId);
| |
| if (!panel) {
| |
| panel = document.createElement('div');
| |
| panel.id = panelId;
| |
| panel.className = 'gs-content-panel';
| |
| switcher.querySelector('.gs-content').appendChild(panel);
| |
| loadContent(panel, panelId, pageName);
| |
| }
| |
|
| |
| panel.classList.add('gs-active');
| |
| setTimeout(function () {
| |
| panel.classList.add('gs-visible');
| |
| }, 16);
| |
|
| |
| schedulePrefetchOthers();
| |
| } | | } |
|
| |
|
| // ============ 头图交叉溶解(双层互换) ============
| | if (document.readyState === 'loading') { |
| function switchHero(url) {
| | document.addEventListener('DOMContentLoaded', init); |
| if (!heroImg || !url) return;
| | } else { |
| if (heroImg.src === url) return; | | init(); |
| | |
| // 先预加载
| |
| var preload = new Image();
| |
| preload.onload = function () {
| |
| // 淡出
| |
| heroImg.style.opacity = '0';
| |
| | |
| // 等淡出真正结束再换图
| |
| heroImg.addEventListener('transitionend', function handler() {
| |
| heroImg.removeEventListener('transitionend', handler);
| |
| heroImg.src = url;
| |
| // 换完淡入
| |
| heroImg.style.opacity = '1';
| |
| });
| |
| };
| |
| preload.src = url;
| |
| }
| |
| | |
| // ============ 内容加载 ============
| |
| function loadContent(panel, panelId, pageName) {
| |
| if (loaded[panelId]) {
| |
| panel.innerHTML = loaded[panelId];
| |
| return;
| |
| }
| |
| try {
| |
| var stored = localStorage.getItem(storageKey + panelId);
| |
| if (stored) {
| |
| loaded[panelId] = stored;
| |
| panel.innerHTML = stored;
| |
| return;
| |
| }
| |
| } catch (e) {}
| |
| | |
| if (!pageName) return;
| |
| panel.innerHTML = '<div class="gs-loading">加载中...</div>';
| |
| | |
| fetchPage(pageName).then(function (html) {
| |
| loaded[panelId] = html;
| |
| try { localStorage.setItem(storageKey + panelId, html); } catch (e) {}
| |
| panel.innerHTML = html;
| |
| }).catch(function () { | |
| panel.innerHTML = '<div class="gs-loading">加载失败,<a href="javascript:location.reload()">刷新</a>重试</div>';
| |
| });
| |
| }
| |
| | |
| // ============ 预加载 ============
| |
| function prefetch(panelId, pageName) {
| |
| prefetching[panelId] = true;
| |
| try {
| |
| var stored = localStorage.getItem(storageKey + panelId);
| |
| if (stored) {
| |
| loaded[panelId] = stored;
| |
| prefetching[panelId] = false;
| |
| return;
| |
| }
| |
| } catch (e) {}
| |
| | |
| fetchPage(pageName).then(function (html) {
| |
| loaded[panelId] = html;
| |
| try { localStorage.setItem(storageKey + panelId, html); } catch (e) {}
| |
| }).finally(function () {
| |
| prefetching[panelId] = false;
| |
| });
| |
| }
| |
| | |
| function schedulePrefetchOthers() {
| |
| setTimeout(function () {
| |
| switcher.querySelectorAll('.gs-tab-btn').forEach(function (btn) {
| |
| var panelId = btn.dataset.panel;
| |
| var pageName = btn.dataset.page;
| |
| if (!loaded[panelId] && !prefetching[panelId] && pageName) {
| |
| prefetch(panelId, pageName);
| |
| }
| |
| });
| |
| }, 500);
| |
| } | |
| | |
| // ============ API 请求 ============
| |
| function fetchPage(pageName) {
| |
| var url = mw.util.wikiScript('api') + | |
| '?action=parse&page=' + encodeURIComponent(pageName) +
| |
| '&prop=text&format=json&disablelimitreport=1&origin=*';
| |
| return fetch(url)
| |
| .then(function (r) { return r.json(); })
| |
| .then(function (data) {
| |
| if (data.parse && data.parse.text) return data.parse.text['*'];
| |
| throw new Error('empty');
| |
| });
| |
| } | | } |
| }); | | })(); |
| </script> | | </script></onlyinclude> |
本Widget为少女前线系列多游戏首页提供切换交互逻辑。
不接受参数,直接在首页调用:{{#widget:GFHomepageSwitcher}}
<script>
(function () {
'use strict';
function init() {
var tabs = document.querySelectorAll('#gf-homepage .gf-tab');
var heroSlides = document.querySelectorAll('#gf-homepage .gf-hero-slide');
var contentPanels = document.querySelectorAll('#gf-homepage .gf-content-panel');
if (!tabs.length) return;
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
var game = this.getAttribute('data-game');
tabs.forEach(function (t) {
t.classList.remove('active');
t.setAttribute('aria-selected', 'false');
});
this.classList.add('active');
this.setAttribute('aria-selected', 'true');
heroSlides.forEach(function (s) {
s.classList.remove('active');
if (s.id === 'hero-' + game) {
s.classList.add('active');
}
});
contentPanels.forEach(function (p) {
p.classList.remove('active');
if (p.id === 'content-' + game) {
p.classList.add('active');
}
});
});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>