본문 바로가기

Project Log/I'mticon

자바스크립트 클립보드 복사기능 - 크로스브라우징 이슈

프로젝트를 얼추 완성하고 배포한 뒤, 친구들한테 테스트를 부탁했는데 한 친구가 복사 기능이 안된다고 했다.

그 친구는 아이폰 safari에서 사용을 했는데, 찾아보니 기존 사용했던 자바스크립트 코드가 iOS safari에서는 지원이 안되는 것으로 보였다.

찾아보니 iOS < 10미만의 버전에서는 보안상의 문제 때문에 코드로 복사기능을 실행시키는 것이 불가했다. 하지만 iOS >= 10 부터는 가능하긴 하다고 한다.

 

iOS < 10 미만의 버전에서 OS clipboard 읽기/쓰기는 'shorcut keys'가 아닌 document.execCommand()로는 실행할 수 없다. 여기서 'shortcut keys'는 copy/paste 액션 메뉴나 커스텀 iOS 단축키, 아니면 블루투스 키보드의 단축키 등을 의미한다. 따라서 javascript를 이용해서 iOS 디바이스의 copy기능을 'programmatically' 실행할 수 없다. 하지만 프로그램적으로 copy할 부분을 선택한 후, 유저의 화면에 copy 툴팁을 보여줘서 유저가 copy를 선택하면 복사기능이 실행되도록 할수는 있다.

 

iOS >= 10 버전에서는 몇가지 제약사항이 있긴 하지만 execCommand('copy')를 통해서 복사기능을 프로그램적으로 실행시킬 수 있다.

  1. text는 <input>이나 <textarea>안에 있어야만 복사가 가능하다.
  2. text를 가지고있는 요소가 <form>태그 안에 있는 것이 아니라면, 반드시 contenteditable이어야 한다.
  3. text를 가지고 있는 요소는 readonly속성이여서는 안된다.
  4. 요소안의 text는 selection 범위안에 있어야 한다.

 

위의 제한사항을 지켜서 코드로 실행시키는 것은 아래와 같다.

  1. 카피할 텍스트는 <input>이나 <textarea>요소안에 있어야 한다.
  2. 카피후 다시 되돌릴 수 있도록 원래의 contenteditable과 readonly속성 값을 저장한다.
  3. 요소의 속성 contenteditable 은 true, readonly는 false로 설정한다.
  4. 선택할 요소의 범위를 설정하고, window의 selection에 추가한다.
  5. 전체 요소를 위한 selection 범위를 설정한다.
  6. 이전 contenteditable readonly 값을 복구한다.
  7. execCommand('copy')를 실행한다.
function iosCopyToClipboard(el) {
    const oldContentEditable = el.contentEditable,
    oldReadOnly = el.readOnly,
    range = document.createRange();

    el.contentEditable = true;
    el.readOnly = false;
    range.selectNodeContents(el);

    const s = window.getSelection();
    s.removeAllRanges();
    s.addRange(range);
    
    el.setSelectionRange(0, 999999); 

    el.contentEditable = oldContentEditable;
    el.readOnly = oldReadOnly;

    document.execCommand('copy');
}

위의 코드를 참고해서 애플 device에서 실행될 경우 아래의 코드가 실행되도록 하였다.

const onCopy = useCallback(() => {
    const el = document.createElement('textarea');
    el.value = data.emoticon;
    el.style.position = 'absolute';
    el.style.left = '-9999px';
    document.body.appendChild(el);

    // iOS인 경우 아래 코드 시행
    if (navigator.userAgent.match(/ipad|ipod|iphone/i)) {
      const range = document.createRange();
      range.selectNodeContents(el);

      el.contentEditable = true;
      el.readOnly = false;

      const s = window.getSelection();
      s.removeAllRanges();
      s.addRange(range);

      el.setSelectionRange(0, data.emoticon.length);
      
    } else { //그 외의 브라우저
      el.select();
    }

    document.execCommand('copy');
    document.body.removeChild(el);

    //copy modal보이기
    handleModalVisibility();
    //copyCount올리는 API
    dispatch(increaseCopyCnt(data.id));
  }, [dispatch, data, handleModalVisibility]);

코드를 실행시켜보니 복사기능은 제대로 되는데 화면 스크롤이 맨 아래로 내려간다ㅠㅠ

아마도 복사할때 textarea를 select하는 과정에서 발생하는 문제인 것 같았다. 화면에 보이지 않도록 el.style.position = 'absolute' , el.style.left = '-9999px'를 적용시켜 놓았기에  textarea가 보이지는 않지만, body에 append된 요소긴 하니까 append된 위치인 body의 맨 아래 부분으로 스크롤이 이동하는 것으로 보였다.

 

그래서 어떻게 할지 고민하다가 클릭 이벤트의 target요소에 <textarea>를 append 하는 것을 시도해보기로 했다. 블럭이 클릭됐을 때 카피기능이 동작하는 것이므로, 카피할 text를 가진 <textarea>는 현재 화면의 블럭요소에 append가 되고, 스타일 opacity속성은 0으로 줘서 화면에서는 보이지 않도록 해주었다.

  const onCopy = useCallback((e) => {
    const el = document.createElement('textarea');
    el.value = data.emoticon;
    el.style.opacity = '0';
    e.target.appendChild(el);

    // handle iOS as a special case
    if (navigator.userAgent.match(/ipad|ipod|iphone/i)) {
      const range = document.createRange();
      range.selectNodeContents(el);

      el.contentEditable = true;
      el.readOnly = false;

      const s = window.getSelection();
      s.removeAllRanges();
      s.addRange(range);

      el.setSelectionRange(0, data.emoticon.length);
    } else {
      el.select();
    }
    
    document.execCommand('copy');
    e.target.removeChild(el);

    //copy modal보이기
    handleModalVisibility();
    //copyCount올리는 API
    dispatch(increaseCopyCnt(data.id));
  }, [dispatch, data, handleModalVisibility]);

참고 링크

https://stackoverflow.com/questions/34045777/copy-to-clipboard-using-javascript-in-ios/34046084

'Project Log > I'mticon' 카테고리의 다른 글

프로젝트 후기  (2) 2019.10.15
자바스크립트 클립보드 복사 기능 구현  (0) 2019.10.05
프로젝트 변경  (0) 2019.10.04
이미지 업로드시 미리보여주기 기능  (0) 2019.09.30
개인용 사진첩 만들기  (0) 2019.09.27