Last active
April 26, 2024 07:37
-
-
Save tak-dcxi/0ba44dc2c869b559e9a19163e0dadbd6 to your computer and use it in GitHub Desktop.
initializeSplitText
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type AccordionOptions = { | |
duration?: number | |
easing?: string | |
} | |
const defaultOptions: AccordionOptions = { | |
duration: 300, | |
easing: 'ease-in-out', | |
} | |
let isCurrentPanelTransitioning = false // 現在のパネルが開いているときの連打防止フラグ | |
let isOtherPanelTransitioning = false // 他のパネルが開いているときの連打防止フラグ | |
// アコーディオンを初期化する | |
const initializeDetailsAccordion = (details: HTMLDetailsElement, options: AccordionOptions = {}): void => { | |
if (!details) { | |
console.error('initializeDetailsAccordion: Details element is not found.') | |
return | |
} | |
const summary = details.querySelector('summary') as HTMLElement | |
const panel = details.querySelector('summary + *') as HTMLElement | |
if (!summary || !panel) { | |
console.error('initializeDetailsAccordion: Elements required for initializeDetailsAccordion are not found.') | |
return | |
} | |
const mergedOptions = { ...defaultOptions, ...options } | |
panel.style.transitionProperty = 'block-size' | |
panel.style.transitionDuration = `${Math.max(1, mergedOptions.duration || 1)}ms` | |
panel.style.transitionTimingFunction = mergedOptions.easing || '' | |
if (details.hasAttribute('name')) { | |
const detailsName = details.getAttribute('name') || '' | |
details.setAttribute('data-name', detailsName) | |
details.removeAttribute('name') | |
} | |
summary.addEventListener('click', (event: MouseEvent) => handleClick(event, details, panel), false) | |
} | |
// 縦書きモードかどうかを取得し、スクロールサイズを返す | |
const getScrollSize = (details: HTMLDetailsElement, panel: HTMLElement): string => { | |
const writingMode = window.getComputedStyle(details).writingMode | |
const scrollSize = `${writingMode.includes('vertical') ? panel.scrollWidth : panel.scrollHeight}px` | |
return scrollSize | |
} | |
// トランジション終了時の処理を扱う | |
const onTransitionEnd = (element: HTMLElement, callback: () => void): void => { | |
const onEnd = (event: TransitionEvent) => { | |
if (event.target !== element) return | |
element.removeEventListener('transitionend', onEnd) | |
callback() | |
} | |
element.addEventListener('transitionend', onEnd) | |
} | |
// アコーディオンを開く | |
const openAccordion = (details: HTMLDetailsElement, panel: HTMLElement): void => { | |
if (details.open) return | |
isCurrentPanelTransitioning = true | |
panel.style.blockSize = '0' | |
panel.style.overflow = 'clip' | |
details.open = true | |
requestAnimationFrame(() => { | |
requestAnimationFrame(() => { | |
panel.style.blockSize = getScrollSize(details, panel) | |
}) | |
onTransitionEnd(panel, () => { | |
panel.style.blockSize = '' | |
panel.style.overflow = '' | |
isCurrentPanelTransitioning = false | |
}) | |
}) | |
} | |
// アコーディオンを閉じる | |
const closeAccordion = (details: HTMLDetailsElement, panel: HTMLElement): void => { | |
if (!details.open) return | |
isCurrentPanelTransitioning = true | |
panel.style.blockSize = getScrollSize(details, panel) | |
panel.style.overflow = 'clip' | |
requestAnimationFrame(() => { | |
requestAnimationFrame(() => { | |
panel.style.blockSize = '0' | |
}) | |
onTransitionEnd(panel, () => { | |
details.open = false | |
panel.style.blockSize = '' | |
panel.style.overflow = '' | |
isCurrentPanelTransitioning = false | |
}) | |
}) | |
} | |
// 同一のdata-name属性を持つ他の開いているアコーディオンを閉じる | |
const closeOtherAccordions = (details: HTMLDetailsElement): void => { | |
const detailsName = details.getAttribute('data-name') | |
if (!detailsName) return | |
const otherDetails = document.querySelector(`details[data-name="${detailsName}"][open]`) as HTMLDetailsElement | |
if (!otherDetails || otherDetails === details) return | |
const otherPanel = otherDetails.querySelector('summary + *') as HTMLElement | |
if (!otherPanel) return | |
isOtherPanelTransitioning = true | |
otherPanel.style.blockSize = getScrollSize(otherDetails, otherPanel) | |
otherPanel.style.overflow = 'clip' | |
requestAnimationFrame(() => { | |
requestAnimationFrame(() => { | |
otherPanel.style.blockSize = '0' | |
}) | |
onTransitionEnd(otherPanel, () => { | |
otherDetails.open = false | |
otherPanel.style.blockSize = '' | |
otherPanel.style.overflow = '' | |
isOtherPanelTransitioning = false | |
}) | |
}) | |
} | |
// クリックイベントハンドラーの設定 | |
const handleClick = (event: MouseEvent, details: HTMLDetailsElement, panel: HTMLElement): void => { | |
event.preventDefault() | |
if (isCurrentPanelTransitioning || isOtherPanelTransitioning) return | |
if (details.open) { | |
closeAccordion(details, panel) | |
} else { | |
closeOtherAccordions(details) | |
openAccordion(details, panel) | |
} | |
} | |
export default initializeDetailsAccordion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment