logo hsb.horse
← 스니펫 목록으로 돌아가기

Snippets

네이티브 UI 요소를 복제하여 스타일 상속하기

cloneNode로 호스트 앱의 기존 버튼을 복제한 후 아이콘과 텍스트만 교체하여 시각적 일관성을 유지하는 경량 패턴. 난독화된 CSS와 싸울 필요 없음.

게시일: 수정일:

브라우저 확장 프로그램이나 기업 포털의 임베디드 UI에서 호스트 앱의 디자인 시스템을 재현하는 것은 어렵습니다. 난독화된 클래스 이름이나 프레임워크 고유 스타일을 추적하는 대신, 기존 네이티브 요소를 복제하고 필요한 부분만 교체하면 시각적 일관성을 “무료로” 얻을 수 있습니다.

코드

interface CloneButtonOptions {
/** 복제할 템플릿 요소의 선택자 */
templateSelector: string
/** 교체할 새 텍스트 */
text: string
/** 교체할 새 아이콘 (선택사항) */
icon?: string | HTMLElement
/** 클릭 핸들러 */
onClick: (event: MouseEvent) => void
}
/**
* 호스트 앱의 기존 버튼을 복제하여 새 버튼 생성
*/
export const cloneNativeButton = (
options: CloneButtonOptions
): HTMLElement | null => {
const { templateSelector, text, icon, onClick } = options
// 템플릿 요소 가져오기
const template = document.querySelector<HTMLElement>(templateSelector)
if (!template) {
console.warn(`Template not found: ${templateSelector}`)
return null
}
// 깊은 복제 생성 (이벤트 핸들러 제외)
const cloned = template.cloneNode(true) as HTMLElement
// 텍스트 노드 교체
const textNode = findTextNode(cloned)
if (textNode) {
textNode.textContent = text
}
// 아이콘 교체 (지정된 경우)
if (icon) {
const iconElement = findIconElement(cloned)
if (iconElement) {
const newIcon = typeof icon === 'string'
? createIconFromString(icon)
: icon
iconElement.replaceWith(newIcon)
}
}
// 이벤트 리스너는 복제되지 않으므로 새로 설정
cloned.addEventListener('click', onClick)
// ID가 있으면 제거 (중복 방지)
if (cloned.id) {
cloned.removeAttribute('id')
}
return cloned
}
/**
* 요소 내 첫 번째 텍스트 노드 찾기
*/
const findTextNode = (element: HTMLElement): Text | null => {
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
{
acceptNode: (node) => {
// 공백만 있는 노드 제외
return node.textContent?.trim()
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_REJECT
}
}
)
return walker.nextNode() as Text | null
}
/**
* 요소 내 첫 번째 아이콘 요소 찾기 (svg, img, i 등)
*/
const findIconElement = (element: HTMLElement): Element | null => {
return (
element.querySelector('svg') ||
element.querySelector('img') ||
element.querySelector('i[class*="icon"]') ||
element.querySelector('[role="img"]')
)
}
/**
* HTML 문자열에서 아이콘 요소 생성
*/
const createIconFromString = (html: string): HTMLElement => {
const template = document.createElement('template')
template.innerHTML = html.trim()
return template.content.firstElementChild as HTMLElement
}

사용 예시

// GitHub의 기존 버튼 스타일 재사용
const myButton = cloneNativeButton({
templateSelector: '.Button--primary',
text: 'Deploy',
icon: '<svg>...</svg>',
onClick: () => {
console.log('Deploying...')
}
})
// DOM에 추가
document.querySelector('.toolbar')?.appendChild(myButton)

작동 원리

  1. querySelector로 복제할 템플릿 요소 가져오기
  2. cloneNode(true)로 깊은 복사 생성 (자식 요소 포함)
  3. TreeWalker로 텍스트 노드 탐색 및 교체
  4. querySelector로 아이콘 요소 찾아서 교체
  5. 이벤트 리스너는 복제되지 않으므로 addEventListener로 새로 설정
  6. id 속성이 있으면 제거하여 중복 방지

cloneNode는 스타일, 클래스, data 속성을 보존하지만 이벤트 리스너는 복사되지 않는 점에 유의하세요.

장점

  • 시각적 일관성: 호스트 앱의 디자인 시스템을 자연스럽게 상속
  • CSS 불필요: 난독화된 클래스 이름이나 프레임워크 스타일을 추적할 필요 없음
  • 유지보수성 높음: 호스트 디자인 업데이트에 자동으로 따라감
  • 경량: DOM API만으로 구현되어 의존성 없음

주의사항

cloneNode는 DOM의 현재 상태를 복사하므로 동적으로 변하는 클래스나 스타일(호버 상태 등)은 복제되지 않습니다. 또한 템플릿 요소가 삭제되면 작동하지 않으므로 존재 확인을 포함해야 합니다.

응용 사례

  • 브라우저 확장에서 시각적으로 일관된 버튼 추가
  • 복잡한 기업 포털 디자인 시스템에 자연스럽게 녹아드는 기능 임베드
  • 서드파티 대시보드에 커스텀 조작 패널 삽입