일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- react-router-dom
- 그럼에도 불구하고
- node.js
- 자바
- JS
- node
- media query
- 코드업
- cleancode
- 프론트엔드
- 코딩테스트
- @media
- JavaScript
- HTML
- redux
- 반응형 페이지
- github
- CSS
- 변수
- 그럼에도불구하고
- webpack
- max-width
- coding
- git
- frontend
- react
- TypeScript
- Servlet
- 자바문제풀이
- java
- Today
- Total
그럼에도 불구하고
"selectstart" 글자 돋보기 만들기 본문
글자를 drag 하면 확대된 말풍선을 보여주는 코드를 만들어보자
조건 :
1. 내가 드래그한 단어 및 문단은 형광펜으로 처리한다.
2. 드래그한 단어를 확대해서 말풍선으로 드래그 영역 끝부분에 보여준다.
3. 말풍선을 누르면 말풍선이 꺼진다.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>text Balloon</title>
<link rel="stylesheet" href="./style.css">
<script src="./main.js" defer></script>
</head>
<body>
<div class="container">
<div class="paragraph">가슴속에 그리고 피어나듯이 이름자를 있습니다. 우는 쉬이 추억과 노새, 어머님, 하나에 새워 청춘이 있습니다. 멀리 슬퍼하는 덮어 토끼, 하나에 아침이 위에 버리었습니다. 계집애들의 헤일 지나고 했던 내 노루, 위에 같이 흙으로 거외다. 이름과, 하나의 우는 벌써 책상을 봅니다. 내린 않은 하나에 별 내 잠, 보고, 봅니다. 하나에 별 토끼, 슬퍼하는 쉬이 까닭입니다. 없이 어머님, 위에도 까닭입니다. 자랑처럼 하나에 가을 무덤 나의 까닭이요, 없이 언덕 사랑과 있습니다.
<br><br>
별들을 이네들은 하나의 내일 묻힌 내 지나가는 노루, 까닭입니다. 흙으로 헤는 아름다운 패, 위에도 한 봅니다. 애기 덮어 하나에 그러나 헤일 있습니다. 나는 멀리 사람들의 별 까닭입니다. 이름을 다하지 했던 내린 별 않은 내일 거외다. 언덕 가을로 아침이 이 가득 그러나 멀듯이, 봅니다. 멀리 가난한 별 라이너 봅니다. 잔디가 불러 어머니 나는 아직 프랑시스 하늘에는 봅니다. 이름과, 이웃 자랑처럼 겨울이 하나에 하나의 위에 내린 있습니다. 소학교 나는 이런 별빛이 묻힌 노새, 우는 봅니다. 못 아무 별 듯합니다.
<br><br>
풀이 북간도에 나는 노새, 프랑시스 하나에 계집애들의 딴은 듯합니다. 내린 나는 별빛이 잠, 계십니다. 라이너 하나 하나에 멀듯이, 써 내 그러나 별 흙으로 버리었습니다. 별에도 내 어머니, 있습니다. 별 하늘에는 릴케 위에 때 봅니다. 이름과, 별 어머님, 하나에 가을 나는 마리아 않은 있습니다. 하나에 당신은 마리아 이름을 둘 까닭입니다. 헤일 오면 다 묻힌 사랑과 별 아름다운 언덕 나는 봅니다. 나는 걱정도 한 불러 아침이 듯합니다. 이 파란 멀리 소녀들의 별 피어나듯이 마디씩 말 있습니다.
</div>
</div>
<div id ="balloon"></div>
</body>
</html>
CSS
body {
background-color: rgba(188, 188, 240, 0.686);
position: relative;
}
.container {
margin: 50px auto;
width: 500px;
height: 800px;
border: 3px solid black;
border-radius: 30px;
padding: 20px;
box-sizing: border-box;
background-color: rgba(255, 255, 255, 0.834);
}
.paragraph {
font-size: 1.1rem;
font-weight: bold;
line-height: 1.4em;
}
.paragraph::selection {
background-color: rgba(255, 192, 203, 0.807);
}
#balloon {
cursor: pointer;
position: absolute;
min-width: 100px;
top: 50%;
left: 50%;
text-align: center;
opacity: 0;
display: none;
}
#balloon::before {
content: ' ';
position: absolute;
top: 100%;
left: 50%;
margin-left: -10px;
border: 10px solid transparent;
border-top: 10px solid rgb(188, 188, 240);
}
#balloon.on {
font-size: 25px;
font-weight: bold;
background-color: rgb(188, 188, 240);
color: black;
border-radius: 10px;
opacity: 1;
display: block;
transition: 0.3s all;
}
JavaScript
const paragraph = document.querySelector('.paragraph');
const balloon = document.querySelector('#balloon');
paragraph.addEventListener('selectstart', () => {
paragraph.addEventListener('mouseup', (event) => {
const selectedText = window.getSelection().toString();
textValidation(selectedText, event);
});
});
function textValidation(selectedText, event) {
if (selectedText.length > 0) {
balloon.innerHTML = selectedText;
balloon.classList.add('on');
balloon.style.top = `${event.clientY - balloon.clientHeight * 2.5}px`;
balloon.style.left = `${event.clientX - balloon.clientWidth / 2}px`;
} else {
removeEvent();
}
}
balloon.addEventListener('click', () => {
removeEvent();
})
function removeEvent() {
balloon.classList.remove('on');
}
※ selectstart
selectstart 이벤트는 텍스트의 선택 시점에 발생한다.
용도
- 텍스트 선택 시점에 작업을 처리하고 싶을 때
- 텍스트 선택 시점의 작업을 무효화하고 싶을 때
paragraph.addEventListener('selectstart', () => {
paragraph.addEventListener('mouseup', (event) => {
const selectedText = window.getSelection().toString();
textValidation(selectedText, event);
});
});
paragraph라는 변수에 'selectstart' 이벤트가 발생하고, 'mouseup' 이벤트가 발생하면,
selectedText라는 변수에 내가 선택한 텍스트의 값을 가져와서 문자열로 만든다.
즉, window.getSelection()은 선택 범위를 반환하며, toString()을 문장 끝에 추가하면 선택 중인 문자열을 반환한다.
※ getSelection()
getSelection()을 쓰면 내가 선택한 텍스트 값을 가져올 수 있다. 그전에 Selection 객체에 대해 알아보자.
[ Selection ]
window.getSelection()을 통해 얻을 수 있는 값이 Selection 객체이다.
Selection 객체는 현재 커서의 위치나 선택한 범위를 나타낸다.
왼쪽 => 오른쪽 또는 오른쪽 => 왼쪽 방향을 가질 수 있고, 시작과 끝을 속성으로 구분할 수 있다.
[ 속성 및 메소드 ]
- Selection.anchorNode: 선택이 시작되는 위치
- Selection.anchorOffset: anchor의 Offset.
anchorNode가 텍스트면 anchorNode 앞에 있는 문자 수 (index인 셈이다.)을 나타내고,
요소면 anchorNode의 자식 노드 수를 나타냅니다 - Selection.focusNode: anchorNode와 반대로 선택이 끝나는 위치
- Selection.focusOffset: focus의 Offset.
anchorOffset의 focus 버전으로 생각하시면 됩니다 - Selection.isCollased: 시작 지점과 끝 지점이 같으면 true, 다르면 false 입니다
- Selection.type: 범위를 지정했으면 "Range", 지정하지 않았으면 "Caret", 아예 클릭을 안했다면 "None"
=> 새로고침하고 클릭 안한 상태에서 isCollapsed는 true지만 type은 "None"이다. - window.getSelection().collapse(dom, number): 커서를 dom의 number로 이동한다.
그 후, textValidation 함수를 통해 selectedText와 event 값을 인자로 받아와서 함수를 실행한다.
function textValidation(selectedText, event) {
if (selectedText.length > 0) {
balloon.innerHTML = selectedText;
balloon.classList.add('on');
balloon.style.top = `${event.clientY - balloon.clientHeight * 2.5}px`;
balloon.style.left = `${event.clientX - balloon.clientWidth / 2}px`;
console.log(`${event.clientX}, ${event.clientY}`);
} else {
removeEvent();
}
}
이 부분에서 많이 애를 먹었다.
일단 첫 번째로 body의 position을 relative로 잡고 말풍선과 관련된 요소들은 모두 position:absolute;로 잡았다.
왜냐하면 말풍선의 위치를 나타내기 위한 절대적인 기준점이 필요하다고 생각했고, 그 기준점을 body로 잡은 것이다.
그 후, balloon의 top 위치와 left 위치를 지정해줬는데 우선 event.clientX, event.clientY로 위치를 잡아봤다.
우선, 여기서 event가 발생한 시점은 paragraph에서 'mouseup'이 된 시점이다.
그 시점에 event.clientX, event.clientY를 잡고 그 값을
balloon.style.top = `${event.clientY} px`;
balloon.style.left = `${event.clientX}px`; 과 같이 잡았다.
하지만, 내가 원하는 위치에 나오지 않았다. (적어도 내가 선택한 문장에 가까운 곳에 말풍선이 나왔으면 했는데 전혀 다른 곳에 나오게 되었다.)
top과 left 모두 드래그한 영역 끝부분에 나오지 못하고 더 아래로, 더 멀리 좌표 값이 잡혔다.
아마, position을 잡는 과정에서 내가 생각한 것과 오차? 가 발생하는 게 아닌가 싶었다. 정확한 원인은 잘 모르겠다.
그래서 가장 근접한 좌표값을 구하기 위해서 좌표에 관련된 속성을 찾다가 clientWidth와 clientHeight에 대해 알게 되었고, 이를 사용하여
balloon.style.top = `${event.clientY - balloon.clientHeight} px`;
balloon.style.left = `${event.clientX - balloon.clientWidth} px`; 과 같이 잡았다.
※ clientWidth, clientHeight
element의 크기를 구하는 방법에는 여러 가지가 있는데 clientWidth와 clientHeight는 offsetWidth, offsetHeight과 다르게 실제로 보이고 있는 콘텐츠가 얼마만큼의 공간을 차지하는가에 대한 값을 가져온다.
정리하자면
- offsetWidth, offsetHeight : 엘리먼트의 패딩과 보더, 스크롤바의 사이즈를 포함한 값을 리턴한다.
- clientWidth, clientHeight : 보더와 스크롤바의 크기를 제외한 실제 컨텐츠의 크기를 리턴한다. (패딩은 포함하고 있다.)
아까보다는 근사치에 가까워졌지만 미흡한 부분이 있었고 그 부분은 수치를 대입해서 바로잡았다.
수치의 기준은 수치를 대입하지 않았을 때, 말풍선의 나오는 위치를 파악하고 내가 자리 잡고 싶은 부분까지 어느 정도 부족한지 파악하여 산정하였다.
최종적으로
balloon.style.top = `${event.clientY - balloon.clientHeight * 2.5 } px`;
balloon.style.left = `${event.clientX - balloon.clientWidth / 2 } px`; 과 같이 잡았다.
말풍선을 클릭하면, 없어지게 하는 것은 간단하다.
removeEvent라는 함수를 만들어서 CSS상에서 내가 만든 .on이라는 class를 제거해주면 된다.
결과
See the Pen Untitled by zenghyun (@zenghyun) on CodePen.
'JavaScript > Function implementation' 카테고리의 다른 글
숫자 뽑기 게임 (0) | 2023.01.31 |
---|---|
Drag & Drop 이용하여 이미지 올리기 (0) | 2023.01.26 |
아날로그 시계 만들기 (0) | 2023.01.07 |
mousemove 이벤트로 색 변환하기 (0) | 2023.01.06 |
FileReader 객체 사용하여 image 올리기 (0) | 2023.01.05 |