게임 이벤트 쿠폰을 입력하거나, 어떤 이벤트의 추천인을 입력하는 등의 다양한 이벤트가 있다.
이때, 쿠폰 번호나 추천인 아이디 같은 특정 텍스를 버튼 하나만 눌러서 복사가 되도록 하는 기능이 있다.
보기엔 간단해 보이는 이 기능을 실제로 어떻게 구현하는지 알아보자.
라떼는 execCommand였다 이거야
기존에는 document.execCommand('copy')를 이용해서 특정 텍스트를 클립보드에 복사했다.
여기서 볼 수 있는 execCommand 메서드는 선택된 요소에 대한 다양한 행동을 할 수 있게 해주는 메서드다.
그래서 해당 메서드는 웹 에디터를 구현할 때 많이 쓰였다.
해당 메서드에서 사용할 수 있는 여러 옵션 중 copy라는 옵션은 선택한 텍스트를 클립보드로 복사하게 해주는 옵션이다.
function fallbackCopyText(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
// 스타일 설정으로 textarea를 화면에서 숨김
textArea.style.position = 'fixed';
textArea.style.top = '-1000px';
textArea.style.left = '-1000px';
document.body.appendChild(textArea);
// 텍스트 선택
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
alert('텍스트가 클립보드에 복사되었습니다! (Fallback)');
} else {
alert('클립보드 복사에 실패했습니다.');
}
} catch (err) {
console.error('Fallback: 클립보드 복사 실패:', err);
alert('클립보드 복사에 실패했습니다.');
}
document.body.removeChild(textArea);
}
위의 코드를 통해 execCommand('copy')에 대해 알아보자.
여기 있는 fallbackCopyText 함수는 특정 이벤트가 발생했을 때,
특정 텍스트를 인자로 받는 함수라고 하자.
fallbackCopyText 함수가 실행될 때 인자로 받은 값을 생성한 textarea 요소에 value로 그 값을 설정한다.
그다음 해당 요소의 스타일을 조작해 화면에 보이지 않도록 스타일을 수정하고,
해당 요소에 있는 값을 선택하는 select()를 사용 한 뒤, execCommand('copy')를 사용한다.
그래서 텍스트가 있으면 복사했다는 메시지를 아니면 실패했다는 메시지를 띄우는 흐름으로
텍스트를 클립보드에 복사하게 된다.
이러한 과정을 통해서 특정 텍스트를 클립보드에 복사할 수 있는데
execCommand는 더 이상 지원하지 않는 메서드라고 한다.
물론 일부 브라우저에서는 아직 지원하는데 지금은 다른 방법을 사용한다.
navigator.clipboard API
우매한 중생들을 위해 새로운 API가 나왔으니 바로 navigator.clipboard API 가 되겠다.
해당 api를 통해 자바스크립트로 좀 더 편하게 클립보드에 텍스트를 복사할 수 있다.
document.getElementById('copyButton').addEventListener('click', function() {
const text = document.getElementById('textInput').value;
if (text) {
navigator.clipboard.writeText(text)
} else {
alert('복사할 텍스트를 입력해주세요.');
}
});
GPT 비서가 짠 코드를 통해 살펴보면 copyButton이라는 버튼을 클릭할 경우
특정 텍스트가 담겨 있는 영역의 value를 가지고 와서 바로 클립보드에 복사하게 된다.
execCommand('copy')를 활용할 때 보다 훨씬 간결한 코드로 사용할 수 있게 됐다.
해당 API에는 기본적으로 4가지 메서드가 있다.
write / writeText / read / readText 가 있는데 복사는 writeText 메서드를 사용했다.
각 메서드 별로 간단하게 알아보면,
write와 writeText는 클립보드에 데이터를 쓸 때 사용하고, promise로 작업 완료 여부를 반환한다.
read와 readText는 클립보드에 있는 데이터를 읽어올 때 사용하고, promise로 데이터를 반환한다.
좀 더 세부적으로 나눠보면
write는 다양한 데이터 유형을 클립보드에 쓸 수 있다 (텍스트, 이미지 등)
writeText는 문자열 데이터만 클립보드에 쓸 수 있다.
read는 다양한 데이터 유형을 클립보드에서 읽어올 수 있고,
readText는 문자열 데이터만 읽어올 수 있다.
상황에 따라서 알맞게 사용하면 될 것 같다.
사파리 브라우저에서는 안되는데요?
저기서 바로 사용하고 행복하고 오래오래 코딩했습니다~!
하면 블로그로 정리까지는 안 했을 텐데, 하나 막혀버린 포인트가 있어서 가져왔다.
내가 블로킹 됐던 상황을 정리해 보면 다음과 같다.
- 유저가 특정 버튼을 클릭함
- 해당 유저가 이벤트에 참여한 유저인지 아닌지를 서버를 통해 확인
- 해당 유저가 이벤트에 참여한 유저라면 해당 유저에게 고유한 코드를 반환함
- 반환한 코드를 바로 클립보드에 복사해서 유저가 손쉽게 붙여 넣기 할 수 있도록 세팅
여기서 4번째 포인트에서 막혀버렸다.
이유는 내가 지금 개발하고 있는 환경은 특정 앱 내에서 웹뷰 영역에 보이는 부분을 React를 통해 구현하고 있는데
안드로이드 기기인지, ios 기기인지에 따라 브라우저 환경이 달라졌는데,
해당 기능이 ios 환경 = 사파리 브라우저일 때 동작하지 않았다.
const onClickCopy = async () => {
if (이벤트에 참여했는지 여부 판단) {
const res = await axios.get(유저 코드 가져오는 통신 api)
if (res.status === 200) {
navigator.clipboard.writeText(res.code)
}
} else {
showModal(true) // 이벤트 참여 독려 모달
}
}
위와 같이 통신 후 분기처리를 통해 구현했는데
이게 크롬 환경에서는 돌아가는데 사파리 환경에서는 돌아가지 않았다..
검색을 통해 알아본 결과 사용자의 액션 직후 복사가 이루어져야 한다는 부분이 있는데
위의 코드에서는 callback 이후 이루어진 동작이기에 작동을 안 할 수 있다는 점이었다.
그래서 해결 방법은 버튼을 누르기 전 미리 해당 유저의 코드를 state에 담아두고
복사 버튼을 누른 경우 해당 텍스트를 바로 복사해 주는 로직으로 구현했다.
const [userCode, setUserCode] = useState("");
const onClickCopy = async () => {
if (userCode) {
navigator.clipboard.writeText(userCode)
} else {
showModal(true) // 이벤트 참여 독려 모달
}
}
useEffect(()=>{
if (이벤트에 참여한 사람인지 판단) {
const res = await axios.get(유저 코드 가져오는 통신 api)
if (res.status === 200) {
setUserCode(res.code)
}
}
},[])
해당 페이지에 접속했을 때 유저가 이벤트에 참여했는지 아닌지 바로 통신을 통해 파악하고
해당 유저의 코드를 상태값에 담아 둔 뒤에, 버튼을 클릭하는 즉시 해당 코드를 담아주니
클립보드에 잘 복사가 되는 것을 알 수 있었다!
포스팅 작성에 참고한 감사한 글들
- 카샤님의 블로그 : Javascript Clipboard
'Stack > JS' 카테고리의 다른 글
데이터 타입 | null과 undefined : 코어 자바스크립트 씹뜯맛즐 하기 (1) | 2024.11.21 |
---|---|
데이터 타입 | 기본형과 참조형 : 코어 자바스크립트 씹뜯맛즐 하기 (2) | 2024.11.20 |
This : 이거라며.. 왜 다르냐고 (1) | 2024.11.15 |
클로저 (Closure) : 자바스크립트의 네크로멘서 (3) | 2024.11.11 |
호이스팅 (Hoisting) : 변수, 함수 끌어~ 올려! (9) | 2024.11.09 |