크로미움 브라우저에서 p 내부 span을 삭제할 때 span이 날아간다면 ?

오랜만에 사내에서 제품으로 관리하고 있는 웹 에디터 유지보수 이슈를 진행하면서 겪은 일을 공유해보자 한다.

 

문제

아래와 같은 HTML을 사용자는 웹 에디터에서 편집하고자 한다.

<p><span style="font-family: Arial;">안녕하세요</span></p>
<p><span style="font-family: Arial;"><br /></span></p>
<p><span style="font-family: Arial;">요즘 날씨가 많이 덥네요</span></p>
<p><span style="font-family: Arial;"><br /></span></p>
<p><span style="font-family: Arial;">건강관리 잘 하세요~</span></p>

 

이 HTML은 아래와 같이 사용자에게 보일 것이다.

만약 사용자가 빈 span 태그에 캐럿( 마우스를 클릭하면 깜빡이는 것 )을 위치한 채

삭제(backspace/delete)를 눌렀다면 사용자가 기대하는 경험은 무엇일까 ?

사용자는 빈 문단이 삭제되면서 아래에 있는 문단이 한 줄 올라오는 것을 기대할 것이다.

하지만 여기서 사용자가 delete 키를 눌러 뒤의 문단을 계속적으로 삭제할 경우 어떻게 될까?

 

현재 Edge 브라우저 기준 about:blank에서 body에 contenteditable 속성을 true로 적용하고

위의 예제 HTML을 넣은 후 delete key를 연속적으로 입력한다면 p 문단 내부에 있는 span이 삭제되고

p 문단만 남는 것을 볼 수 있다.

 

그럼 이 문제를 어떻게 해결해야 할까?

해결

일단 문제가 발생하는 현상을 분석해 본 결과 아래와 같은 조건들이 발견됐다.

  • p 문단 아래 span이 존재하면서 자식으로 br이 있는 경우, 즉 p 문단의 childNodes가 1이고 span 이어야 한다.
  • span에 offsetWidth가 0이거나 0보다 큰 경우, 즉 br만 존재하거나 텍스트가 존재해야 한다.

그렇다면 위 조건들을 어떻게 풀어낼 수 있을까?

모든 조건문을 쓰기에는 어려우니 대표적인 조건들은 아래와 같을 것이다.

var sel = document.getSelection();
var range = sel.getRangeAt(0);
var startContainer = range.startContainer;
var node;

if(e.keyCode == 8){
	node = $(startContainer).prev();	
}else if(e.keyCode == 46){
	node = $(startContainer).next();
}

if(range.collapsed && node.offsetWidth  == 0 && node.paretNode.nodeName == "P" 
&& node.NodeName == "SPAN" && node.getElementsByTagName("br").length == 0 ){
 ...
}

 

 

위의 조건들 외 더 많은 조건들이 있고 무조건적인 정답은 아니다. 다양한 사용자의 상황에 따라 조건은 달라질 수 있을 것이다.

 

조건을 알맞게 설정했다면 이제 로직을 작성할 차례이다.

지금까지 웹 에디터를 개발하면서 브라우저가 노드를 삭제하는 경우는 보통 노드 내부에 값이 없기 때문이었다.

 

그래서 나는 너비 없는 문자를 제일 마지막 문단 뒤에 추가해서 노드가 삭제하는 걸 방지하고자 했다.

var childNode = node.childNodes[0];
if(childNode.childNodes[childNode.childNodes.length - 1].nodeType == 3 && 
childNode.childNodes[childNode.childNodes.length - 1] == ""){
	var textNode = document.createTextNode("&#8203");
	childNode.childNodes[0].appendChild(textNode);
}

 

하지만 너비 없는 문자를 추가해도 브라우저에서는 span 내부에 아무것도 없다는 것으로 인식해서 그런지 span태그를 삭제해 버렸다.

그렇다면 문단 내부에 텍스트를 모두 삭제하면 캐럿이 보이기 위한 br태그를 넣어줘야 하는데

마지막 문자가 삭제될 때 br 태그를 미리 넣어주면 브라우저가 span 태그를 삭제하지 않지 않을까 생각했다.

var childNode = node.childNodes[0];
if(childNode.childNodes[childNode.childNodes.length - 1].nodeType == 1 && 
childNode.childNodes[childNode.childNodes.length - 1].nodeName == "BR"){
	var brNode = document.createElement("br");
	childNode.childNodes[0].appendChild(brNode);
}

 

마지막 문자가 삭제될 때, 뒤의 추가로 문자가 있는지 확인해서  문자가 없다면  br을 추가하는 로직을 넣었더니 브라우저가 span 태그를 삭제하지 않게 됐다.

 

여기까지 일반적인 케이스에서의 문제를 해결했다.

하지만 내가 추가로 발견한 버그가 있었기 때문에 아직 끝난 게 아니었다.

 

한글은 다른 언어와 다르게 자음과 모음을 조합하면서 작성하는 언어이다.

 

그렇기 때문에 ㅎ + ㅏ + ㄴ이라는 글을 작성하고 바로 backspace key를 입력할 경우

IME(Input Method Event) 버그로 인해 브라우저에서는 이벤트의 key Event를 process로 받아온다.

또한 keyCode는 229번으로 모든 입력 값이 변경된다.

 

key Event가 process이고 keyCode가 229라면 위에서 내가 작성한 e.keyCode == 8과 e.keyCode == 46

조건은 통과되지 않을 것이다.

 

그렇기 때문에 조건문에 아래와 같은 구문을 추가해 주었다.

if(e.code == "Backspace" || e.keyCode == 8 || e.keyCode == 46)

 

이제 정상적으로 빈 문단에서 삭제, 글자가 조합되는 가운데 삭제, 텍스트가 있는 상태에서 삭제를 해도

p 내부 span을 삭제하지 않을 것이다.

'개발 > ETC' 카테고리의 다른 글

Docker 시작하기  (0) 2024.07.08
[CSS] border Trick, Customized Dashed Border  (1) 2024.01.03
[Classic ASP] Url parser EUC-KR 한글 깨짐 해결  (0) 2021.12.27
[JSP] setCharacterEncoding , setContentType  (0) 2021.11.10
CSS,HTML 과 CSS3 HTML5  (0) 2021.01.06
<