PageSourceSearch

https://kabu.com/2024renew_assets/js/anchor-tab.js?v=1790907664

js kabu.com collected 2026-10-02 02:24:26 UTC 15,495 bytes, 378 lines download raw bytes

1// -----------------------------------------------------------------------
2// アンカー付の外部リンクをクリックした際に表示位置が合わない現象を対応(発生条件は下記)
3// 
4// 【発生条件】
5// アンカーリンク先が初期表示タブではない非表示状態のタブ(右のタブ)内にアンカーが設置されている
6// アンカー先より上にslickのカルーセルなどjsで作っている動的コンテンツがある
7//  例 → https://kabu.com/item/foreign_stock/us_stock/default.html#anc03
8// 
9// 【原因】
10// slickなどは非表示から表示されてからカルーセルが作成される
11// 外部リンクでアンカー先に飛ぶとすぐに該当箇所に飛ぶが、その後アンカー先の上部でカルーセルが作成され、表示位置が下にずれてしまう。
12// 
13// 【対処】
14// 外部リンクのアンカー付きでウチのサイトに入ってきた場合、 ”できるだけ早い段階” でURLから一旦アンカーを外す処理をして、アンカ先に移動しないようにしてる
15// その後、slickが表示されたらURLにアンカーを再度付与し、該当箇所までいどうする。
16// 
17// 補足:戻る/進む(back/forward)の場合は介入しない
18// 補足:slickが無いページでは即スクロールに分岐
19// 補足:ハッシュ値(アンカー)が無ければ何もしない。
20// -----------------------------------------------------------------------
21
22(function(){
23  if (!location.hash) return;
24  // 戻る/進むのスクロール復元は尊重
25  var n = (performance.getEntriesByType && performance.getEntriesByType('navigation'))?.[0];
26  if (n && n.type === 'back_forward') return;
27
28  // 退避してハッシュを一旦外す(自動スクロール防止)
29  try {
30    sessionStorage.setItem('deferredHash', location.hash);
31    history.replaceState(null, '', location.pathname + location.search);
32    // 念のため即座にトップへ
33    document.documentElement.style.scrollBehavior = 'auto';
34    scrollTo(0, 0);
35  } catch(e){}
36})();
37
38(function(){
39  var h = sessionStorage.getItem('deferredHash');
40  if (!h) return;
41  sessionStorage.removeItem('deferredHash');
42
43  function doScroll($t){
44    if (typeof updateHeaderHeight === 'function') updateHeaderHeight();//「updateHeaderHeightという関数が存在する時だけ呼び出す」ための安全なガード
45
46    // PC幅(>=769px)のときだけ追加で引くピクセル数(調整用)
47    // 正の値にすると「もう少し上」に止まります。例: 12, 16 など
48    var DESKTOP_EXTRA_OFFSET_PX =70;
49
50
51    //「いまの画面幅がPC幅かどうか」を真偽値で判定して、isDesktopに入れています。
52    //769px以上ならtrue、768px以下ならfalse。
53    var isDesktop = window.matchMedia 
54      ? window.matchMedia('(min-width: 769px)').matches
55      : (window.innerWidth >= 769);
56
57    var baseTop = $t.offset().top - (window.headerHeight || 0) - (window.adjustmentHeight || 0);
58    var top = baseTop - (isDesktop ? DESKTOP_EXTRA_OFFSET_PX : 0);
59
60    $('html, body').stop(true).animate({
61      scrollTop: Math.max(0, Math.round(top))
62    }, window.anchorScrollSpeed || 500);
63
64    // URLにハッシュを戻す(hashchangeを発火させたくない場合はreplaceState)
65    history.replaceState(null, '', location.pathname + location.search + h);
66  }
67
68  window.addEventListener('load', function(){
69    var id = decodeURIComponent(h.slice(1));
70    var $t = $('#' + id);
71    if (!$t.length) $t = $('[name="'+id+'"]');
72    if (!$t.length) return;
73
74    // タブ内なら表示してから
75    var $scope = $(document);
76    if ($t.closest('.tab-content').length){
77      var $tabBody = $t.closest('.tab-body');
78      var $tabHeader = $tabBody.siblings('.tab-header');
79      var targetId = $t.closest('.tab-content').data('content-id');
80      $tabHeader.find('.tab').removeClass('active');
81      $tabHeader.find('[data-target="'+targetId+'"]').addClass('active');
82      $tabBody.children('.tab-content').hide();
83      var $shown = $tabBody.children('[data-content-id="'+targetId+'"]').show();
84      $scope = $shown;
85    }
86
87    // カルーセル有無で分岐
88    var sel = '.carousel-primary, .carousel-2slides, .carousel-3slides, .carousel-4slides, .carousel-video, .carousel-group-service, .carousel-step, .carousel-flow, .carousel-sp-normal-scroll';
89    var $cars = $scope.find(sel).filter('.slick-initialized');
90
91    if (!$cars.length){
92      doScroll($t); // カルーセルが無ければ即スクロール
93      return;
94    }
95
96    // カルーセルがある場合はrefresh/setPosition完了を待ってからスクロール(ä
96¿é™ºã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆä»˜ãï¼‰
97    var remaining = $cars.length, finished = false;
98    var timer = setTimeout(function(){ if (!finished){ finished = true; doScroll($t); } }, 600);
99
100    function oneDone(){
101      if (finished) return;
102      if (--remaining <= 0){
103        finished = true;
104        clearTimeout(timer);
105        doScroll($t);
106      }
107    }
108
109    $cars.each(function(){
110      var $s = $(this);
111      $s.one('setPosition.anchorFix reInit.anchorFix', oneDone);
112      try { $s.slick('refresh'); }
113      catch(e){
114        try { $s.slick('setPosition'); } catch(_){}
115        try { $s.slick('resize'); }      catch(_){}
116        // イベントが来ない場合は上のタイムアウトで保険
117      }
118    });
119  });
120})();
121
122
123
124// -----------------------------------------------------------------------
125// 既存ページのアンカー処理と喧嘩したため
126// common.js からアンカーに関係する処理(アンカーとタブ)を分離した
127// -----------------------------------------------------------------------
128
129// アンカーリンク押下時のスクロールスピード
130var anchorScrollSpeed = 500;
131// ヘッダの高さ
132var headerHeight = 0;
133// あそび
134var adjustmentHeight = 16;
135
136// ヘッダの高さを取得する
137// -----------------------------------------------------------------------
138
139function updateHeaderHeight() {
140  var $header = $(".header");
141  headerHeight = $header.length ? $header.outerHeight() : 0;
142}
143
144$(function () {
145  // 初期ページ読み込み時に高さを取得
146  updateHeaderHeight();
147
148  // ブラウザの横幅が変更されたときに高さを再取得
149  $(window).on("resize", function () {
150    updateHeaderHeight();
151  });
152});
153
154// アンカー先タブで隠れていた場合の処理。※アンカー先が非表示ではアンカーリンクは機能しないため
155// アンカー先タブでカルーセルがあると、そのままではカルーセルが縦長になってしまうのでslickをリフレッシュして正しい高さを取得してスクロールするようにしている
156// -----------------------------------------------------------------------
157$(document).ready(function() {
158  // ID 'if-anchorHideTab' がHTMLに存在するか確認
159  if ($('#if-anchorHideTab').length > 0) {
160    // 存在する場合のみ、以下の処理を実行
161    $('.btn-anchor').on('click', function(event) {
162      var targetId = $(this).attr('href'); // クリックしたリンクの href を取得
163      var $targetElement = $(targetId);
164      // ターゲット要素が表示されているかを判定
165      if (!$targetElement.is(':visible')) {
166        event.preventDefault(); // デフォルトのアンカーリンクの動作を防止
167
168        // ヘッダの高さを再取得(関数があるとして想定)
169        updateHeaderHeight();
170        var $parentTabContainer = $targetElement.closest('.tab-container'); // タブコンテナを特定
171        var targetTabContentId  = $targetElement.closest('.tab-content').data('content-id'); // ターゲットIDが含まれるタブのIDを取得
172        // 直下のタブをアクティブにする
173        $parentTabContainer.children('.tab-header').children('.tab').removeClass('active');
174        $parentTabContainer.find('.tab[data-target="' + targetTabContentId + '"]').addClass('active');
175
176        // 直下のタブコンテンツを表示
177        $parentTabContainer.children('.tab-body').children('.tab-content').hide();
178        var $shown = $parentTabContainer
179          .children('.tab-body')
180          .children('.tab-content[data-content-id="' + targetTabContentId + '"]')
181          .show();
182
183        // ターゲット要素までスクロール(slickの有無で分岐)
184        function doScroll() {
185          updateHeaderHeight();
186          $('html, body').stop(true).animate({
187            scrollTop: $targetElement.offset().top - headerHeight - adjustmentHeight
188          }, anchorScrollSpeed);
189        }
190
191        // カルーセルが無い場合は即スクロール
192        var sel    = '.carousel-primary, .carousel-2slides, .carousel-3slides, .carousel-4slides, .carousel-video, .carousel-group-service, .carousel-step, .carousel-flow, .carousel-sp-normal-scroll';
193        var $inTab = $shown.find(sel).filter('.slick-initialized');
194        if (!$inTab.length) {
195          doScroll();
196          return;
197        }
198
199        // カルーセルがある場合:リフレッシュ完了を待ってからスクロール(ä
199¿é™ºã‚¿ã‚¤ãƒ ã‚¢ã‚¦ãƒˆä»˜ãï¼‰
200        var remaining = $inTab.length;
201        var finished  = false;
202        var timer = setTimeout(function(){
203          if (!finished) { finished = true; doScroll(); }
204        }, 600); // 必要なら調整(200〜800ms目安)
205
206        function oneDone() {
207          if (finished) return;
208          remaining--;
209          if (remaining <= 0) {
210            finished = true;
211            clearTimeout(timer);
212            doScroll();
213          }
214        }
215
216        $inTab.each(function(){
217          var $s = $(this);
218          // refresh/setPosition 後に発火するイベントを1回だけ待つ
219          $s.one('setPosition.anchorFix reInit.anchorFix', oneDone);
220          try {
221            $s.slick('refresh'); // refreshが使える場合
222          } catch (e) {
223            // フォールバック(refresh未実装のslick用)
224            try { $s.slick('setPosition'); } catch(_) {}
225            try { $s.slick('resize'); }      catch(_) {}
226            // フォールバック時はイベントが来ない場合があるため、上記タイムアウトで保険
227          }
228        });
229      }
230    });
231  }
232});
233
234// アンカーリンク(ページ内リンク)を踏んだときのスクロール処理
235// -----------------------------------------------------------------------
236
237$(function () {
238  $('.renewal-wrapper a[href^="#"]').on("click", function (e) {
239    // デフォルトのリンク動作を無効化
240    e.preventDefault();
241
242    // ヘッダの高さを再取得
243    updateHeaderHeight();
244
245    var targetId = this.getAttribute("href");
246
247    // href が '#' の場合はトップにスクロールする
248    if (targetId === "#") {
249      $("html, body").animate({ scrollTop: 0 }, anchorScrollSpeed);
250    } else {
251      var target = $(targetId);
252      // ヘッダーの高さを考慮した位置で止める
253      if (target.length) {
254        $("html, body").animate({ scrollTop: target.offset().top - headerHeight - adjustmentHeight }, anchorScrollSpeed);
255      }
256    }
257  });
258});
259
260// 通常タブコンテンツ(領域自体を切り替えるもの)
261// -----------------------------------------------------------------------
262
263$(function () {
264  $(document).on("click keydown", ".renewal-wrapper .tab-header.target-tab-content .tab", function (event) {
265    // クリック、エンターキー入力、スペースキー入力のいずれかの場合
266    if (event.type === "click" || event.key === "Enter" || event.key === " ") {
267      // Space キーのスクロールを防ぐ
268      event.preventDefault();
269
270      // クリックされたタブがすでに active であれば処理を終了
271      if ($(this).hasClass("active")) {
272        return;
273      }
274
275      // 要素の変数化
276      var $tabHeader = $(this).closest(".tab-header");
277      var $tabBody = $(this).closest(".tab-container").children(".tab-body");
278      var targetId = $(this).data("target");
279      var $targetContent = $tabBody.children('[data-content-id="' + targetId + '"]');
280
281      // アクティブなタブのクラスを削除し、新しいタブに追加
282      $tabHeader.find(".tab").removeClass("active");
283      $(this).addClass("active");
284
285      // タブコンテンツ表示の切り替え
286      $tabBody.children(".tab-content").hide();
287      $targetContent.show();
288
289      // 各 slick をリフレッシュ
290      $targetContent.find(".carousel-primary").each(function () {
291        $(this).slick("refresh");
292      });
293      $targetContent.find(".carousel-2slides").each(function () {
294        $(this).slick("refresh");
295      });
296      $targetContent.find(".carousel-3slides").each(function () {
297        $(this).slick("refresh");
298      });
299      $targetContent.find(".carousel-4slides").each(function () {
300        $(this).slick("refresh");
301      });
302      $targetContent.find(".carousel-video").each(function () {
303        $(this).slick("refresh");
304      });
305      $targetContent.find(".carousel-group-service").each(function () {
306        $(this).slick("refresh");
307      });
308      $targetContent.find(".carousel-step").each(function () {
309        $(this).slick("refresh");
310      });
311      $targetContent.find(".carousel-flow").each(function () {
312        $(this).slick("refresh");
313      });
314      $targetContent.find(".carousel-sp-normal-scroll").each(function () {
315        $(this).slick("refresh");
316      });
317
318      
319
320      // slick のスマホ時アロー位置調整
321      adjustSlickArrowPosition();
322    }
323  });
324
325  // アンカーリンク対応処理
326  function activateTabFromHash(shouldScroll) {
327    // URL の #以降を取得
328    var hash = window.location.hash;
329    if (hash) {
330      var $targetElement = $(hash);
331
332      // 該当要素がページ内に存在する場合
333      if ($targetElement.length) {
334        // ヘッダの高さを再取得
335        updateHeaderHeight();
336
337        // タブコンテンツ内に存在する場合
338        if ($targetElement.closest(".tab-content").length) {
339          var $tabBody = $targetElement.closest(".tab-body");
340          var $tabHeader = $tabBody.siblings(".tab-header");
341          var targetId = $targetElement.closest(".tab-content").data("c
341ontent-id");
342
343          // 該当するタブをアクティブ化
344          $tabHeader.find(".tab").removeClass("active");
345          $tabHeader.find('[data-target="' + targetId + '"]').addClass("active");
346
347          // タブコンテンツ表示の切り替え
348          $tabBody.children(".tab-content").hide();
349          $tabBody.children('[data-content-id="' + targetId + '"]').show();
350        }
351
352        // スクロールするか否かでスクロールスピードを変える
353        var speed = 0;
354        if (shouldScroll) {
355          speed = anchorScrollSpeed;
356        }
357
358        // 該当要素にスクロール
359        $("html, body").animate({ scrollTop: $targetElement.offset().top - headerHeight - adjustmentHeight }, speed);
360      }
361    }
362  }
363
364  // ページ読み込み時にアンカーリンク対応を実行
365  setTimeout(function () {
366    activateTabFromHash(false); // ページ読み込み時はスクロールは不要
367  }, 1);
368
369  // URL のハッシュが変更されたときもタブを切り替え
370  $(window).on("hashchange", function () {
371    activateTabFromHash(true);
372  });
373});
374
375
376  
377
378

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.