PageSourceSearch

https://www.hawaiians.co.jp/cms/wp-content/themes/srh/js/project/common.js?1787293434

js hawaiians.co.jp collected 2026-09-24 17:38:19 UTC 25,455 bytes, 807 lines download raw bytes

1$(function () {
2
3    /* ------------------------------------------
4       グローバルナビゲーションの開閉
5    ------------------------------------------ */
6
7    //1.アコーディオン用の変数
8    var $window = $(window),
9        $menuButton = $(".js-navBtn"),
10        $accordion = $('.js-navIcon');
11        $menuArea = $('.js-navMenu');
12        $menuAreaWrap = $('.js-navWrap');
13
14    //3.開くときの動き
15    function openAction() {
16        //クラスをつける
17        $accordion.addClass('is-open');
18
19        if ($accordion.hasClass('is-close')) {
20            $accordion.removeClass('is-close');
21        } else {
22        }
23        if ($menuArea.hasClass('is-close')) {
24            $menuArea.removeClass('is-close');
25        } else {
26        }
27        $menuArea.addClass('is-open');
28    }
29
30    //4.閉じるときの動き
31    function closeAction() {
32        //クラスを外す
33        $accordion.removeClass('is-open');
34
35        if ($menuArea.hasClass('is-open')) {
36            $menuArea.removeClass('is-open');
37        } else {
38        }
39        $menuArea.addClass('is-close');
40        $accordion.addClass('is-close');
41
42        setTimeout(function () {
43            $menuAreaWrap.scrollTop(0);
44        }, 1000);
45    }
46
47    //5.クリックイベント
48    $menuButton.on('click', function () {
49        const $slider = $('.js-header-menu'); // ← slick要素取得
50
51        //open用のクラスがあるかどうか判定
52        if ($accordion.hasClass('is-open')) {
53            //閉じるときの動き
54            closeAction();
55            $('body').css('overflow-y', 'visible');
56        } else {
57            //開くときの動き
58            openAction();
59            $('body').css('overflow-y', 'hidden');
60            //開いたときにスライダーを最初のスライドへ
61            if ($slider.hasClass('slick-initialized')) {
62                $slider.slick('slickGoTo', 0, true);
63            }
64        }
65    });
66
67    // 6.メニュークリックでモーダルを開く
68    $('.js-open-slide').on('click', function(e) {
69        e.preventDefault();
70
71        // スライド番号取得
72        const slideIndex = $(this).data('slide');
73        const $modal = $('.js-navMenu');
74        const $slider = $modal.find('.js-header-menu');
75        
76        if (!$modal.hasClass('is-open')) {
77            //開くときの動き
78            openAction();
79            $('body').css('overflow-y', 'hidden');
80
81            // モーダル開く
82            $modal.addClass('is-open');
83
84            // slickを再描画+指定スライドへ
85            $slider.slick('setPosition');
86            $slider.slick('slickGoTo', slideIndex, true);
87        } else {
88            $slider.slick('slickGoTo', slideIndex);
89        }
90    });
91
92    // 7.検索ボタンでメニューを閉じる
93    $('.js-search-btn').on('click', function() {
94        closeAction();
95        $('body').css('overflow-y', 'visible');
96    });
97
98    // 翻訳ドロップダウンリスト選択時の処理
99    $('.js-translate').on('click', (event) => {
100        const lang = $(event.currentTarget).data('lang');
101        const curUrl = new URL(window.location.href);
102        const redirectTo = [
103            curUrl.origin,
104            lang,
105            (curUrl.searchParams.get('src') || curUrl.pathname).substring(1),
106        ].join('/');
107        location.href= redirectTo;
108    });
109});
110
111/* ------------------------------------------
112SP - グローバルメニューの動き
113------------------------------------------ */
114$('.js-navBtn').on('click', function() {
115    const $gnav = $('.l-gnav');
116    if ($gnav.hasClass('is-open')) {
117        $gnav.removeClass('is-open').addClass('is-close');
118    } else {
119        $gnav.removeClass('is-close').addClass('is-open');
120    }
121});
122
123// スライドメニューリンククリック
124$(function(){
125    $('.l-sp-nav .js-open-slide').on('click', function() {
126        // 1. すべてリセット(.l-sp-nav 内だけ)
127        $('.l-sp-nav .js-open-slide').removeClass('is-active')
128            .find('img').each(function() {
129                $(this).attr('src', $(this).attr('src').replace('-white.svg', '.svg'));
130            });
131
132        // 2. クリックされた要素をactiveに
133        $(this).addClass('is-active');
134
135        // 3. is-activeが付いた要素の画像をwhiteに変更
136        $(this).find('img').attr('src', function(index, src) {
137            return src.replace('.svg', '-white.svg');
138        });
139    });
140
141    // ナビボタン(ハンバーガー)クリック時にリセット(.l-sp-nav 内だけ)
142    $('.js-navBtn').on('click', function() {
143        $('.l-sp-nav .js-open-slide').removeClass('is-active')
144            .find('img').each(function() {
145                $(this).attr('src', $(this).attr('src').replace('-white.svg', '.svg'));
146            });
147    });
148});
149
150
151// slickのスライド切り替え後も発火させる
152const $slider = $('.js-header-menu');
153$slider.on('afterChange', function(event, slick, currentSlide) {
154    $('.l-sp-nav .js-open-slide').removeClass('is-active')
155        .find('img').each(function() {
156            $(this).attr('src', $(this).attr('src').replace('-white.svg', '.svg'));
157        });
158
159    const $currentNav = $('.l-sp-nav .js-open-slide[data-slide="' + currentSlide + '"]');
160
161    $currentNav.addClass('is-active')
162        .find('img').attr('src', function(index, src) {
163            return src.replace('.
163svg', '-white.svg');
164        });
165});
166
167
168$(window).on('load', function() {
169    setTimeout(function() {
170        $('.l-sp-nav').addClass('is-visible');
171    }, 100); // 0.1秒遅延
172});
173
174/* ------------------------------------------
175グローバルメニューの動き
176------------------------------------------ */
177$(function(){
178    // スライドメニューのリンククリック
179    $('.js-open-slide').on('click', function() {
180        $('.js-open-slide').removeClass('is-active'); // 一旦全部リセット
181        $(this).addClass('is-active'); // クリックしたものだけactive
182    });
183
184    // ナビボタン(ハンバーガーメニュー)クリック時にもリセット
185    $('.js-navBtn').on('click', function() {
186        $('.js-open-slide').removeClass('is-active');
187    });
188});
189
190
191/* ------------------------------------------
192アコーディオン(条件検索ボタン)
193------------------------------------------ */
194const buttons = document.querySelectorAll('.js-accordion-btn');
195
196buttons.forEach((button) => {
197    button.addEventListener('click', (e) => {
198        const body = e.currentTarget.nextElementSibling;
199        const text = body.children[0];
200        const textHeight = text.offsetHeight;
201
202        const menu = e.currentTarget.parentNode;
203        menu.classList.toggle('is-open');
204
205        if(menu.classList.contains('is-open')) {
206            body.style.height = textHeight + 'px';
207        } else {
208            body.style.height = 0;
209        }
210    });
211});
212
213/* ------------------------------------------
214アコーディオン(c-accordion01)
215------------------------------------------ */
216const accordion01 = document.querySelectorAll('.js-accordion01-heading');
217
218accordion01.forEach((button) => {
219    button.addEventListener('click', (e) => {
220        const body = e.currentTarget.nextElementSibling;
221        const text = body.children[0];
222        const menu = e.currentTarget.parentNode;
223
224        // 開くとき
225        if (!menu.classList.contains('is-open')) {
226            menu.classList.add('is-open');
227
228            // とりあえず今時点の高さで開く
229            body.style.height = text.offsetHeight + 'px';
230
231            const lazyImages = text.querySelectorAll('img[loading="lazy"]');
232            let loadedCount = 0;
233
234            if (lazyImages.length > 0) {
235                lazyImages.forEach((img) => {
236                    const done = () => {
237                        loadedCount++;
238                        if (loadedCount === lazyImages.length) {
239                            // 全画像ロード後、次フレームで高さを再計測
240                            requestAnimationFrame(() => {
241                                body.style.height = text.offsetHeight + 'px';
242                            });
243                        }
244                    };
245
246                    if (img.complete) {
247                        done();
248                    } else {
249                        img.addEventListener('load', done);
250                        img.addEventListener('error', done);
251                    }
252                });
253            }
254        } 
255        // 閉じるとき
256        else {
257            menu.classList.remove('is-open');
258            body.style.height = 0;
259        }
260    });
261});
262
263
264
265/* ------------------------------------------
266アコーディオン(c-accordion02)
267------------------------------------------ */
268const accordion02 = document.querySelectorAll('.js-accordion02-heading');
269
270accordion02.forEach((button) => {
271    button.addEventListener('click', (e) => {
272        const body = e.currentTarget.nextElementSibling;
273        const text = body.children[0];
274        const textHeight = text.offsetHeight;
275
276        const menu = e.currentTarget.parentNode;
277        menu.classList.toggle('is-open');
278
279        if(menu.classList.contains('is-open')) {
280            body.style.height = textHeight + 'px';
281        } else {
282            body.style.height = 0;
283        }
284    });
285});
286
287/* ------------------------------------------
288スムーススクロール
289------------------------------------------ */
290$(function() {
291
292    // ページロード時にハッシュがある場合のスクロール
293    if (location.hash) {
294        var urlHash = location.hash;
295        $('html, body').stop().scrollTop(0); // 一旦トップへ
296        setTimeout(function() {
297            var target = $(urlHash);
298            if (target.length) {
299                // headerの高さを毎回取得して可変対応
300                var headerHeight = $('header').length ? $('header').outerHeight() : 0;
301                var position = target.offset().top - headerHeight;
302                $('html, body').stop().animate({ scrollTop: position }, 500);
303            }
304        }, 100);
305    }
306
307    // ページ内リンクのスムーススクロール
308    $('a[href^="#"]').not('[href="#"]').click(function(e) {
309        e.preventDefault(); // デフォルトのジャンプ無効化
310        var href = $(this).attr('href');
311        var target = $(href);
312
313        if (target.length) {
314            var headerHeight = $('header').length ? $('header').outerHeight() : 0;
315            var position = target.offset().top - headerHeight;
316            $('html, body').stop().animate({ scrollTop: position }, 500);
317        }
318    });
319
320});
321
322/* ------------------------------------------
323タブ
324------------------------------------------ */
325$(".js-tab-nav > div").click(function() {
326	var num = $(".js-tab-nav > div").index(this);
327	$(".js-tab-contents > div").removeClass('is-active');
328	$(".js-tab-contents > div").eq(num).addClass('is-active');
329	$(".js-tab-nav > div").removeClass('is-active');
330	$(this).addClass('is-active');
331});
332
333//よくあるご質問のタブ
334document.addEventListener("DOMContentLoaded", () => {
335    // すべての .js-tab01 を見つける
336    document.querySelectorAll('.js-tab01').forEach(tabsContainer => {
337        // このタブグループ内のタブとパネルを取得
338        const tabs = tabsContainer.querySelectorAll('[role="tab"]');
339        const panels = tabsContainer.querySelectorAll('[role="tabpanel"]');
340
341        // 各タブにクリックイベントを設定
342        tabs.forEach(tab => {
343            tab.addEventListener("click", () => {
344                // ステップ1: すべてのタブを非アクティブにする
345                tabs.forEach(t => t.setAttribute('aria-selected', 'false'));
346                panels.forEach(p => p.hidden = true);
347
348                // ステップ2: クリックされたタブをアクティブにする
349                tab.setAttribute('aria-selected', 'true');
350
351                // ステップ3: 対応するパネルを見つける(安全に!)
352                const targetId = tab.getAttribute('aria-controls');
353
354                // エラー防止:aria-controlsが設定されているかチェック
355                if (!targetId) {
356                    console.warn('aria-controlsが設定されていないタブがあります:', tab);
357                    return;
358                }
359
360                // IDに特殊文字が含まれていても安全に検索
361                const safeId = (window.CSS && CSS.escape) ? CSS.escape(targetId) : targetId;
362                const panel = tabsContainer.querySelector(`#${safeId}`);
363
364                // エラー防止:パネルが見つかるかチェック
365                if (!panel) {
366                    console.warn('対応するパネルが見つかりません:', targetId);
367                    return;
368                }
369
370                // ステップ4: 見つけたパネルを表示
371                panel.hidden = false;
372            });
373        });
374    });
375});
376
377/* ------------------------------------------
378アニメーショントリガー(gsap.js&ScrollTrigger.js)
379------------------------------------------ */
380
381// フェードイン
382gsap.utils.toArray(".js-animate-fadein").forEach(function(target) {
383	gsap.from(target, {
384		scrollTrigger: {
385			trigger: target,
386			start: "top 75%",
387			toggleActions: "play none none complete",
388			once: true,
389			//markers: true,
390		},
391		duration: 1, //アニメーションの時間の設定
392		opacity: 0,
393	});
394});
395
396// フェードイン(発動が早いパターン)
397gsap.utils.toArray(".js-animate-fadein--early").forEach(function(target) {
398	gsap.from(target, {
399		scrollTrigger: {
400			trigger: target,
401			start: "top 90%",
402			toggleActions: "play none none complete",
403			once: true,
404			//markers: true,
405		},
406		duration: 1, //アニメーションの時間の設定
407		opacity: 0,
408	});
409});
410
411// 上から表示
412gsap.utils.toArray(".js-animate-down").forEach(function(target) {
413	gsap.from(target, {
414		scrollTrigger: {
415			trigger: target,
416			start: "top 75%",
417			toggleActions: "play none none complete",
418			once: true,
419			// markers: true,
420		},
421		duration: 1, //アニメーションの時間の設定
422		opacity: 0,
423		yPercent: -8,
424	});
425});
426
427// 下から表示
428gsap.utils.toArray(".js-animate-up").forEach(function(target) {
429	gsap.from(target, {
430		scrollTrigger: {
431			trigger: target,
432			start: "top 75%",
433			toggleActions: "play none none complete",
434			once: true,
435			// markers: true,
436		},
437		duration: 1, //アニメーションの時間の設定
438		opacity: 0,
439		yPercent: 8,
440
441		// うまく動かない
442		// stagger: { //同じclass名のバリデーションの設定
443		// 	each: 0.5, //ディレイ時間
444		// 	from: "start", //出現方法の指定
445		// },
446	});
447});
448
449// 左から表示
450gsap.utils.toArray(".js-animate-ltr").forEach(function(target) {
451	gsap.from(target, {
452		scrollTrigger: {
453			trigger: target,
454			start: "top 75%",
455			toggleActions: "play none none complete",
456			once: true,
457			// markers: true,
458		},
459		duration: 1, //アニメーションの時間の設定
460		opacity: 0,
461		xPercent: 8,
462	});
463});
464
465// 右から表示
466gsap.utils.toArray(".js-animate-rtl").forEach(function(target) {
467	gsap.from(target, {
468		scrollTrigger: {
469			trigger: target,
470			start: "top 75%",
471			toggleActions: "play none none complete",
472			once: true,
473			// markers: true,
474		},
475		duration: 1, //アニメーションの時間の設定
476		opacity: 0,
477		xPercent: -8,
478	});
479});
480
481// 左から表示(img)
482gsap.utils.toArray(".js-animate-img-ltr").forEach(function(target) {
483	gsap.from(target, {
484		scrollTrigger: {
485			trigger: target,
486			start: "top 75%",
487			toggleActions: "play none none complete",
488			once: true,
489			// markers: true,
490		},
491		duration: 1, //アニメーションの時間の設定
492		opacity: 0,
493		xPercent: -50,
494	});
495});
496
497// 右から表示(img)
498gsap.utils.toArray(".js-animate-img-rtl").forEach(function(target) {
499	gsap.from(target, {
500		scrollTrigger: {
501			trigger: target,
502			start: "top 75%",
503			toggleActions: "play none none complete",
504			once: true,
505			// markers: true,
506		},
507		duration: 1, //アニメーションの時間の設定
508		opacity: 0,
509		xPercent: 50,
510	});
511});
512
513// fvロゴフェードイン
514gsap.utils.toArray(".js-animate-logo-fadein").forEach(function(target) {
515    gsap.to(target, {
516        duration: 1,
517        delay: 1.5,
518        opacity: 1,
519        ease: "power2.out"
520    });
521});
522
523// スクロールトリガー(動きはCSSでつける)
524$('.js-ScrollTrigger').each(function (){
525    var dy = $(window).scrollTop(); //y座標を取得
526    var connect = $(this);
527    var position = connect.offset().top - dy; //end:.itemのpositionを取得
528    var w_height = $(window).height();
529    var actionYposition = 0; //アクション発生のY座標の指定
530    var windowHeight = $(window).height() - actionYposition;
531
532    arrivalAction();
533
534    $(window).scroll(function(){
535        arrivalAction();
536      });
537
538  //表示アクション
539  function arrivalAction()
540  {
541
542          var dy = $(window).scrollTop(); //y座標を取得
543          var position = connect.offset().top - dy; //end:.itemのpositionを取得
544          var windowHeight = $(window).height() -actionYposition;
545
546          if(position < windowHeight) {
547            connect.addClass("is-animation");
548          }
549  }
550});
551
552
553/* ------------------------------------------
554// パララックス(gsap.js&ScrollTrigger.js)
555------------------------------------------ */
556
557gsap.registerPlugin(ScrollTrigger);
558
559gsap.utils.toArray('.js-parallax').forEach(wrap => {
560    const y = wrap.getAttribute('data-y') || -100;
561
562    gsap.to(wrap, {
563        y: y,
564        scrollTrigger: {
565            trigger: wrap,
566            start: 'top bottom',
567            end: 'bottom top',
568            scrub: 1,
569            // markers: true
570        }
571    });
572});
573
574
575/*=======================================
576テーブルスクロール
577=========================================*/
578document.addEventListener('DOMContentLoaded', () => {
579    const tableContainers = document.querySelectorAll('.c-table-scrool');
580    for (const tableContainer of tableContainers) {
581      const scrollHint = document.createElement('div');
582      scrollHint.className = 'scroll-hint scroll-hint--show';
583      scrollHint.innerText = '横にスクロールできます';
584      tableContainer.appendChild(scrollHint);
585  
586      const showScrollHint = () => {
587        if (tableContainer.scrollWidth > tableContainer.clientWidth) {
588          scrollHint.classList.add('scroll-hint--show');
589        } else {
590          scrollHint.classList.remove('scroll-hint--show');
591        }
592      };
593      showScrollHint();
594  
595      tableContainer.addEventListener('scroll', () => {
596        scrollHint.classList.remove('scroll-hint--show');
597      });
598    }
599});
600
601
602/* ------------------------------------------
603// カレンダーの月送り
604------------------------------------------ */
605document.addEventListener("DOMContentLoaded", () => {
606  document.querySelectorAll('.js-tab02').forEach(tabsContainer => {
607    const tabs = tabsContainer.querySelectorAll('[role="tab"]');
608    const panels = tabsContainer.querySelectorAll('[role="tabpanel"]');
609
610    // 現在のタブインデックスを取得
611    let currentIndex = Array.from(tabs).findIndex(tab => tab.getAttribute('aria-selected') === 'true');
612
613    // タブ切替関数
614    function activateTab(index) {
615      if (index < 0 || index >= tabs.length) return;
616
617      tabs.forEach(t => t.setAttribute('aria-selected', 'false'));
618      panels.forEach(p => p.hidden = true);
619
620      const activeTab = tabs[index];
621      activeTab.setAttribute('aria-selected', 'true');
622
623      const targetId = activeTab.getAttribute('aria-controls');
624      if (!targetId) return;
625      const safeId = (window.CSS && CSS.escape) ? CSS.escape(targetId) : targetId;
626      const panel = tabsContainer.querySelector(`#${safeId}`);
627      if (panel) panel.hidden = false;
628
629      currentIndex = index;
630    }
631
632    // タブクリック
633    tabs.forEach((tab, i) => {
634      tab.addEventListener("click", () => activateTab(i));
635    });
636
637    // パネル内の月送りボタンを取得してクリックイベント
638    panels.forEach(panel => {
639      const prevBtn = panel.querySelector('.is-prev');
640      const nextBtn = panel.querySelector('.is-next');
641
642      if (prevBtn) {
643        prevBtn.addEventListener("click", () => {
644          if (currentIndex > 0) activateTab(currentIndex - 1);
645        });
646      }
647
648      if (nextBtn) {
649        nextBtn.addEventListener("click", () => {
650          if (currentIndex < tabs.length - 1) activateTab(currentIndex + 1);
651        });
652      }
653    });
654  });
655});
656
657/* ------------------------------------------
658// メニュー
659------------------------------------------ */
660$(function() {
661    $('.js-header-menu').slick({
662        arrows: false,
663        autoplay: false,
664        dots: true,
665        dotsClass: 'dots-list',
666        appendDots: $('.js-menudots-box'),
667        infinite: false,
668        slidesToScroll: 1,
669        slidesToShow: 3,
670        autoplaySpeed: 2000,
671        variableWidth: false,
672        speed: 400,
673        responsive: [{
674                breakpoint: 1280,
675                settings: {
676                    slidesToShow: 2,
677                    slidesToScroll: 1,
678                    variableWidth: false,
679                }
680            },
681            {
682                breakpoint: 767,
683                settings: {
684                    slidesToShow: 1,
685                    slidesToScroll: 1,
686                    variableWidth: false,
687                }
688            },
689        ]
690    });
691});
692
693
694/* ------------------------------------------
695// 買うページにリンクしたときにダイアログを表示
696------------------------------------------ */
697document.addEventListener('DOMContentLoaded', () => {
698  // URLパラメータを取得
699  const params = new URLSearchParams(window.location.search);
700  const dialogId = params.get('dialog');
701  // dialogId が指定されていれば開く
702  if (dialogId) {
703    const dialog = document.getElementById(`dialog-${dialogId}`);
704    if (dialog) {
705      dialog.showModal();
706    }
707  }
708});
709
710// 閉じたときにリンクリセット
711document.querySelectorAll('dialog').forEach(dialog => {
712  dialog.addEventListener('close', () => {
713    const url = new URL(window.location);
714    url.searchParams.delete('dialog');
715    history.replaceState(null, '', url); // URL書き換え
716  });
717});
718
719
720// ヘッダー下にスクロールで非表示
721// let lastScroll = 0;
722
723// $(window).on('scroll', function () {
724//   // PCサイズ(1024px以上)のときだけ実行
725//   if (window.innerWidth < 1024) return;
726
727//   const current = $(this).scrollTop();
728
729//   if (current > lastScroll && current > 700) {
730//     // 下スクロール → 隠す
731//     $('.l-header, .l-nav-stay').addClass('is-hide');
732//   } else {
733//     // 上スクロール → 表示
734//     $('.l-header, .l-nav-stay').removeClass('is-hide');
735//   }
736
737//   lastScroll = current;
738// });
739
740let lastScroll = 0;
741
742$(window).on('scroll', function () {
743  // PCサイズ(1024px以上)のときだけ実行
744  if (window.innerWidth < 1024) return;
745
746  const current = $(this).scrollTop();
747
748  if (current > lastScroll && current > 700) {
749    // 下スクロール → 隠す
750    $('.l-nav-stay').addClass('is-hide');
751  } else {
752    // 上スクロール → 表示
753    $('.l-nav-stay').removeClass('is-hide');
754  }
755
756  lastScroll = current;
757});
758
759
760
761
762// バナー================================================
763// .c-bannerが存在するか確認
764const bannerElement = document.querySelector('.js-banner');
765
766if (bannerElement) {
767
768  const KEY = 'bannerClosedTime';
769  const HOUR = 60 * 60 * 1000; // 1時間
770
771  const now = new Date().getTime();
772  const closedTime = localStorage.getItem(KEY);
773
774  // 1時間以内なら表示しない
775  if (closedTime && (now - closedTime < HOUR)) {
776    bannerElement.style.display = 'none';
777  } else {
778
779    // 表示アニメーション
780    gsap.from(bannerElement, {
781      opacity: 1,
782      y: 0,
783      scrollTrigger: {
784        trigger: bannerElement,
785        start: "top 50%",
786        end: "bottom top",
787        toggleActions: "play none none reverse"
788      }
789    });
790
791    // ×ボタンのクリックイベント
792    const closeBtn = bannerElement.querySelector('span');
793    if (closeBtn) {
794      closeBtn.addEventListener('click', function () {
795
796        // 閉じた時間を保存
797        localStorage.setItem(KEY, new Date().getTime());
798
799        // バナーを非表示
800        gsap.to(bannerElement, {
801          autoAlpha: 0,
802          y: 50,
803        });
804      });
805    }
806  }
807}

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.