打开/关闭菜单
26
6679
46
1.2万
OGAS数据中枢
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

Widget:GameSwitcher:修订间差异

来自OGAS数据中枢
弃权者留言 | 贡献
无编辑摘要
弃权者留言 | 贡献
无编辑摘要
标签手工回退
 
(未显示同一用户的13个中间版本)
第1行: 第1行:
<script type="text/javascript">
<noinclude>
document.addEventListener('DOMContentLoaded', function () {
少女前线系列多游戏首页切换Widget。调用方式:<code><nowiki>{{#widget:GameSwitcher}}</nowiki></code>
   var switcher = document.querySelector('.game-switcher');
</noinclude><includeonly><script>
  if (!switcher) return;
( function () {
   'use strict';


var heroWrap = document.getElementById('gs-hero-wrap');
  var heroImages = {
var heroImg = null;
    'gf':  'index_gf_hero.jpg',
    'gf2':  'index_gf2_hero.jpg',
    'gfnc': 'index_gfnc_hero.jpg',
    'gfb':  'index_gfb_hero.jpg'
  };


if (heroWrap && firstBtn) {
  function getApiBase() {
  heroImg = document.createElement('img');
    var canonical = document.querySelector( 'link[rel="canonical"]' );
  heroImg.id = 'gs-hero-img';
    if ( canonical ) {
  heroImg.src = firstBtn.dataset.hero;
      var m = canonical.href.match( /^(https?:\/\/[^\/]+(?:\/[^\/]+)*?)\/wiki\// );
  heroWrap.appendChild(heroImg);
      if ( m ) { return m[ 1 ] + '/api.php'; }
}
    }
var loaded = {};
    return window.location.origin + '/api.php';
   var prefetching = {};
   }
  var storageKey = 'gs-cache-';


   // ============ 初始化双层头图 ============
   function getWikiImageUrl( filename, callback ) {
  var layerStyle = 'position:absolute;top:0;left:0;width:100%;height:100%;background-size:cover;background-position:center;transition:opacity 0.4s ease';
    fetch( getApiBase() + '?action=query&titles=File:' + encodeURIComponent( filename ) +
          '&prop=imageinfo&iiprop=url&format=json&origin=*' )
      .then( function ( r ) { return r.json(); } )
      .then( function ( data ) {
        var pages = data.query.pages;
        var page = pages[ Object.keys( pages )[ 0 ] ];
        if ( page.imageinfo && page.imageinfo[ 0 ] ) {
          callback( page.imageinfo[ 0 ].url );
        }
      } )
      .catch( function ( e ) { console.warn( 'GameSwitcher: 图片加载失败', filename, e ); } );
  }


   var layerA = document.createElement('div');
   function applyFadeColor() {
  var layerB = document.createElement('div');
    var style = getComputedStyle( document.documentElement );
  layerA.className = 'gs-hero-layer';
    var bg = style.getPropertyValue( '--color-surface-0' ).trim()
  layerB.className = 'gs-hero-layer';
          || style.getPropertyValue( '--background-color-base' ).trim()
  layerA.style.cssText = layerStyle + ';opacity:1';
          || '#1d1e20';
  layerB.style.cssText = layerStyle + ';opacity:0';
    var el = document.getElementById( 'gf-hero-fade-bottom' );
 
    if ( el ) {
  if (heroWrap) {
      el.style.background = 'linear-gradient(to bottom, transparent 0%, ' + bg + ' 100%)';
    heroWrap.appendChild(layerA);
     }
     heroWrap.appendChild(layerB);
   }
   }


   var activeLayer   = layerA;
function moveIndicator( tab ) {
   var inactiveLayer = layerB;
   var indicator = document.getElementById( 'gf-tab-indicator' );
   if ( !indicator ) { return; }
  indicator.style.width    = tab.offsetWidth + 'px';
   indicator.style.transform = 'translateX(' + tab.offsetLeft + 'px)';
}


  // ============ 初始化按钮和头图 ============
   var currentGame = 'gf';
   var firstBtn = switcher.querySelector('.gs-tab-btn');
   var switching  = false;
   if (firstBtn) {
  var pending     = null;
    firstBtn.classList.add('gs-active');
     firstBtn.setAttribute('aria-selected', 'true');
    if (heroWrap) {
      activeLayer.style.backgroundImage = 'url(' + firstBtn.dataset.hero + ')';
    }
  }


   // 默认面板直接可见,不走入场动画
   function doSwitch( game, tabEl ) {
  var defaultPanel = switcher.querySelector('.gs-content-panel.gs-active');
    var slides = document.querySelectorAll( '#gf-homepage .gf-hero-slide' );
  if (defaultPanel) {
    var panels = document.querySelectorAll( '#gf-homepage .gf-content-panel' );
     defaultPanel.classList.add('gs-visible');
     var tabs  = document.querySelectorAll( '#gf-homepage .gf-tab' );
  }


  // ============ 点击 ============
    var outSlide = document.getElementById( 'hero-' + currentGame );
  switcher.addEventListener('click', function (e) {
     var inSlide  = document.getElementById( 'hero-' + game );
     var btn = e.target.closest('.gs-tab-btn');
    if (btn) activate(btn);
  });


  // ============ 键盘 ============
    if ( inSlide ) {
  switcher.addEventListener('keydown', function (e) {
      inSlide.style.zIndex = '1';
    var btn = e.target.closest('.gs-tab-btn');
      inSlide.classList.add( 'active' );
    if (!btn) return;
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      activate(btn);
     }
     }
     if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
     if ( outSlide ) {
       e.preventDefault();
       outSlide.style.zIndex = '0';
      var btns = Array.from(switcher.querySelectorAll('.gs-tab-btn'));
       outSlide.classList.remove( 'active' );
       var idx = btns.indexOf(btn);
      var next = e.key === 'ArrowRight' ? idx + 1 : idx - 1;
      if (next >= 0 && next < btns.length) btns[next].focus();
     }
     }
  });


  // ============ 悬停预加载 ============
    tabs.forEach( function ( t ) {
  switcher.addEventListener('mouseenter', function (e) {
      var on = t.getAttribute( 'data-game' ) === game;
    var btn = e.target.closest('.gs-tab-btn');
      t.classList.toggle( 'active', on );
    if (!btn) return;
      t.setAttribute( 'aria-selected', on ? 'true' : 'false' );
    var panelId = btn.dataset.panel;
     } );
     var pageName = btn.dataset.page;
     if ( tabEl ) { moveIndicator( tabEl ); }
     if (pageName && !loaded[panelId] && !prefetching[panelId]) {
      prefetch(panelId, pageName);
    }
  }, true);


  // ============ 激活面板 ============
    panels.forEach( function ( p ) {
  function activate(btn) {
      p.classList.toggle( 'active', p.id === 'content-' + game );
    if (btn.classList.contains('gs-active')) return;
    } );


     var panelId = btn.dataset.panel;
     currentGame = game;
     var heroSrc = btn.dataset.hero;
     switching  = true;
    var pageName = btn.dataset.page;


     switchHero(heroSrc);
     setTimeout( function () {
      slides.forEach( function ( s ) { s.style.zIndex = ''; } );
      switching = false;
      if ( pending && pending.game !== currentGame ) {
        var p = pending;
        pending = null;
        doSwitch( p.game, p.tabEl );
      } else {
        pending = null;
      }
    }, 520 );
  }


     // 按钮高亮
  function switchGame( game, tabEl ) {
     switcher.querySelectorAll('.gs-tab-btn').forEach(function (b) {
     if ( game === currentGame ) { return; }
      b.classList.remove('gs-active');
     if ( switching ) {
      b.setAttribute('aria-selected', 'false');
      pending = { game: game, tabEl: tabEl };
    });
      var tabs = document.querySelectorAll( '#gf-homepage .gf-tab' );
    btn.classList.add('gs-active');
      tabs.forEach( function ( t ) {
    btn.setAttribute('aria-selected', 'true');
        var on = t.getAttribute( 'data-game' ) === game;
 
        t.classList.toggle( 'active', on );
    // 隐藏当前面板
        t.setAttribute( 'aria-selected', on ? 'true' : 'false' );
    switcher.querySelectorAll('.gs-content-panel').forEach(function (p) {
       } );
      p.classList.remove('gs-visible');
      if ( tabEl ) { moveIndicator( tabEl ); }
       p.classList.remove('gs-active');
       return;
    });
 
    // 找到或创建目标面板
    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);
     }
     }
 
     doSwitch( game, tabEl );
     panel.classList.add('gs-active');
    setTimeout(function () {
      panel.classList.add('gs-visible');
    }, 16);
 
    schedulePrefetchOthers();
   }
   }


   // ============ 头图交叉溶解(双层互换) ============
   function init() {
function switchHero(url) {
    var tabs = document.querySelectorAll( '#gf-homepage .gf-tab' );
  if (!heroImg || !url) return;
    if ( !tabs.length ) { return; }
  if (heroImg.src === url) return;


  // 先预加载
    Object.keys( heroImages ).forEach( function ( game ) {
  var preload = new Image();
      getWikiImageUrl( heroImages[ game ], function ( url ) {
  preload.onload = function () {
        var bg  = document.getElementById( 'hero-' + game + '-bg' );
    // 淡出
        var fg  = document.getElementById( 'hero-' + game + '-fg' );
    heroImg.style.opacity = '0';
        var val = 'url("' + url + '")';
        if ( bg ) { bg.style.backgroundImage = val; }
        if ( fg ) { fg.style.backgroundImage = val; }
      } );
    } );


     // 等淡出真正结束再换图
     applyFadeColor();
    heroImg.addEventListener('transitionend', function handler() {
      heroImg.removeEventListener('transitionend', handler);
      heroImg.src = url;
      // 换完淡入
      heroImg.style.opacity = '1';
    });
  };
  preload.src = url;
}


  // ============ 内容加载 ============
    var firstActive = document.querySelector( '#gf-homepage .gf-tab.active' );
  function loadContent(panel, panelId, pageName) {
     if ( firstActive ) {
     if (loaded[panelId]) {
       setTimeout( function () { moveIndicator( firstActive ); }, 50 );
       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;
     tabs.forEach( function ( tab ) {
    panel.innerHTML = '<div class="gs-loading">加载中...</div>';
      tab.addEventListener( 'click', function () {
        switchGame( this.getAttribute( 'data-game' ), this );
      } );
      tab.addEventListener( 'keydown', function ( e ) {
        if ( e.key === 'Enter' || e.key === ' ' ) {
          e.preventDefault();
          switchGame( this.getAttribute( 'data-game' ), this );
        }
      } );
    } );


     fetchPage(pageName).then(function (html) {
     window.addEventListener( 'resize', function () {
       loaded[panelId] = html;
       var active = document.querySelector( '#gf-homepage .gf-tab.active' );
      try { localStorage.setItem(storageKey + panelId, html); } catch (e) {}
       if ( active ) { moveIndicator( active ); }
       panel.innerHTML = html;
     } );
    }).catch(function () {
      panel.innerHTML = '<div class="gs-loading">加载失败,<a href="javascript:location.reload()">刷新</a>重试</div>';
     });
   }
   }


   // ============ 预加载 ============
   if ( document.readyState === 'loading' ) {
  function prefetch(panelId, pageName) {
     document.addEventListener( 'DOMContentLoaded', init );
    prefetching[panelId] = true;
  } else {
    try {
     init();
      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 () {
</script></includeonly>
      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>

2026年6月20日 (六) 23:23的最新版本

少女前线系列多游戏首页切换Widget。调用方式:{{#widget:GameSwitcher}}