1. 들어가며…
웹사이트를 탐색하다 보면, 일정 길이를 초과한 텍스트에 말줄임(…) 처리가 된 것을 볼 수 있다. 특히, 뉴스 피드나 검색 결과 페이지처럼 많은 정보가 한눈에 들어와야 하는 상황에서, 말줄임 기법은 중요한 역할을 한다.
이 방법은 글을 일정 부분에서 잘라내고 "…"을 추가하여 화면 공간을 보다 효율적으로 사용하게 해준다. 아래 이미지는 말줄임 기법이 적용된 예시로, 텍스트가 일정 길이를 넘을 경우 어떻게 처리되는지 보여준다.
그렇다면, 이러한 말줄임은 어떻게 구현할 수 있을까?
JavaScript를 사용해 글자를 특정 길이로 자른 후 '…'를 추가할 수 있으며,
또는 CSS의 text-overflow
속성을 활용할 수도 있다. 아래는 CSS를 이용한 말줄임 처리 예시이다.
See the Pen text one line ellipsis by KumJungMin (@kumjungmin) on CodePen.
.text {
width: 250px;
font-size: 50px;
text-overflow: ellipsis; /* 글자가 width 이상일 때 말줄임 처리 */
white-space: nowrap; /* 내부가 넘쳐도 일렬로 배치 */
overflow: hidden; /* width를 넘어가는 경우, 숨김 */
}
그런데, 말줄임 처리 영역에 글자가 아닌 다른 요소가 있어도 제대로 작동할까?
ellipsis
는 텍스트뿐만 아니라 자식 요소가 공간을 초과할 때도 적용된다.
아래 예시를 보면, item 4
가 말줄임 처리로 인해 숨겨진 것을 확인할 수 있다.
See the Pen ellipsis hidden issue - (1) text click issue by KumJungMin (@kumjungmin) on CodePen.
그렇다면,
item 4
는 숨겨졌지만 아무런 상호작용도 일어나지 않는 걸까? 놀랍게도 아니다!
이유는 간단하다. 말줄임 처리된 요소가 화면에 보이지 않더라도 여전히 DOM에 존재하기 때문이다.
그 결과, UI 상에서 보이지 않더라도 말줄임 처리된 영역이나 그 우측 영역을 클릭하면 클릭 이벤트가 발생할 수 있다.
문제가 발생하는 예시 코드는 다음과 같다.
See the Pen ellipsis hidden issue by KumJungMin (@kumjungmin) on CodePen.
첫 번째 문제는
item 4
가 위치한 부분(빨간 박스)을 클릭하면,item 4
의 클릭 이벤트가 발생하고...
- 두 번째 문제는 말줄임 영역(…)을 클릭하면,
item 3
의 클릭 이벤트가 발생한다는 것이다!
그렇다면, 말줄임 처리 후에도 숨겨진 요소에서 클릭 이벤트가 발생하는 문제는 어떻게 해결할 수 있을까요?
이번 시간에는 CSS ellipsis
로 인해 발생할 수 있는 이러한 버그를 어떻게 수정할 수 있는지 알아보겠다!
2. css ellipsis(말줄임) 문제 해결법
문제 해결법 섹션의 코드는 아래 HTML 구조를 기준으로 설명한다. (예시 코드)
<div class="container">
<div class="chip" data-id="1">Item 1</div>
<div class="chip" data-id="2">Item 2</div>
<div class="chip" data-id="3">Item 3</div>
<div class="chip" data-id="4">Item 4</div>
</div>
1) 말줄임 영역의 상호작용 차단하기
(1) 현상
- 말줄임표 영역(…)을 클릭하면, 왼쪽에 위치한 요소에서 클릭 이벤트가 발생하는 문제가 있다.
- 이 문제는 말줄임표와 상호작용할 수 있는 요소가 인접해 있을 때 발생할 수 있다.
- 그렇다면, 이 문제를 어떻게 해결할 수 있을까?
(2) 해결 방법: CSS에서 pointer-events 설정하기
- 해결 방법은 간단하다. 바로
pointer-events
속성을 사용해 말줄임표 영역에서 클릭 이벤트가 발생하는 걸 막는 것이다. - 여기서
pointer-events
란 특정 요소가 마우스 클릭, 터치 등과 같은 이벤트를 받을 수 있는지 제어하는 CSS 속성이다.
pointer-events: auto;
pointer-events: none;
pointer-events 값 | 설명 |
---|---|
auto |
기본값으로, 요소가 정상적으로 포인터 이벤트를 수신함 |
none |
해당 요소는 모든 포인터 이벤트를 무시함 (클릭, 마우스 오버, 포커스 등의 이벤트가 발생하지 않음) |
- 말줄임 처리된 영역에서는 포인터 이벤트를 차단하고, 버튼과 같은 상호작용이 필요한
.chip
요소에서는 이벤트를 활성화해야 한다. - 이를 위해 먼저
.container
에서 포인터 이벤트를 막는다. - 하지만
.container
에 포인터 이벤트를 막으면, 하위 요소의 포인터 이벤트도 함께 차단된다.
.container {
pointer-events: none;
}
- 따라서,
.container
하위 요소 중.chip
에서는 포인터 이벤트가 정상적으로 동작하도록,.chip
에pointer-events: auto
를 추가해야 한다.
.container .chip {
pointer-events: auto;
}
(3) 결과 보기
- 이와 같이 CSS를 설정하면, 말줄임 영역을 클릭했을 때 인접한 요소에서 클릭 이벤트가 발생하는 문제를 해결할 수 있다.
.container {
pointer-events: none;
}
.container .chip {
pointer-events: auto;
}
말줄임 영역을 클릭해도 Item3의 클릭 이벤트가 발생하지 않는 모습
2) 숨겨진 요소의 상호작용 차단하기
(1) 현상
ellipsis
의 두 번째 문제는 숨겨진 요소에서도 상호작용이 발생한다는 점이다.- 아래 GIF를 보면, 말줄임 우측 여백을 클릭할 때 숨겨진 요소(
.item 4
)의 클릭 이벤트가 발생하는 걸 알 수 있다.
왜 이런 현상이 발생하는 걸까?
- CSS의
text-overflow: ellipsis
속성은 요소의 콘텐츠가 지정된 영역을 넘어설 때 텍스트를 잘라내고 말줄임표(...
)를 표시하는 방식이다. - 하지만 중요한 점은 브라우저가 요소의 전체 내용을 여전히 렌더링한다는 것이다. 즉, 시각적으로 말줄임표 처리가 되지만, 실제로는 DOM(Document Object Model)에 해당 요소가 그대로 남아 있다.
- 개발자 도구에서 확인해보면,
item 4
가 여전히 존재하는 것을 알 수 있다. (아래 그림 참고)
(2) 해결 방법: 요소가 숨겨져 있는지 여부 체크하기
- 이 문제를 해결하려면, 해당 요소가 숨겨져 있는지 확인하는 함수(
isVisible
)가 필요하다. isVisible
함수의 전체 코드는 다음과 같다.
const isVisible = (element) => {
// (a)
const isOverflow = container.clientWidth < container.scrollWidth;
if (!isOverflow) return true;
// (b)
const target = element.closest('.chip');
const targetStyles = window.getComputedStyle(target);
const targetRight = target.offsetLeft + target.offsetWidth + parseFloat(targetStyles.marginRight);
// (c)
const containerStyles = window.getComputedStyle(container);
const containerWidth = container.clientWidth - parseFloat(containerStyles.paddingLeft) - parseFloat(containerStyles.paddingRight);
// (d)
return targetRight <= containerWidth;
};
(a) 오버플로우 여부 확인
const isOverflow = container.clientWidth < container.scrollWidth;
container.clientWidth
는 컨테이너 요소의 가시적인 너비이고,container.scrollWidth
는 컨테이너의 전체 콘텐츠 너비를 나타낸다.- 만약
clientWidth
가scrollWidth
보다 작다면, 콘텐츠가 넘쳐서 스크롤이 발생하고 있음을 의미한다.
(b) 타겟 요소의 끝 위치 계산
const target = element.closest('.chip');
const targetStyles = window.getComputedStyle(target);
const targetRight = target.offsetLeft + target.offsetWidth + parseFloat(targetStyles.marginRight);
- 타겟의 우측 위치를 알면, 타겟이 화면을 벗어났는지 알 수 있다.
- 타겟의 우측 위치(
targetRight
)는 "타겟의 좌측 위치 + 타겟의 전체 너비(border 포함) + 우측 margin"로 계산한다.
(c) 컨테이너의 콘텐츠 너비 계산
const containerStyles = window.getComputedStyle(container);
const containerWidth = container.clientWidth - parseFloat(containerStyles.paddingLeft) - parseFloat(containerStyles.paddingRight);
- 마지막으로
clientWidth
를 사용해, 컨테이너의 컨텐츠 너비를 구한다. - 이때
clientWidth
은 패딩이 포함된 값이므로,clientWidth
에서 좌우 패딩을 빼야 한다.
(d) 요소가 가시 영역 내에 있는지 확인
targetRight <= containerWidth
- 타겟 요소의 우측 좌표(
targetRight
)가 컨테이너의 가시 영역(containerWidth
) 내에 있는지 확인한다. - 만약
targetRight
가containerWidth
보다 작거나 같다면, 타겟 요소는 가시 영역 내에 있으므로 보이는 상태이다.
+) 클릭 이벤트에서 호출하기
- 앞서 설명한
isVisible
함수를 사용하면, 숨겨진 요소에서 클릭 이벤트가 발생하지 않도록 처리할 수 있다. - 이를 클릭 이벤트 핸들러에서 다음과 같이 활용할 수 있다.
- 아래
handleClick()
은.container
내부의.chip
요소에서 클릭 이벤트가 발생할 때 실행되는 핸들러이다. - 만약
isVisible(item)
이false
를 반환하면, 해당 요소는 숨겨진 상태이므로 early return으로 처리하여 이벤트를 중단한다.
const container = document.querySelector('.container');
container.addEventListener('click', handleClick);
function handleClick(e) {
const item = e.target.closest('.chip')
if (!item) return;
if (!isVisible(item)) return; // 추가!
...
}
- 이렇게 구현하면 숨겨진 요소에 클릭 이벤트가 발생하지 않도록 방지할 수 있다.
(3) 결과 보기
item4가 숨겨진 영역(빨간 박스)을 클릭해도 Item4의 클릭 이벤트가 발생하지 않는 모습
그럼 앞선 2가지 문제를 수정한 모습은 어떨까? 아래 playground가 바로 수정 결과이다.
See the Pen ellipsis hidden issue - resolved-1 by KumJungMin (@kumjungmin) on CodePen.
3. 마치며…
이번 시간에는 CSS ellipsis
를 사용하면서 발생할 수 있는 두 가지 주요 문제에 대해 다루었다.
첫 번째는 말줄임표 영역에서 클릭 이벤트가 발생하는 문제였고, 두 번째는 말줄임으로 가려진 요소에서 클릭 이벤트가 발생하는 현상이었다.
이 문제들을 해결하기 위해 pointer-events
속성을 활용하거나, 숨겨진 요소가 가시적인지 여부를 체크하는 함수를 사용할 수 있었다.
말줄임 영역에 버튼이 들어가는 경우가 흔치는 않겠지만, 만약 이와 유사한 구조를 취해야 한다면 말줄임표와 숨겨진 요소에서 의도하지 않은 상호작용이 발생하지 않는지 확인하는 게 중요할 듯 하다.
해당 포스팅에서 사용한 예시 코드를 보고 싶다면 아래 링크를 참고해도 좋다 :)
'개발 기술 > 사소하지만 놓치기 쉬운 개발 지식' 카테고리의 다른 글
모바일에서 연속 입력시 값이 무시되는 이유(feat. click, touch, pointer event) (4) | 2024.10.19 |
---|---|
iOS 모바일의 보안과 이벤트 유실(feat. 사용자 활성화, 이벤트 루프) (20) | 2024.10.03 |
[JS] event.target vs event.currentTarget의 차이 (with. vue slot) (0) | 2024.09.08 |
[CSS] clip-path로 별점(Rating) UI 구현하는 법 (with. vue 컴포넌트 예시 포함) (13) | 2024.08.28 |
[JS] RangeSlider를 구현하는 법(feat. 중첩 range input) (2) | 2024.07.14 |
댓글