편집 요약 없음 |
편집 요약 없음 |
||
| 1번째 줄: | 1번째 줄: | ||
/** | /** | ||
* MediaWiki | * MediaWiki 커스텀 펼치기/접기 애니메이션 | ||
* | * | ||
* | * 지원 예시: | ||
* | * | ||
* | * mw-customtoggle-KSC_LEADER | ||
* | * ↕ | ||
* mw-customcollapsible-KSC_LEADER | |||
* | |||
* mw-customtoggle-COMPANY | |||
* ↕ | |||
* mw-customcollapsible-COMPANY | |||
* | |||
* 식별자 이름과 관계없이 자동으로 연결합니다. | |||
*/ | */ | ||
| 13번째 줄: | 18번째 줄: | ||
'use strict'; | 'use strict'; | ||
var DURATION = | var DURATION = 300; | ||
var ANIMATING_CLASS = 'mw-custom-collapse-animating'; | |||
function | /** | ||
var | * 토글 요소에서 식별자 추출 | ||
* | |||
* mw-customtoggle-KSC_LEADER | |||
* → KSC_LEADER | |||
*/ | |||
function getToggleName( toggle ) { | |||
var classes = toggle.classList; | |||
var i; | var i; | ||
var prefix = 'mw-customtoggle-'; | |||
for ( i = 0; i < | for ( i = 0; i < classes.length; i++ ) { | ||
if ( | if ( classes[ i ].indexOf( prefix ) === 0 ) { | ||
return classes[ i ].substring( prefix.length ); | |||
return | |||
} | } | ||
} | } | ||
/* | return null; | ||
return | } | ||
/** | |||
* CSS 선택자에서 사용할 문자열 이스케이프 | |||
*/ | |||
function escapeSelector( value ) { | |||
if ( window.CSS && CSS.escape ) { | |||
return CSS.escape( value ); | |||
} | |||
return value.replace( | |||
/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, | |||
'\\\\$1' | |||
); | |||
} | } | ||
function | /** | ||
if ( ! | * 식별자와 연결된 펼침 대상 찾기 | ||
return; | */ | ||
function getTarget( name ) { | |||
if ( !name ) { | |||
return null; | |||
} | } | ||
/* | |||
'mw- | * class 방식 | ||
* | |||
* class=" | |||
* mw-collapsible | |||
* mw-customcollapsible-KSC_LEADER | |||
* " | |||
*/ | |||
var target = document.querySelector( | |||
'.mw-customcollapsible-' + | |||
escapeSelector( name ) | |||
); | ); | ||
if ( target ) { | |||
'mw- | return target; | ||
} | |||
/* | |||
* id 방식도 대응 | |||
*/ | |||
target = document.getElementById( | |||
'mw-customcollapsible-' + name | |||
); | ); | ||
return target; | |||
' | } | ||
/** | |||
* 실제 애니메이션을 적용할 콘텐츠 | |||
*/ | |||
function getContent( target ) { | |||
var content = target.querySelector( | |||
'.mw-collapsible-content' | |||
); | ); | ||
if ( content ) { | |||
return content; | |||
} | |||
return target; | |||
} | |||
/** | |||
* 현재 접혀있는지 확인 | |||
*/ | |||
function isCollapsed( target, content ) { | |||
if ( target.classList.contains( 'mw-collapsed' ) ) { | |||
return true; | |||
} | } | ||
return window.getComputedStyle( content ).display === 'none'; | |||
} | } | ||
function | /** | ||
if ( content.classList.contains( | * 펼치기 | ||
*/ | |||
function expand( target, content, toggle ) { | |||
if ( content.classList.contains( ANIMATING_CLASS ) ) { | |||
return; | return; | ||
} | } | ||
content.classList.add( ANIMATING_CLASS ); | |||
target.classList.remove( 'mw-collapsed' ); | |||
content.style.display = 'block'; | content.style.display = 'block'; | ||
content.style.height = 'auto'; | content.style.height = 'auto'; | ||
content.style.overflow = 'hidden'; | |||
var targetHeight = content.scrollHeight; | var targetHeight = content.scrollHeight; | ||
| 74번째 줄: | 137번째 줄: | ||
content.style.opacity = '0'; | content.style.opacity = '0'; | ||
/* | |||
* 강제 reflow | |||
*/ | |||
content.offsetHeight; | content.offsetHeight; | ||
| 98번째 줄: | 161번째 줄: | ||
animation.onfinish = function () { | animation.onfinish = function () { | ||
content.classList.remove( | content.classList.remove( ANIMATING_CLASS ); | ||
content.style.display = ''; | content.style.display = ''; | ||
content.style.height = ''; | content.style.height = ''; | ||
content.style.opacity = ''; | content.style.opacity = ''; | ||
content.style.overflow = ''; | |||
toggle.setAttribute( | |||
'aria-expanded', | |||
'true' | |||
); | |||
animation.cancel(); | animation.cancel(); | ||
}; | }; | ||
} | } | ||
function | /** | ||
if ( content.classList.contains( | * 접기 | ||
*/ | |||
function collapse( target, content, toggle ) { | |||
if ( content.classList.contains( ANIMATING_CLASS ) ) { | |||
return; | return; | ||
} | } | ||
var startHeight = content.getBoundingClientRect().height; | content.classList.add( ANIMATING_CLASS ); | ||
var startHeight = | |||
content.getBoundingClientRect().height; | |||
content.style.display = 'block'; | content.style.display = 'block'; | ||
content. | content.style.overflow = 'hidden'; | ||
var animation = content.animate( | var animation = content.animate( | ||
| 139번째 줄: | 212번째 줄: | ||
animation.onfinish = function () { | animation.onfinish = function () { | ||
target.classList.add( 'mw-collapsed' ); | |||
content.classList.remove( | content.classList.remove( ANIMATING_CLASS ); | ||
content.style.display = 'none'; | content.style.display = 'none'; | ||
content.style.height = ''; | content.style.height = ''; | ||
content.style.opacity = ''; | content.style.opacity = ''; | ||
content.style.overflow = ''; | |||
toggle.setAttribute( | |||
'aria-expanded', | |||
'false' | |||
); | |||
animation.cancel(); | animation.cancel(); | ||
}; | |||
} | |||
/** | |||
* 토글 실행 | |||
*/ | |||
function toggleCollapsible( toggle ) { | |||
var name = getToggleName( toggle ); | |||
if ( !name ) { | |||
return false; | |||
} | |||
var target = getTarget( name ); | |||
if ( !target ) { | |||
return false; | |||
} | |||
var content = getContent( target ); | |||
if ( !content ) { | |||
return false; | |||
} | |||
if ( isCollapsed( target, content ) ) { | |||
}; | expand( | ||
target, | |||
content, | |||
toggle | |||
); | |||
} else { | |||
collapse( | |||
target, | |||
content, | |||
toggle | |||
); | |||
} | |||
return true; | |||
} | } | ||
/* | /** | ||
* capture | * 클릭 | ||
* | * | ||
* capture=true를 사용해서 | |||
* MediaWiki 기본 토글보다 먼저 처리 | |||
*/ | */ | ||
document.addEventListener( | document.addEventListener( | ||
| 161번째 줄: | 279번째 줄: | ||
function ( event ) { | function ( event ) { | ||
var toggle = event.target.closest( | var toggle = event.target.closest( | ||
'.mw- | '.mw-customtoggle' | ||
); | ); | ||
| 168번째 줄: | 286번째 줄: | ||
} | } | ||
var | if ( !getToggleName( toggle ) ) { | ||
'.mw- | return; | ||
} | |||
event.preventDefault(); | |||
event.stopPropagation(); | |||
event.stopImmediatePropagation(); | |||
toggleCollapsible( toggle ); | |||
}, | |||
true | |||
); | |||
/** | |||
* Enter / Space 키 지원 | |||
*/ | |||
document.addEventListener( | |||
'keydown', | |||
function ( event ) { | |||
if ( | |||
event.key !== 'Enter' && | |||
event.key !== ' ' | |||
) { | |||
return; | |||
} | |||
var toggle = event.target.closest( | |||
'.mw-customtoggle' | |||
); | ); | ||
if ( ! | if ( !toggle ) { | ||
return; | return; | ||
} | } | ||
if ( !getToggleName( toggle ) ) { | |||
if ( ! | |||
return; | return; | ||
} | } | ||
| 184번째 줄: | 326번째 줄: | ||
event.preventDefault(); | event.preventDefault(); | ||
event.stopPropagation(); | event.stopPropagation(); | ||
event.stopImmediatePropagation(); | |||
toggleCollapsible( toggle ); | |||
}, | }, | ||
true | true | ||
2026년 8월 20일 (목) 13:06 판
/**
* MediaWiki 커스텀 펼치기/접기 애니메이션
*
* 지원 예시:
*
* mw-customtoggle-KSC_LEADER
* ↕
* mw-customcollapsible-KSC_LEADER
*
* mw-customtoggle-COMPANY
* ↕
* mw-customcollapsible-COMPANY
*
* 식별자 이름과 관계없이 자동으로 연결합니다.
*/
( function () {
'use strict';
var DURATION = 300;
var ANIMATING_CLASS = 'mw-custom-collapse-animating';
/**
* 토글 요소에서 식별자 추출
*
* mw-customtoggle-KSC_LEADER
* → KSC_LEADER
*/
function getToggleName( toggle ) {
var classes = toggle.classList;
var i;
var prefix = 'mw-customtoggle-';
for ( i = 0; i < classes.length; i++ ) {
if ( classes[ i ].indexOf( prefix ) === 0 ) {
return classes[ i ].substring( prefix.length );
}
}
return null;
}
/**
* CSS 선택자에서 사용할 문자열 이스케이프
*/
function escapeSelector( value ) {
if ( window.CSS && CSS.escape ) {
return CSS.escape( value );
}
return value.replace(
/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g,
'\\\\$1'
);
}
/**
* 식별자와 연결된 펼침 대상 찾기
*/
function getTarget( name ) {
if ( !name ) {
return null;
}
/*
* class 방식
*
* class="
* mw-collapsible
* mw-customcollapsible-KSC_LEADER
* "
*/
var target = document.querySelector(
'.mw-customcollapsible-' +
escapeSelector( name )
);
if ( target ) {
return target;
}
/*
* id 방식도 대응
*/
target = document.getElementById(
'mw-customcollapsible-' + name
);
return target;
}
/**
* 실제 애니메이션을 적용할 콘텐츠
*/
function getContent( target ) {
var content = target.querySelector(
'.mw-collapsible-content'
);
if ( content ) {
return content;
}
return target;
}
/**
* 현재 접혀있는지 확인
*/
function isCollapsed( target, content ) {
if ( target.classList.contains( 'mw-collapsed' ) ) {
return true;
}
return window.getComputedStyle( content ).display === 'none';
}
/**
* 펼치기
*/
function expand( target, content, toggle ) {
if ( content.classList.contains( ANIMATING_CLASS ) ) {
return;
}
content.classList.add( ANIMATING_CLASS );
target.classList.remove( 'mw-collapsed' );
content.style.display = 'block';
content.style.height = 'auto';
content.style.overflow = 'hidden';
var targetHeight = content.scrollHeight;
content.style.height = '0px';
content.style.opacity = '0';
/*
* 강제 reflow
*/
content.offsetHeight;
var animation = content.animate(
[
{
height: '0px',
opacity: 0
},
{
height: targetHeight + 'px',
opacity: 1
}
],
{
duration: DURATION,
easing: 'ease',
fill: 'forwards'
}
);
animation.onfinish = function () {
content.classList.remove( ANIMATING_CLASS );
content.style.display = '';
content.style.height = '';
content.style.opacity = '';
content.style.overflow = '';
toggle.setAttribute(
'aria-expanded',
'true'
);
animation.cancel();
};
}
/**
* 접기
*/
function collapse( target, content, toggle ) {
if ( content.classList.contains( ANIMATING_CLASS ) ) {
return;
}
content.classList.add( ANIMATING_CLASS );
var startHeight =
content.getBoundingClientRect().height;
content.style.display = 'block';
content.style.overflow = 'hidden';
var animation = content.animate(
[
{
height: startHeight + 'px',
opacity: 1
},
{
height: '0px',
opacity: 0
}
],
{
duration: DURATION,
easing: 'ease',
fill: 'forwards'
}
);
animation.onfinish = function () {
target.classList.add( 'mw-collapsed' );
content.classList.remove( ANIMATING_CLASS );
content.style.display = 'none';
content.style.height = '';
content.style.opacity = '';
content.style.overflow = '';
toggle.setAttribute(
'aria-expanded',
'false'
);
animation.cancel();
};
}
/**
* 토글 실행
*/
function toggleCollapsible( toggle ) {
var name = getToggleName( toggle );
if ( !name ) {
return false;
}
var target = getTarget( name );
if ( !target ) {
return false;
}
var content = getContent( target );
if ( !content ) {
return false;
}
if ( isCollapsed( target, content ) ) {
expand(
target,
content,
toggle
);
} else {
collapse(
target,
content,
toggle
);
}
return true;
}
/**
* 클릭
*
* capture=true를 사용해서
* MediaWiki 기본 토글보다 먼저 처리
*/
document.addEventListener(
'click',
function ( event ) {
var toggle = event.target.closest(
'.mw-customtoggle'
);
if ( !toggle ) {
return;
}
if ( !getToggleName( toggle ) ) {
return;
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
toggleCollapsible( toggle );
},
true
);
/**
* Enter / Space 키 지원
*/
document.addEventListener(
'keydown',
function ( event ) {
if (
event.key !== 'Enter' &&
event.key !== ' '
) {
return;
}
var toggle = event.target.closest(
'.mw-customtoggle'
);
if ( !toggle ) {
return;
}
if ( !getToggleName( toggle ) ) {
return;
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
toggleCollapsible( toggle );
},
true
);
}() );