프로젝트를 얼추 완성하고 배포한 뒤, 친구들한테 테스트를 부탁했는데 한 친구가 복사 기능이 안된다고 했다.
그 친구는 아이폰 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')를 통해서 복사기능을 프로그램적으로 실행시킬 수 있다.
- text는 <input>이나 <textarea>안에 있어야만 복사가 가능하다.
- text를 가지고있는 요소가 <form>태그 안에 있는 것이 아니라면, 반드시 contenteditable이어야 한다.
- text를 가지고 있는 요소는 readonly속성이여서는 안된다.
- 요소안의 text는 selection 범위안에 있어야 한다.
위의 제한사항을 지켜서 코드로 실행시키는 것은 아래와 같다.
- 카피할 텍스트는 <input>이나 <textarea>요소안에 있어야 한다.
- 카피후 다시 되돌릴 수 있도록 원래의 contenteditable과 readonly속성 값을 저장한다.
- 요소의 속성 contenteditable 은 true, readonly는 false로 설정한다.
- 선택할 요소의 범위를 설정하고, window의 selection에 추가한다.
- 전체 요소를 위한 selection 범위를 설정한다.
- 이전 contenteditable readonly 값을 복구한다.
- 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 |