특정 텍스트 clipboard에 복사하기 : 사파리 환경은 또 다르다고

게임 이벤트 쿠폰을 입력하거나, 어떤 이벤트의 추천인을 입력하는 등의 다양한 이벤트가 있다.

이때, 쿠폰 번호나 추천인 아이디 같은 특정 텍스를 버튼 하나만 눌러서 복사가 되도록 하는 기능이 있다.

보기엔 간단해 보이는 이 기능을 실제로 어떻게 구현하는지 알아보자. 

 

라떼는 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)
     }
  }
},[])

해당 페이지에 접속했을 때 유저가 이벤트에 참여했는지 아닌지 바로 통신을 통해 파악하고

해당 유저의 코드를 상태값에 담아 둔 뒤에, 버튼을 클릭하는 즉시 해당 코드를 담아주니

클립보드에 잘 복사가 되는 것을 알 수 있었다!

 

 


포스팅 작성에 참고한 감사한 글들

- MDN : execCommand()

- MDN : navigator.clipboard

- 카샤님의 블로그 : Javascript Clipboard