1. 개요
이번에는 텍스트를 읽는 기능이다. 텍스트 상자의 글을 인식하여 읽어 주며, 속도 및 음성의 높낮이를 조절할 수 있다.
2. HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Speech Synthesis</title>
<link href='https://fonts.googleapis.com/css?family=Pacifico' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="voiceinator">
<h1>The Voiceinator 5000</h1>
<select name="voice" id="voices">
<option value="">Select A Voice</option>
</select>
<label for="rate">Rate:</label>
<input name="rate" type="range" min="0" max="3" value="1" step="0.1">
<label for="pitch">Pitch:</label>
<input name="pitch" type="range" min="0" max="2" step="0.1">
<textarea name="text">Hello! I love JavaScript 👍</textarea>
<button id="stop">Stop!</button>
<button id="speak">Speak</button>
</div>
</body>
</html>
3. CSS
html {
font-size: 10px;
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 0;
padding: 0;
font-family: sans-serif;
background-color: #3BC1AC;
display: flex;
min-height: 100vh;
align-items: center;
background-image:
radial-gradient(circle at 100% 150%, #3BC1AC 24%, #42D2BB 25%, #42D2BB 28%, #3BC1AC 29%, #3BC1AC 36%, #42D2BB 36%, #42D2BB 40%, transparent 40%, transparent),
radial-gradient(circle at 0 150%, #3BC1AC 24%, #42D2BB 25%, #42D2BB 28%, #3BC1AC 29%, #3BC1AC 36%, #42D2BB 36%, #42D2BB 40%, transparent 40%, transparent),
radial-gradient(circle at 50% 100%, #42D2BB 10%, #3BC1AC 11%, #3BC1AC 23%, #42D2BB 24%, #42D2BB 30%, #3BC1AC 31%, #3BC1AC 43%, #42D2BB 44%, #42D2BB 50%, #3BC1AC 51%, #3BC1AC 63%, #42D2BB 64%, #42D2BB 71%, transparent 71%, transparent),
radial-gradient(circle at 100% 50%, #42D2BB 5%, #3BC1AC 6%, #3BC1AC 15%, #42D2BB 16%, #42D2BB 20%, #3BC1AC 21%, #3BC1AC 30%, #42D2BB 31%, #42D2BB 35%, #3BC1AC 36%, #3BC1AC 45%, #42D2BB 46%, #42D2BB 49%, transparent 50%, transparent),
radial-gradient(circle at 0 50%, #42D2BB 5%, #3BC1AC 6%, #3BC1AC 15%, #42D2BB 16%, #42D2BB 20%, #3BC1AC 21%, #3BC1AC 30%, #42D2BB 31%, #42D2BB 35%, #3BC1AC 36%, #3BC1AC 45%, #42D2BB 46%, #42D2BB 49%, transparent 50%, transparent);
background-size:100px 50px;
}
.voiceinator {
padding: 2rem;
width: 50rem;
margin: 0 auto;
border-radius: 1rem;
position: relative;
background: white;
overflow: hidden;
z-index: 1;
box-shadow: 0 0 5px 5px rgba(0,0,0,0.1);
}
h1 {
width: calc(100% + 4rem);
margin: -2rem 0 2rem -2rem;
padding: .5rem;
background: #ffc600;
border-bottom: 5px solid #F3C010;
text-align: center;
font-size: 5rem;
font-weight: 100;
font-family: 'Pacifico', cursive;
text-shadow: 3px 3px 0 #F3C010;
}
.voiceinator input,
.voiceinator button,
.voiceinator select,
.voiceinator textarea {
width: 100%;
display: block;
margin: 10px 0;
padding: 10px;
border: 0;
font-size: 2rem;
background: #F7F7F7;
outline: 0;
}
textarea {
height: 20rem;
}
input[type="select"] {
}
.voiceinator button {
background: #ffc600;
border: 0;
width: 49%;
float: left;
font-family: 'Pacifico', cursive;
margin-bottom: 0;
font-size: 2rem;
border-bottom: 5px solid #F3C010;
cursor: pointer;
position: relative;
}
.voiceinator button:active {
top: 2px;
}
.voiceinator button:nth-of-type(1) {
margin-right: 2%;
}
4. Javascript
const msg = new SpeechSynthesisUtterance();
let voices = [];
const voicesDropdown = document.querySelector('[name="voice"]');
const options = document.querySelectorAll('[type="range"], [name="text"]');
const speakButton = document.querySelector('#speak');
const stopButton = document.querySelector('#stop');
먼저 HTML 요소들을 정의한다. voicesDropdown에는 name=voice인 select 태그를, options에는 type=range인 input 태그 두 개와 name=text인 textarea 태그 한 개를, speakButton과 stopButton에는 두 버튼을 각각 할당한다.
SpeechSynthesisUtterance는 Web Speech API의 인터페이스로, 음성 요청을 나타낸다. 여기에는 음성 서비스가 읽어야 할 내용과 읽는 방법에 대한 정보(language, pitch, volume, rate, text) 등이 포함되어 있다.
voices는 빈 배열을 할당한다.
msg.text = document.querySelector('[name="text"]').value;
function populateVoices() {
voices = this.getVoices();
voicesDropdown.innerHTML = voices
.filter(voice => voice.lang.includes('en'))
.map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
.join('');
}
speechSynthesis.addEventListener('voiceschanged', populateVoices);
msg의 text의 값은 options 중의 하나로 선택했던 name=text인 텍스트에어리어 태그의 value로 설정한다. 이를 통해 SpeechSynthesisUtterance는 텍스트 상자 속의 문자열을 읽게 된다.
speechSynthesis에 voiceschanged가 발생할 때 populateVoices 함수를 실행하는 이벤트 리스너를 추가하는데, voiceschanged 이벤트는 getVoice() 매소드에 의해 SpeechSynthesis 객체가 반환될 때 발생한다.
populateVoices 함수는, 먼저 this(speechSynthesisget 객체)에 getVoices() 매소드로 각 목소리들이 담긴 객체들의 배열을 비어있는 배열 voices에 할당한다. 이 배열의 형태를 가공하여 Dropdown 형태로 select 태그의 내용으로 입력한다. 먼저 각각의 목소리가 담긴 객체의 언어를 영어로 설정하기 위해 filter를 걸어 lang 속성의 값이 'en'을 포함한 객체만을 반환한다. 이 객체들의 형태를 변환하기 위해 map을 걸어 각각의 voice 객체들을 option 태그에 입력하고, value와 태그의 내용을 입력한다. 각 항목들 사이의 콤마를 join을 이용해 지운다.
function toggle(startOver = true) {
speechSynthesis.cancel();
if (startOver) {
speechSynthesis.speak(msg);
}
}
speakButton.addEventListener('click', toggle);
stopButton.addEventListener('click', toggle.bind(null, false));
// stopButton.addEventListener('click', () => toggle(false));
toggle 함수는 speakButton과 stopButton이 클릭되었을 때 실행되도록 한다. toggle 함수는 기본적으로 speechSynthesis의 매소드인 cancel을 통해 진행중인 음성 읽기 서비스를 중단시킨다. 여기에 true로 설정한 startOver가 전달되는 경우에는 speak 매소드로 speechSynthesis를 다시 실행시킨다. toggle에 false가 전달되면 if문은 실행되지 않고 서비스가 중단되기만 할 것이다.
bind 함수는 호출될 때 새로운 함수를 생성한다. 첫 번째 인자로 null을 넘기는 경우는, 두 번째 인자를 해당 함수의 첫 번째 인자로 고정하고 싶을 때 이용한다. 위 상황에서는 if문이 실행되지 않도록 전달되는 인자를 false로 고정하여 넘긴다고 생각할 수 있다.
function setVoice() {
msg.voice = voices.find(voice => voice.name === this.value);
toggle();
}
voicesDropdown.addEventListener('change', setVoice);
위에서 만든 voicesDropdown에 change 이벤트가 발생할 때, 즉 사용자가 Dropdown 목록에서 값을 변경했을 때, setVoice 함수가 실행되도록 이벤트 리스너를 건다.
setVoice는 SpeechSynthesis 객체(msg)의 voice 값을 설정하는데, 앞서 populateVoices 함수의 실행으로 인해 각 목소리들이 담긴 객체들의 배열이 된 voices에서, find API를 이용해 name 값이 전달된 change의 값과 일치하는 것을 찾아 할당한다. 이후 toggle 함수를 실행시킨다. toggle 함수는 진행 중이던 음성 스피치 서비스를 종료하고 speak 매소드가 담긴 if문을 실행시킨다.
function setOption() {
msg[this.name] = this.value;
toggle();
}
options.forEach(option => option.addEventListener('change', setOption));
setOption은 options의 각각의 요소가 change 이벤트를 전달받았을 때 시행된다. 즉, range의 값을 변경하거나, 텍스트 상자의 글 내용을 변경했을 때 발생한다. 이는 전달받은 이벤트의 대상인 this의 name을 msg에서 찾아 그 값을 this로 전달된 값으로 바꿔 준다. 이후 toggle을 통해 speak를 실행시킨다.
[Javascript] 25. Event Capture, Propagation, Bubbling and Once (0) | 2021.05.08 |
---|---|
[Javascript] 24. Sticky Nav (0) | 2021.05.07 |
[Javascript] 22. Follow Along Link Highlighter (0) | 2021.05.05 |
[Javascript] 20. Speech Detection (0) | 2021.05.04 |
[Javascript] 19. Webcam Fun (0) | 2021.05.03 |
댓글 영역