"복사" 버튼 하나로 코드·링크·텍스트를 바로 클립보드에 저장. navigator.clipboard API로 깔끔하게 구현하고 복사 완료 피드백까지 완성해봐요.
브라우저가 제공하는 클립보드 접근 API예요. 버튼 하나로 텍스트를 복사할 수 있어 사용자 경험을 크게 향상시켜요.
핵심 3줄: await navigator.clipboard.writeText(text) — 이게 전부예요. async/await로 감싸고, 완료 후 버튼 텍스트를 바꿔주면 끝이에요.
| 방법 | 지원 범위 | 특징 | 추천도 |
|---|---|---|---|
document.execCommand('copy') |
모든 브라우저 (구형 포함) | 텍스트 선택 후 복사. 동기식. 이미 deprecated 됨 | 구식 |
navigator.clipboard.writeText() |
Chrome 66+, Firefox 63+, Safari 13.1+ | Promise 기반 비동기. HTTPS 또는 localhost 필요 | 권장 |
navigator.clipboard는 보안 컨텍스트(HTTPS 또는 localhost)에서만 동작해요. HTTP 사이트에서는 document.execCommand 폴백이 필요해요.
클립보드 API는 버튼 클릭 같은 사용자 제스처 안에서만 호출할 수 있어요. 페이지 로드 시 자동으로 복사하려 하면 차단돼요.
iOS Safari 13.4 미만에서는 navigator.clipboard가 없을 수 있어요. try/catch로 에러를 잡고 execCommand로 폴백 처리하세요.
복사가 됐는지 사용자가 알 수 없으면 불안해요. 버튼 텍스트 변경, 색상 변화, 토스트 알림 등으로 복사 완료를 명확하게 알려주세요.
3가지 복사 패턴을 직접 체험해보세요. 버튼을 클릭하면 클립보드에 복사돼요.
① 기본 복사 버튼
텍스트를 수정하고 복사 버튼을 눌러보세요. 2초 후 버튼이 원래대로 돌아와요.
② 코드 블록 복사
코드 영역 오른쪽 상단의 copy 버튼을 눌러보세요.
const copyToClipboard = async (text) => {
try {
await navigator.clipboard.writeText(text);
console.log('복사 완료!');
} catch (err) {
console.error('복사 실패:', err);
}
};
③ 현재 페이지 URL 복사
현재 페이지의 URL을 클립보드에 복사해요. 블로그나 공유 버튼에 유용해요.
기본 복사 버튼부터 심화 패턴까지 단계별로 살펴봐요.
이것만 알면 80% 완성이에요. async/await로 복사하고 버튼 텍스트를 바꿔요.
const btn = document.querySelector('#copyBtn'); const text = '복사할 텍스트를 여기에'; btn.addEventListener('click', async () => { await navigator.clipboard.writeText(text); // 복사 완료 피드백 btn.textContent = '복사됨! ✓'; btn.style.background = '#16a34a'; // 초록색으로 변경 // 2초 후 원래대로 setTimeout(() => { btn.textContent = '📋 복사'; btn.style.background = ''; }, 2000); });
input 또는 textarea 값을 복사할 때는 .value를 읽어서 writeText에 넘겨요.
const input = document.querySelector('#myInput'); const btn = document.querySelector('#copyBtn'); btn.addEventListener('click', async () => { const text = input.value.trim(); if (!text) { alert('복사할 내용이 없어요!'); return; } try { await navigator.clipboard.writeText(text); showFeedback(btn, '복사됨! ✓'); } catch (err) { console.error('복사 실패:', err); fallbackCopy(text); // execCommand 폴백 } }); function showFeedback(btn, msg) { const original = btn.textContent; btn.textContent = msg; btn.disabled = true; setTimeout(() => { btn.textContent = original; btn.disabled = false; }, 2000); }
<!-- HTML --> <div class="code-area" style="position:relative"> <pre id="myCode">const x = 42;</pre> <button class="copy-code-btn" style="position:absolute;top:10px;right:10px" onclick="copyCode(this,'myCode')">copy</button> </div> // JavaScript async function copyCode(btn, preId) { const code = document.getElementById(preId).innerText; await navigator.clipboard.writeText(code); btn.textContent = 'copied! ✓'; btn.style.color = '#a6e3a1'; setTimeout(() => { btn.textContent = 'copy'; btn.style.color = ''; }, 2000); }
실서비스에서 자주 맞닥뜨리는 예외 상황과 UX 향상 팁이에요.
iOS Safari 구버전 대응: iOS 13.4 미만에서는 navigator.clipboard가 없을 수 있어요. try/catch 안에서 navigator.clipboard 존재 여부를 확인하고 없으면 document.execCommand('copy') 폴백을 사용하세요.
/** * 텍스트를 클립보드에 복사 (폴백 포함) * @param {string} text - 복사할 텍스트 * @returns {Promise<boolean>} 성공 여부 */ async function copyToClipboard(text) { // 최신 브라우저: navigator.clipboard API if (navigator.clipboard && window.isSecureContext) { try { await navigator.clipboard.writeText(text); return true; } catch (err) { console.warn('Clipboard API 실패, 폴백 시도:', err); } } // 폴백: execCommand (구형 브라우저 / HTTP) const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.cssText = 'position:fixed;opacity:0;top:-9999px'; document.body.appendChild(textarea); textarea.focus(); textarea.select(); try { document.execCommand('copy'); return true; } catch (err) { console.error('복사 실패:', err); return false; } finally { document.body.removeChild(textarea); } } // 사용 예시 btn.addEventListener('click', async () => { const ok = await copyToClipboard('복사할 텍스트'); if (ok) { showSuccess('복사됐어요! ✓'); } else { showError('복사에 실패했어요. 직접 선택 후 Ctrl+C를 눌러주세요.'); } });
복사 완료 시 초록색으로, 실패 시 빨간색으로 버튼 색상을 바꿔 직관적으로 상태를 알려요. 2초 후 원래 색상으로 돌아오게 setTimeout을 사용해요.
화면 하단이나 상단에 "복사됐어요!" 토스트를 띄우는 방식도 인기 있어요. 여러 곳의 복사 버튼이 있을 때 중앙 피드백으로 일관성을 줄 수 있어요.
버튼에 aria-label="코드 복사"를 추가하고, 복사 완료 시 aria-live="polite" 영역에 "복사됐습니다"를 넣으면 스크린 리더 사용자도 피드백을 받을 수 있어요.
복사 버튼 기능을 AI에게 요청할 때 이 프롬프트를 참고해보세요.
아래 프롬프트를 복사해서 AI에 붙여넣어 보세요.
내 블로그 코드 블록에 복사 버튼을 추가해줘. 요구사항: - 코드 블록(pre 태그) 오른쪽 상단에 "copy" 버튼 표시 - 클릭하면 해당 코드 내용을 클립보드에 복사 - 복사 성공 시 버튼이 "copied! ✓"로 바뀌고 초록색이 됨 - 2초 후 원래 "copy" 버튼으로 되돌아옴 - navigator.clipboard API 사용 (iOS Safari 폴백 포함) - 페이지에 코드 블록이 여러 개 있어도 각각 독립적으로 동작 - 순수 Vanilla JS (라이브러리 없이) 현재 내 코드 블록 HTML 구조: <pre class="code-block"><code>코드 내용</code></pre>
클립보드 복사 구현 시 자주 맞닥뜨리는 질문들이에요.
navigator.clipboard는 보안 컨텍스트(HTTPS)에서만 동작해요. HTTP로 배포된 사이트에서는 사용할 수 없어요. 해결책은 ① HTTPS로 배포하거나, ② document.execCommand('copy') 폴백을 함께 구현하는 것이에요. 대부분의 호스팅 서비스(Netlify, Vercel, GitHub Pages)는 기본으로 HTTPS를 제공해요.
pre.innerText와 pre.textContent의 차이 때문일 수 있어요. innerText는 화면에 렌더링된 텍스트를, textContent는 HTML 태그를 포함한 원본을 반환해요. 코드 하이라이팅을 위해 span 태그를 사용했다면 textContent에는 태그가 포함돼요. innerText를 사용하면 화면에 보이는 텍스트만 복사돼요.
data-copy-target 같은 데이터 속성으로 버튼과 복사 대상을 연결하면 돼요. 예시: <button data-copy-target="code1">복사</button> → document.querySelectorAll('[data-copy-target]').forEach(btn => btn.addEventListener('click', ...)). 이벤트 위임 패턴으로 하나의 이벤트 리스너로 모든 버튼을 처리할 수 있어요.