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

Widget:GameSwitcher:修订间差异

来自OGAS数据中枢
弃权者留言 | 贡献
创建页面,内容为“<script> $(function () { var $switcher = $('.game-switcher'); if (!$switcher.length) return; var $hero = $('#gs-hero-wrap'); var $heroImg = $hero.find('img'); var loaded = {}; // 内存缓存: { panelId: html } var prefetching = {}; // 防重复预取 var storageKey = 'gs-cache-'; // ============ 初始化 ============ var $firstBtn = $switcher.find('.gs-tab-btn').first(); if ($firstBtn.length) { $firstBtn.addClass('gs-active').…”
 
弃权者留言 | 贡献
无编辑摘要
标签手工回退
 
(未显示同一用户的27个中间版本)
第1行: 第1行:
<script>
<noinclude>
$(function () {
少女前线系列多游戏首页切换Widget。调用方式:<code><nowiki>{{#widget:GameSwitcher}}</nowiki></code>
   var $switcher = $('.game-switcher');
</noinclude><includeonly><script>
  if (!$switcher.length) return;
( function () {
   'use strict';


   var $hero = $('#gs-hero-wrap');
   var heroImages = {
   var $heroImg = $hero.find('img');
    'gf':   'index_gf_hero.jpg',
  var loaded = {};      // 内存缓存: { panelId: html }
    'gf2':  'index_gf2_hero.jpg',
   var prefetching = {};  // 防重复预取
    'gfnc': 'index_gfnc_hero.jpg',
  var storageKey = 'gs-cache-';
    'gfb': 'index_gfb_hero.jpg'
   };


   // ============ 初始化 ============
   function getApiBase() {
  var $firstBtn = $switcher.find('.gs-tab-btn').first();
    var canonical = document.querySelector( 'link[rel="canonical"]' );
  if ($firstBtn.length) {
    if ( canonical ) {
    $firstBtn.addClass('gs-active').attr('aria-selected', 'true');
      var m = canonical.href.match( /^(https?:\/\/[^\/]+(?:\/[^\/]+)*?)\/wiki\// );
    if ($heroImg.length && !$heroImg.attr('src')) {
      if ( m ) { return m[ 1 ] + '/api.php'; }
      $heroImg.attr('src', $firstBtn.data('hero'));
     }
     }
    return window.location.origin + '/api.php';
   }
   }
  restoreHash();


   // ============ 点击 ============
   function getWikiImageUrl( filename, callback ) {
  $switcher.on('click', '.gs-tab-btn', function () {
    fetch( getApiBase() + '?action=query&titles=File:' + encodeURIComponent( filename ) +
    activate($(this));
          '&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 ); } );
  }


   // ============ 键盘 ============
   function applyFadeColor() {
  $switcher.on('keydown', '.gs-tab-btn', function (e) {
    var style = getComputedStyle( document.documentElement );
    if (e.key === 'Enter' || e.key === ' ') {
    var bg = style.getPropertyValue( '--color-surface-0' ).trim()
      e.preventDefault();
          || style.getPropertyValue( '--background-color-base' ).trim()
       activate($(this));
          || '#1d1e20';
    var el = document.getElementById( 'gf-hero-fade-bottom' );
    if ( el ) {
       el.style.background = 'linear-gradient(to bottom, transparent 0%, ' + bg + ' 100%)';
     }
     }
    if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
   }
      e.preventDefault();
      var $btns = $switcher.find('.gs-tab-btn');
      var idx = $btns.index(this);
      var next = e.key === 'ArrowRight' ? idx + 1 : idx - 1;
      if (next >= 0 && next < $btns.length) {
        $btns.eq(next).focus();
      }
    }
   });


   // ============ 悬停预加载 ============
function moveIndicator( tab ) {
  $switcher.on('mouseenter', '.gs-tab-btn', function () {
   var indicator = document.getElementById( 'gf-tab-indicator' );
     var panelId = $(this).data('panel');
  if ( !indicator ) { return; }
    var pageName = $(this).data('page');
  indicator.style.width     = tab.offsetWidth + 'px';
    if (pageName && !loaded[panelId] && !prefetching[panelId]) {
  indicator.style.transform = 'translateX(' + tab.offsetLeft + 'px)';
      prefetch(panelId, pageName);
}
    }
  });


   // ============ URL hash ============
   var currentGame = 'gf';
   $(window).on('hashchange', restoreHash);
  var switching  = false;
   var pending    = null;


  // ============ 激活面板 ============
   function doSwitch( game, tabEl ) {
   function activate($btn) {
     var slides = document.querySelectorAll( '#gf-homepage .gf-hero-slide' );
     if ($btn.hasClass('gs-active')) return;
    var panels = document.querySelectorAll( '#gf-homepage .gf-content-panel' );
    var tabs  = document.querySelectorAll( '#gf-homepage .gf-tab' );


     var panelId = $btn.data('panel');
     var outSlide = document.getElementById( 'hero-' + currentGame );
     var heroSrc = $btn.data('hero');
     var inSlide  = document.getElementById( 'hero-' + game );
    var pageName = $btn.data('page');


     // 1. 头图淡入淡出
     if ( inSlide ) {
    switchHero(heroSrc);
       inSlide.style.zIndex = '1';
 
      inSlide.classList.add( 'active' );
    // 2. 按钮高亮
    }
    $switcher.find('.gs-tab-btn')
     if ( outSlide ) {
       .removeClass('gs-active')
       outSlide.style.zIndex = '0';
      .attr('aria-selected', 'false');
       outSlide.classList.remove( 'active' );
    $btn.addClass('gs-active').attr('aria-selected', 'true');
 
    // 3. 面板切换
    var $panel = $('#' + panelId);
    $switcher.find('.gs-content-panel').removeClass('gs-active');
 
     if (!$panel.length) {
       $panel = $('<div>', {
        id: panelId,
        class: 'gs-content-panel gs-active'
       }).appendTo('.gs-content');
      loadContent($panel, panelId, pageName);
    } else {
      $panel.addClass('gs-active');
     }
     }


     // 4. URL hash
     tabs.forEach( function ( t ) {
     setHash(panelId);
      var on = t.getAttribute( 'data-game' ) === game;
      t.classList.toggle( 'active', on );
      t.setAttribute( 'aria-selected', on ? 'true' : 'false' );
    } );
     if ( tabEl ) { moveIndicator( tabEl ); }


     // 5. 后台静默预取其余
     panels.forEach( function ( p ) {
    schedulePrefetchOthers();
      p.classList.toggle( 'active', p.id === 'content-' + game );
  }
    } );


  // ============ 头图切换(预加载 + 淡入淡出) ============
     currentGame = game;
  function switchHero(url) {
     switching  = true;
    if (!$hero.length || !url) return;
     var current = $heroImg.attr('src');
     if (current === url) return;


     $('<img>').on('load', function () {
     setTimeout( function () {
      $hero.addClass('gs-hero--fade');
      slides.forEach( function ( s ) { s.style.zIndex = ''; } );
       setTimeout(function () {
       switching = false;
         $heroImg.attr('src', url);
      if ( pending && pending.game !== currentGame ) {
         $hero.removeClass('gs-hero--fade');
         var p = pending;
       }, 160);
        pending = null;
     }).attr('src', url);
        doSwitch( p.game, p.tabEl );
      } else {
         pending = null;
       }
     }, 520 );
   }
   }


   // ============ 内容加载 ============
   function switchGame( game, tabEl ) {
  function loadContent($panel, panelId, pageName) {
    if ( game === currentGame ) { return; }
    if (loaded[panelId]) {
    if ( switching ) {
      $panel.html(loaded[panelId]);
      pending = { game: game, tabEl: tabEl };
      var tabs = document.querySelectorAll( '#gf-homepage .gf-tab' );
      tabs.forEach( function ( t ) {
        var on = t.getAttribute( 'data-game' ) === game;
        t.classList.toggle( 'active', on );
        t.setAttribute( 'aria-selected', on ? 'true' : 'false' );
      } );
      if ( tabEl ) { moveIndicator( tabEl ); }
       return;
       return;
     }
     }
    doSwitch( game, tabEl );
  }


    var stored = mw.storage.get(storageKey + panelId);
  function init() {
     if (stored) {
     var tabs = document.querySelectorAll( '#gf-homepage .gf-tab' );
      loaded[panelId] = stored;
     if ( !tabs.length ) { return; }
      $panel.html(stored);
      return;
    }
 
     if (!pageName) return;


     $panel.html('<div class="gs-loading">加载中...</div>');
     Object.keys( heroImages ).forEach( function ( game ) {
      getWikiImageUrl( heroImages[ game ], function ( url ) {
        var bg  = document.getElementById( 'hero-' + game + '-bg' );
        var fg  = document.getElementById( 'hero-' + game + '-fg' );
        var val = 'url("' + url + '")';
        if ( bg ) { bg.style.backgroundImage = val; }
        if ( fg ) { fg.style.backgroundImage = val; }
      } );
    } );


     $.get(mw.util.wikiScript('api'), {
     applyFadeColor();
      action: 'parse',
      page: pageName,
      prop: 'text',
      format: 'json',
      disablelimitreport: 1
    }).then(function (data) {
      if (data.parse && data.parse.text) {
        var html = data.parse.text['*'];
        loaded[panelId] = html;
        try { mw.storage.set(storageKey + panelId, html); } catch (e) {}
        $panel.html(html);
      }
    }).fail(function () {
      $panel.html('<div class="gs-loading">加载失败,<a href="javascript:location.reload()">刷新</a>重试</div>');
    });
  }


  // ============ 预加载 ============
     var firstActive = document.querySelector( '#gf-homepage .gf-tab.active' );
  function prefetch(panelId, pageName) {
     if ( firstActive ) {
    prefetching[panelId] = true;
       setTimeout( function () { moveIndicator( firstActive ); }, 50 );
 
     var stored = mw.storage.get(storageKey + panelId);
     if (stored) {
       loaded[panelId] = stored;
      prefetching[panelId] = false;
      return;
     }
     }


     $.get(mw.util.wikiScript('api'), {
     tabs.forEach( function ( tab ) {
       action: 'parse',
       tab.addEventListener( 'click', function () {
      page: pageName,
        switchGame( this.getAttribute( 'data-game' ), this );
      prop: 'text',
       } );
       format: 'json',
       tab.addEventListener( 'keydown', function ( e ) {
       disablelimitreport: 1
        if ( e.key === 'Enter' || e.key === ' ' ) {
    }).then(function (data) {
          e.preventDefault();
      if (data.parse && data.parse.text) {
          switchGame( this.getAttribute( 'data-game' ), this );
        var html = data.parse.text['*'];
        }
        loaded[panelId] = html;
       } );
        try { mw.storage.set(storageKey + panelId, html); } catch (e) {}
     } );
       }
    }).always(function () {
      prefetching[panelId] = false;
     });
  }


  function schedulePrefetchOthers() {
     window.addEventListener( 'resize', function () {
     setTimeout(function () {
      var active = document.querySelector( '#gf-homepage .gf-tab.active' );
      $switcher.find('.gs-tab-btn').each(function () {
      if ( active ) { moveIndicator( active ); }
        var panelId = $(this).data('panel');
     } );
        var pageName = $(this).data('page');
        if (!loaded[panelId] && !prefetching[panelId] && pageName) {
          prefetch(panelId, pageName);
        }
      });
     }, 500);
   }
   }


   // ============ URL hash ============
   if ( document.readyState === 'loading' ) {
  function setHash(panelId) {
     document.addEventListener( 'DOMContentLoaded', init );
     try { history.replaceState(null, '', '#' + panelId); } catch (e) {}
  } else {
    init();
   }
   }


  function restoreHash() {
} )();
    var hash = window.location.hash.replace('#', '');
</script></includeonly>
    if (hash && hash.indexOf('panel-') === 0) {
      var $btn = $switcher.find('[data-panel="' + hash + '"]');
      if ($btn.length && !$btn.hasClass('gs-active')) {
        activate($btn);
      }
    }
  }
});
</script>

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

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