PageSourceSearch

https://www.city.kai.yamanashi.jp/ssi/js/common.js

js city.kai.yamanashi.jp collected 2026-10-03 17:34:13 UTC 24,897 bytes, 717 lines download raw bytes

1/*********************************************************************************
2案件ごとにカスタマイズする関数
3*********************************************************************************/
4
5// アコーディオン設定(スマートフォン用)
6/*********************************************************************************
7	概要:
8		javascriptで、アコーディオンのためのタグ・イベントを追加する。
9	使用例:
10		setAccordion();
11	引数:
12		無し
13	返り値:
14		無し
15	詳細:
16		アコーディオンさせたい場合、タップさせる要素(タイトル)に対して class="acc_title" を追加する。
17		<div>
18			<h2 class="acc_title">タイトル</h2>
19			<ul>
20				...
21			</ul>
22		</div>
23		上記の場合、h2に .acc_title を付与することで、ulが表示/非表示される。
24
25		<div>
26			<h2 class="acc_title">タイトル</h2>
27		</div>
28		のように、acc_titleの兄弟要素がない場合はアコーディオンにならない
29*********************************************************************************/
30function setAccordion() {
31	let accTitle = document.querySelectorAll('.acc_title');
32	// クラス.acc_title の内側にlabelタグを作成。
33	function createLabel() {
34		accTitle.forEach((element, index) => {
35			let parentElement = element.parentNode;
36			let childElementNum = parentElement.children.length;
37			if (childElementNum !== 1) {
38				if (childElementNum === 2 && parentElement.children[1].children.length == 0) {
39					parentElement.classList.add('no_acc');
40				} else {
41					const label = document.createElement('label');
42					label.setAttribute('for', `acc_ttl_label${index}`);
43					label.textContent = element.textContent;
44					element.innerHTML = '';
45					element.appendChild(label);
46
47					element.insertAdjacentHTML('beforebegin', '<input id="acc_ttl_label' + index + '" type="checkbox">');
48					parentElement.classList.add('acc_parent');
49				}
50			} else {
51				parentElement.classList.add('no_acc');
52			}
53		});
54	}
55	// クラス.acc_titleにイベントを割り当てる。
56	function addEventToAccTitle() {
57		accTitle.forEach((element) => {
58			if (!(element.parentElement.classList.contains('no_acc'))) {
59				element.addEventListener('click', function () {
60					this.classList.toggle('acc_open');
61				});
62			}
63		});
64		// controller.abort(); イベントが必要なくなったらabort()を実行してイベントを削除。
65	}
66	function createAndSetAccordion() {
67		createLabel();
68		addEventToAccTitle();
69	}
70	// すべてを実行
71	if (accTitle.length !== 0) {
72		createAndSetAccordion();
73	}
74}
75
76// スマートフォン用アコーディオン設定(class付与)
77/*********************************************************************************
78	概要:
79		javascriptで、アコーディオンのためのクラスを追加する。
80	使用例:
81		setAccordionClass();
82	引数:
83		無し
84	返り値:
85		無し
86	詳細:
87		こちらを使用する場合は、setAccordion()を無効にすること。※併用不可
88*********************************************************************************/
89function setAccordionClass() {
90	document.querySelectorAll('.acc_title').forEach(function(element) {
91		element.setAttribute('tabindex', '0');
92		element.classList.add('accordion');
93		let span = document.createElement('span');
94		span.classList.add('accordion_icon');
95		while (element.firstChild) {
96			span.appendChild(element.firstChild);
97		}
98		element.appendChild(span);
99
100		element.addEventListener('click', toggleAccordion);
101		element.addEventListener('keypress', function(event) {
102			if (event.key === 'Enter' || event.key === ' ') {
103				toggleAccordion.call(element);
104			}
105		});
106	});
107
108	document.querySelectorAll('.acc_title a').forEach(function(link) {
109		link.addEventListener('click', function(event) {
110			event.stopPropagation();
111		});
112		link.addEventListener('keypress', function(event) {
113			if (event.key === 'Enter' || event.key === ' ') {
114				event.stopPropagation();
115			}
116		});
117	});
118}
119function toggleAccordion() {
120	this.classList.toggle('acc_open');
121}
122
123// 空の関連情報を削除
124/*********************************************************************************
125	概要:
126		空の関連情報を削除
127	使用例:
128		emptyKanrenRemove();
129	引数:
130		無し
131	返り値:
132		無し
133*********************************************************************************/
134function emptyKanrenRemove() {
135	let kanrenElement = document.querySelector('#kanren_info');
136	//取得した#kanren_infoの子要素が1件だけであれば#kanren_infoを削除
137	if (kanrenElement !== null) {
138		if (kanrenElement.childElementCount == 1) {
139			kanrenElement.remove();
140		}
141	}
142}
143
144// 画面内に要素があるか判定
145/*********************************************************************************
146	概要:
147		要素が見えているかの判定(ビューポート内にあるか)
148		画面内にある場合には、監視を指定した要素にクラス.is_visibleが追加される。
149		(画面から外れると.is_visibleは消える)
150	使用例:
151		chkViewElement('#header');
152		chkViewElement('.detail_free');
153	引数:
154		要素名(querySelectorAllの仕様による)
155	返り値:
156		無し
157*********************************************************************************/
158function chkViewElement(target) {
159	const targetElements = document.querySelectorAll(target);
160	const options = {
161		rootMargin: '0px',
162		threshold: 0
163	};
164	const observer = new IntersectionObserver(intersect, options);
165	targetElements.forEach((targetElement) => {
166		observer.observe(targetElement);
167	});
168
169	function intersect(entries) {
170		entries.forEach((entry) => {
171			if (entry.isIntersecting) {
172				// ビューポート内にある時
173				entry.target.classList.add('is_visible');
174			} else {
175				// ビューポート内に無い時
176				entry.target.classList.remove('is_visible');
177			}
178		});
179	}
180}
181
182// #headerが画面外に出たらクラスを付与
183/*********************************************************************************
184	概要:
185		#headerが画面外に出たら、bodyに対してクラス header_hideを追加
186	使用例:
187		chkHeaderView();
188	引数;
189		無し
190	返り値:
191		無し
192	詳細:
193		無し
194*********************************************************************************/
195function chkHeaderView() {
196	const observer = new IntersectionObserver(entries => {
197		// 要素が画面内から外に出たかどうかをチェック
198		let addClassElm = document.querySelector('body');
199		entries.forEach(entry => {
200		if (entry.isIntersecting) {
201			// 要素が画面内にいる場合
202			addClassElm.classList.remove('header_hide');
203		} else {
204			// 要素が画面外にいる場合
205			addClassElm.classList.add('header_hide');
206		}
207		});
208	});
209	// 要素を監視する
210	observer.observe(document.querySelector("#header"));
211}
212function headerScroll_scroll() {
213	let headerElement = document.querySelector('#header');
214	let headerElementHeight = headerElement.clientHeight;
215	let scrollVal = 0;
216	if (headerElement != null) {
217		window.addEventListener('scroll', (event) => {
218			scrollVal = window.scrollY;
219			console.log(scrollVal);
220			if (scrollVal > headerElementHeight) {
221				headerElement.classList.add('header_fixed');
222			} else {
223				headerElement.classList.remove('header_fixed');
224			}
225		});
226	}
227}
228
229// スムーススクロール
230/*********************************************************************************
231	概要:
232		アンカーリンク押下時にアニメーションしながら移動する
233	使用例:
234		setAnchorAnime()
235	引数:
236		無し
237	返り値:
238		無し
239	詳細:
240		無し
241*********************************************************************************/
242
243function setAnchorAnime() {
244	if (!accElementChk()) {
245		document.querySelectorAll('a[href^="#"]').forEach(function(elm) {
246			elm.addEventListener('click', function(event) {
247				event.preventDefault();
248				var target = document.getElementById(this.hash.slice(1));
249				var scrollTop = target.offsetTop;
250				window.scrollTo({
251					top: scrollTop,
252					behavior: "smooth"
253				});
254				// location.hash = this.hash;
255			});
256		});
257	}
258}
259
260
261
262// 閲覧補助機能(開閉)
263/*********************************************************************************
264	概要:
265		閲覧補助機能展開用クラス追加・削除イベント
266	使用例:
267		accSupportMenu();
268	引数:
269		無し
270	返り値:
271		無し
272	詳細:
273		無し
274*********************************************************************************/
275function accSupportMenu() {
276	let eventList = ['click'];
277	// 開閉ボタンをbuttoタグではなくaタグを使用する場合は、下記のpushしている行のコメントを削除して有効にしてください。
278	// eventList.push('keydown');
279
280	let closeElement = document.querySelector('#support_menu_close');
281	let openElement = document.querySelector('#support_menu_open');
282	let boxElement = document.querySelector('#support_menu_box');
283
284	let existChk = [closeElement, openElement, boxElement];
285	// 全ての要素が存在すれば実行
286	if (domExistCheck(existChk)) {
287		eventList.forEach((element) => {
288			closeElement.addEventListener(element, () => {
289				openElement.classList.remove('open');
290				boxElement.classList.remove('open');
291			});
292			openElement.addEventListener(element, (event) => {
293				openElement.classList.toggle('open');
294				boxElement.classList.toggle('open');
295				event.stopPropagation();
296			});
297
298			boxElement.addEventListener('click', function(event) {
299				event.stopPropagation();
300			});
301			document.addEventListener('click', function() {
302				if(boxElement.classList.contains('open')){
303					openElement.classList.remove('open');
304					boxElement.classList.remove('open');
305				}
306			});
307			// キーボード操作対応
308			closeElement.addEventListener('focusout',(event)=> {
309				if(boxElement.classList.contains('open')){
310					openElement.classList.remove('open');
311					boxElement.classList.remove('open');
312				}
313			});
314		});
315	}
316}
317
318// スマートフォンボトムメニュー(メニュー開閉)
319/*********************************************************************************
320	概要:
321		スマートフォン画面のメニュー展開用クラス追加・削除イベント
322	使用例:
323		spMenuToggle();
324	引数:
325		無し
326	返り値:
327		無し
328	詳細:
329		無し
330*********************************************************************************/
331function spMenuToggle() {
332	let spMenuElement = document.querySelector('#spm_menu');
333	let targetElement = document.querySelector('#container');
334
335	let existChk = [spMenuElement, targetElement];
336	// 全ての要素が存在すれば実行
337	if (domExistCheck(existChk)) {
338		spMenuElement.addEventListener('click', () => {
339			targetElement.classList.remove('spm_search_open','spm_kaiyu_open');
340			targetElement.classList.toggle('spm_menu_open');
341		});
342	}
343}
344
345// スマートフォンボトムメニュー(検索開閉)
346/*********************************************************************************
347	概要:
348		スマートフォン画面の検索展開用クラス追加・削除イベント
349	使用例:
350		spSearchToggle();
351	引数:
352		無し
353	返り値:
354		無し
355	詳細:
356		無し
357*********************************************************************************/
358function spSearchToggle() {
359	let spSearchElement = document.querySelector('#spm_search');
360	let targetElement = document.querySelector('#container');
361
362	let existChk = [spSearchElement, targetElement];
363	// 全ての要素が存在すれば実行
364	if (domExistCheck(existChk)) {
365		spSearchElement.addEventListener('click', () => {
366			targetElement.classList.remove('spm_menu_open','spm_kaiyu_open');
367			targetElement.classList.toggle('spm_search_open');
368		});
369	}
370}
371
372// スマートフォンボトムメニュー(回遊ボタン開閉)
373/*********************************************************************************
374	概要:
375		スマートフォン画面の回遊ボタン展開用クラス追加・削除イベント
376	使用例:
377		spKaiyuToggle();
378	引数:
379		無し
380	返り値:
381		無し
382	詳細:
383		無し
384*********************************************************************************/
385function spKaiyuToggle() {
386	let spKaiyuElement = document.querySelector('#spm_kaiyu');
387	let targetElement = document.querySelector('#container');
388
389	let existChk = [spKaiyuElement, targetElement];
390	// 全ての要素が存在すれば実行
391	if (domExistCheck(existChk)) {
392		spKaiyuElement.addEventListener('click', () => {
393			targetElement.classList.remove('spm_menu_open','spm_search_open');
394			targetElement.classList.toggle('spm_kaiyu_open');
395		});
396	}
397}
398
399// スマートフォンボトムメニュー(ページトップボタン)
400/*********************************************************************************
401	概要:
402		スマートフォン画面のページトップボタンを押した際、展開メニューが開いていた場合は閉じる
403	使用例:
404		spPagetopButtonToggle();
405	引数:
406		無し
407	返り値:
408		無し
409	詳細:
410		無し
411*********************************************************************************/
412function spPagetopButtonToggle() {
413	let spPagetopButtonElement = document.querySelector('#spm_pagetop');
414	let targetElement = document.querySelector('#container');
415
416	let existChk = [spPagetopButtonElement, targetElement];
417	// 全ての要素が存在すれば実行
418	if (domExistCheck(existChk)) {
419		spPagetopButtonElement.addEventListener('click', () => {
420			targetElement.classList.remove('spm_menu_open','spm_search_open','spm_kaiyu_open');
421		});
422	}
423}
424
425// スマートフォン閲覧補助(開閉)
426/*********************************************************************************
427	概要:
428		スマートフォン画面の閲覧補助展開用クラス追加・削除イベント
429	使用例:
430		spSupportToggle();
431	引数:
432		無し
433	返り値:
434		無し
435	詳細:
436		無し
437*********************************************************************************/
438function spSupportToggle() {
439	let spSupportElement = document.querySelector('#spm_support');
440	let targetElement = document.querySelector('#container');
441
442	let existChk = [spSupportElement, targetElement];
443	// 全ての要素が存在すれば実行
444	if (domExistCheck(existChk)) {
445		spSupportElement.addEventListener('click', () => {
446			targetElement.classList.toggle('spm_support_open');
447		});
448	}
449}
450
451// サブサイトメニュー(開閉)
452/*********************************************************************************
453	概要:
454		ブラウザ幅でdetailsタグのopen属性のトグルを行う
455	使用例:
456		toggleWithMediaQuery();
457	引数:
458		無し
459	返り値:
460		無し
461	詳細:
462		サブサイトメニューのdetailタグのopen属性をブラウザ幅で追加・削除する。
463		※element.open = true; でopen属性追加。
464*********************************************************************************/
465function toggleWithMediaQuery() {
466	function handleTableChange(event) {
467		if (event.matches) {
468			submenu.forEach((element) => {
469				element.open = true;
470			});
471		} else {
472			submenu.forEach((element) => {
473				element.open = false;
474			});
475		}
476	}
477	const mediaQuery = window.matchMedia('(min-width: 1080px)');
478
479	// メニューの展開トグル
480	let submenu = Array.from(document.querySelectorAll('#subsite_menu_wrap details'));
481	if (submenu.length > 0) {
482		// mediaQuery.addListener(handleTableChange);
483		mediaQuery.addEventListener('change', handleTableChange);
484		handleTableChange(mediaQuery);
485	}
486}
487
488// mouseoverイベントが発生したらfocusを削除
489/*********************************************************************************
490	概要:
491		グローバルナビゲーションにフォーカスがあたっている状態(:focus)でマウスを乗せると同時に複数箇所展開されるため、
492		mouseoverイベントが発生したらfocusを削除(blur)する
493	使用例:
494		gnavDelFocusOnmouseover();
495	引数:
496		無し
497	返り値:
498		無し
499	詳細:
500		何もしないと、キーボードのfocusが残った状態でマウスでhoverするとfocusで展開したメニューは開いたままhoverした要素が展開されます。
501		それを防ぐため、mouseoverイベント発生時にfocusを削除します。
502*********************************************************************************/
503function gnavDelFocusOnmouseover() {
504	let ul = document.querySelector('#top_search');
505	if (ul !== null) {
506		ul.addEventListener('mouseover', function(){
507			let focused = document.activeElement;
508			if(focused.tagName == 'A'){
509				focused.blur();
510			}
511		});
512	}
513}
514
515// スクロールするとクラスを付与
516/*********************************************************************************
517	概要:
518		一定量スクロールすると、elmIDで指定した要素にクラス.fixedを追加する。
519	使用例:
520		scrollFixed(200, '#footer_link_pagetop'); // 200pxスクロールすると#footer_link_pagetopに.fixedを追加
521	引数:
522		scrollVal : スクロール量(px)
523		elmID : 対象要素。IDを指定。クラスを指定した場合はページ内の最初のクラスが対象になる。
524	返り値:
525		無し
526*********************************************************************************/
527function scrollFixed(scrollVal,elmID) {
528	window.addEventListener('scroll', function() {
529		let header = document.querySelector(elmID);
530		let scrollPosition = window.scrollY;
531		if (header != null) {
532			if (scrollPosition > scrollVal) {
533				header.classList.add('fixed');
534			} else {
535				header.classList.remove('fixed');
536			}
537		}
538	});
539}
540
541// makeCloneSlideElm関数
542/*********************************************************************************
543	概要:
544		複数枚表示するスライドに対応するため、
545		規定値以上になるまでulタグの中身のコピーを作成する。
546		※コピー後のliタグの数は、処理前のliタグの倍数となる。
547	使用例:
548		makeCloneSlideElm('ul#top_banner_pr_list', 4);
549		makeCloneSlideElm('ul#top_banner_pr_list', 4, 1); //1枚の時はスライドを実行しない
550
551	引数:
552		targetElm : 対象要素。ulタグを指定。
553					ページ内に同じクラスの要素が複数ある場合は
554					最初の要素が対象となる。
555		MAXNUM    : スライドが発火するために必要な要素の数。
556					例えば、スライドの画像を一度に3件表示したいときは、最低4件必要になる。
557		NOSLIDENUM: スライドさせない枚数。デフォルト(省略したとき)は 1。
558	返り値:
559		真偽値(true / false)
560		true  : スライドの要素のコピーを実行したとき
561		        →スライドの準備が出来た状態
562		false : スライドの要素のコピーをしなかったとき
563		        (スライドの枚数が最低枚数かそれ以下のとき)
564		        →スライドをさせない状態
565	使用例:
566		※コピーを作成した後にスライドを実行させる順番を守るため、asyncを使用。
567		----------------------------------------
568		async function setSlide() { // 名前は何でもよい。
569			let doSlide = await makeCloneSlideElm('ul#top_banner_pr_list', 4, 1); // awaitを指定し、処理が終わるまで待機。
570			if(doSlide === true){
571				banner_pr_slider(); // スライドさせる関数
572			}
573		}
574		setSlide();
575		----------------------------------------
576		※単にスライドをコピーする場合は下記のいずれか一方の書式を使用
577		makeCloneSlideElm('ul#top_banner_pr_list', 4);
578		makeCloneSlideElm('ul#top_banner_pr_list', 4, 1); //1枚の時はスライドを実行しない
579*********************************************************************************/
580function makeCloneSlideElm(parentElm, MAXNUM, NOSLIDENUM = 1) {
581	function getChildlenElm(parentElm) {
582		return ulSlide.querySelectorAll(parentElm + ' > *').length;
583	}
584	const ulSlide = document.querySelector(parentElm);
585	let liNum = getChildlenElm(parentElm);
586	const ulClone = ulSlide.cloneNode(true);
587	if (liNum > NOSLIDENUM) {
588		if (liNum < MAXNUM) {
589			while (liNum < MAXNUM) {
590				ulSlide.insertAdjacentHTML('beforeend', ulClone.innerHTML);
591				liNum = getChildlenElm(parentElm);
592			}
593		}
594		return true;
595	} else {
596		return false;
597	}
598}
599
600
601/* ==================================================
602html読み込み後に実行される処理
603================================================== */
604document.addEventListener('DOMContentLoaded', function() {
605
606	// setAccordion();   /* こちらを使用する場合は setAccordionClass() を無効にすること ※併用不可 */
607	setAccordionClass(); /* こちらを使用する場合は setAccordion() を無効にすること ※併用不可 */
608
609	// イベントの登録(ポップアップの展開など)
610	accSupportMenu();
611	spMenuToggle();
612	spSearchToggle();
613	spKaiyuToggle();
614	spPagetopButtonToggle();
615	spSupportToggle();
616	toggleWithMediaQuery();
617	scrollFixed(200, '#footer_link_pagetop'); // 200pxスクロールすると#footer_link_pagetopに.fixedを追加
618
619	// 指定のクラスが存在するページの#main_bodyと#sidebar2それぞれに目次を作成(無い場合は作成されない)
620	if (document.querySelectorAll('.detail,.site_index,.site_detail,.soshiki_kakuka,.sitemap,.tpl_detail,.tpl_site_index,.tpl_site_detail,.tpl_soshiki_kakuka,.tpl_sitemap').length > 0) {
621		addTocToDetail('#main_body,#sidebar2');
622	}
623
624	// 部局、所属、分類ページで実行
625	if (document.querySelectorAll('.soshiki_kakubu,.soshiki_kakuka,.life,.tpl_soshiki_kakubu,.tpl_soshiki_kakuka,.tpl_life').length > 0) {
626		emptyKanrenRemove();
627	}
628
629	// chkViewElement('#skip');
630	// chkViewElement('.detail_free');
631	chkHeaderView();
632	// headerScroll_scroll();
633	// setAnchorAnime();
634
635	// グローバルナビゲーションを展開させる際に使用する
636	// gnavDelFocusOnmouseover();
637
638});
639/* ==================================================
640html読み込み後に実行される処理 ここまで
641================================================== */
642
643
644function scrollToHash(hash, immediate) {
645  if (!hash || hash === '#') return;
646
647  const id = hash.replace(/^#/, '');
648  const target = document.getElementById(id);
649  if (!target) return;
650
651  // 固定ヘッダー高さ(必要なら適宜変更)
652  const header = document.querySelector('header');
653  const headerHeight = header ? header.offsetHeight : 0;
654
655  const rect = target.getBoundingClientRect();
656  const currentScroll =
657    window.pageYOffset || document.documentElement.scrollTop;
658
659  let top = rect.top + currentScroll - headerHeight;
660
661  // Safari が変な位置を返した場合の保険
662  const maxScroll =
663    document.documentElement.scrollHeight - window.innerHeight;
664  top = Math.max(0, Math.min(top, maxScroll));
665
666  window.scrollTo({
667    top,
668    behavior: immediate ? 'auto' : 'smooth'
669  });
670}
671
672
673// ページ内リンククリック時の挙動
674function setAnchorAnime() {
675  document.querySelectorAll('a[href^="#"]').forEach(function (elm) {
676    elm.addEventListener('click', function (event) {
677      const hash = this.hash;
678      if (!hash || hash === '#') return;
679
680      event.preventDefault();
681      scrollToHash(hash, false);
682    });
683  });
684}
685
686
687// 初回読み込み時(別ページから #付きURL で飛んできた場合)の再制御
688(function () {
689  const initialHash = window.location.hash;
690
691  // まず Safari の標準動作を無効化(スクロールしない状態に)
692  if (initialHash) {
693    history.replaceState(
694      null,
695      '',
696      window.location.pathname + window.location.search
697    );
698  }
699
700  // ページの読み込み完了後、JS側でアンカー飛び直し
701  window.addEventListener('load', function () {
702    setAnchorAnime(); // クリック対応有効化
703
704    if (!initialHash) return;
705
706    setTimeout(function () {
707      scrollToHash(initialHash, true);
708
709      // URL 表示のみ元に戻す
710      history.replaceState(
711        null,
712        '',
713        window.location.pathname + window.location.search + initialHash
714      );
715    }, 150); // レイアウト確定待ち(Safari対策)
716  });
717})();

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.