In today’s digital world, accessibility and user experience are paramount. Imagine a web application that can read text aloud, making information accessible to everyone, including those with visual impairments or who prefer auditory learning. This tutorial will guide you through building a simple, yet functional, Text-to-Speech (TTS) application using Vue.js. This project is perfect for beginners and intermediate developers looking to expand their Vue.js skills and create something practical.
Why Build a Text-to-Speech App?
Text-to-Speech applications have numerous applications. They can be used for:
- Accessibility: Enabling users with visual impairments to access web content.
- Education: Helping students learn pronunciation and improve reading comprehension.
- Productivity: Allowing users to listen to documents while multitasking.
- Entertainment: Creating interactive audio experiences.
Building this app will give you hands-on experience with:
- Vue.js fundamentals: Components, data binding, and event handling.
- Browser APIs: Working with the SpeechSynthesis API.
- User Interface (UI) design: Creating a simple and intuitive interface.
Prerequisites
Before we begin, ensure you have the following:
- A basic understanding of HTML, CSS, and JavaScript.
- Node.js and npm (or yarn) installed. You’ll need these to manage your project’s dependencies.
- A code editor like Visual Studio Code, Sublime Text, or Atom.
Setting Up the Vue.js Project
Let’s start by setting up our Vue.js project using the Vue CLI (Command Line Interface). Open your terminal and run the following commands:
npm install -g @vue/cli
vue create text-to-speech-app
During the project creation, you can choose the default setup (babel, eslint) or customize it based on your preferences. Once the project is created, navigate into your project directory:
cd text-to-speech-app
Project Structure Overview
Your project structure should look something like this:
text-to-speech-app/
├── node_modules/
├── public/
│ └── index.html
├── src/
│ ├── App.vue
│ ├── components/
│ │ └── TTSInput.vue
│ ├── main.js
│ └── assets/
│ └── ...
├── package.json
└── ...
- public/index.html: The main HTML file where our app will be rendered.
- src/App.vue: The root component of our application.
- src/components/TTSInput.vue: A component that will contain the text input and controls.
- src/main.js: The entry point of our application, where Vue is initialized.
Building the TTSInput Component
Let’s create the core functionality of our application within the TTSInput.vue component. This component will handle the text input, speech synthesis, and any associated UI elements.
Create a file named TTSInput.vue inside the src/components/ directory and add the following code:
<template>
<div class="tts-input-container">
<textarea v-model="textToSpeak" placeholder="Enter text here..." class="tts-textarea"></textarea>
<div class="tts-controls">
<button @click="speak" :disabled="!textToSpeak" class="tts-button">Speak</button>
<button @click="stop" :disabled="!textToSpeak" class="tts-button">Stop</button>
</div>
</div>
</template>
<script>
export default {
name: 'TTSInput',
data() {
return {
textToSpeak: '', // The text entered by the user
};
},
methods: {
speak() {
if (!this.textToSpeak) return; // Prevent speaking empty text
const utterance = new SpeechSynthesisUtterance(this.textToSpeak);
window.speechSynthesis.speak(utterance);
},
stop() {
window.speechSynthesis.cancel();
}
},
};
</script>
<style scoped>
.tts-input-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
border: 1px solid #ccc;
border-radius: 5px;
width: 80%;
margin: 20px auto;
}
.tts-textarea {
width: 100%;
height: 150px;
margin-bottom: 10px;
padding: 10px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 4px;
}
.tts-controls {
display: flex;
justify-content: space-around;
width: 100%;
}
.tts-button {
padding: 10px 20px;
font-size: 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.tts-button:hover {
background-color: #3e8e41;
}
.tts-button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
</style>
Let’s break down this code:
- Template: The template defines the UI structure. It includes a
textareafor text input, and two buttons: “Speak” and “Stop”. Thev-modeldirective binds the text input to thetextToSpeakdata property. The@clickdirectives attach click event handlers to the buttons. The:disableddirective disables the buttons when there is no text in the input. - Script: The script section defines the component’s logic.
- Data: The
datafunction initializes thetextToSpeakproperty to an empty string. - Methods: The
methodsobject contains thespeakandstopfunctions. - speak(): Creates a new
SpeechSynthesisUtteranceobject with the text to be spoken. It then callswindow.speechSynthesis.speak(utterance)to start the speech. It also includes a check to prevent the speech synthesis if the text area is empty. - stop(): Calls
window.speechSynthesis.cancel()to stop any ongoing speech. - Style: The style section contains CSS to style the component, making it visually appealing.
Integrating the TTSInput Component into App.vue
Now, let’s integrate our TTSInput component into the main application component (App.vue).
Open src/App.vue and replace its content with the following:
<template>
<div id="app">
<h1>Text-to-Speech App</h1>
<TTSInput />
</div>
</template>
<script>
import TTSInput from './components/TTSInput.vue';
export default {
name: 'App',
components: {
TTSInput,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Here’s what changed:
- We import the
TTSInputcomponent. - We register the
TTSInputcomponent in thecomponentsobject. - We use the
<TTSInput />tag in the template to render the component.
Running the Application
To run the application, execute the following command in your terminal:
npm run serve
This will start a development server, and you should be able to access your application in your web browser, typically at http://localhost:8080/. You should see a text area and two buttons. Type some text into the text area and click “Speak” to hear the text read aloud. Click “Stop” to interrupt the speech.
Advanced Features and Enhancements
This is a basic implementation. Let’s explore some ways to enhance the application.
1. Voice Selection
The SpeechSynthesis API allows you to select different voices. Let’s add a dropdown to allow users to choose their preferred voice.
Modify the TTSInput.vue component as follows:
<template>
<div class="tts-input-container">
<textarea v-model="textToSpeak" placeholder="Enter text here..." class="tts-textarea"></textarea>
<div class="tts-controls">
<select v-model="selectedVoice" class="tts-select">
<option v-for="voice in voices" :key="voice.name" :value="voice">
{{ voice.name }} ({{ voice.lang }})
</option>
</select>
<button @click="speak" :disabled="!textToSpeak" class="tts-button">Speak</button>
<button @click="stop" :disabled="!textToSpeak" class="tts-button">Stop</button>
</div>
</div>
</template>
<script>
export default {
name: 'TTSInput',
data() {
return {
textToSpeak: '',
voices: [],
selectedVoice: null,
};
},
mounted() {
this.getVoices();
},
methods: {
getVoices() {
this.voices = window.speechSynthesis.getVoices();
// Set a default voice if available
if (this.voices.length > 0) {
this.selectedVoice = this.voices[0];
}
},
speak() {
if (!this.textToSpeak) return;
const utterance = new SpeechSynthesisUtterance(this.textToSpeak);
if (this.selectedVoice) {
utterance.voice = this.selectedVoice;
}
window.speechSynthesis.speak(utterance);
},
stop() {
window.speechSynthesis.cancel();
},
},
watch: {
// Re-fetch voices if the voices change
'$route': {
handler: 'getVoices',
deep: true
}
}
};
</script>
<style scoped>
/* Existing styles... */
.tts-select {
padding: 10px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 10px;
}
</style>
Here’s what’s new:
- Data properties: We added
voices(an array to store available voices) andselectedVoice(the currently selected voice). mounted()lifecycle hook: We callgetVoices()when the component is mounted to populate thevoicesarray.getVoices()method: This method fetches the available voices usingwindow.speechSynthesis.getVoices()and stores them in thevoicesarray. It also sets a default voice.- Template: We added a
selectelement to display the available voices. Thev-fordirective iterates over thevoicesarray and creates anoptionfor each voice. Thev-modeldirective binds the select element to theselectedVoicedata property. - speak() method: Inside the speak method, we now check if a voice is selected and assign it to the utterance object before speaking.
- Style: Added a style for the select element.
Make sure to refresh your browser, and you should see a dropdown to select different voices. The available voices depend on the user’s operating system and browser.
2. Speech Rate and Pitch Control
Let’s add controls to adjust the speech rate (speed) and pitch.
Modify the TTSInput.vue component as follows:
<template>
<div class="tts-input-container">
<textarea v-model="textToSpeak" placeholder="Enter text here..." class="tts-textarea"></textarea>
<div class="tts-controls">
<select v-model="selectedVoice" class="tts-select">
<option v-for="voice in voices" :key="voice.name" :value="voice">
{{ voice.name }} ({{ voice.lang }})
</option>
</select>
<label for="rate">Rate: </label>
<input type="range" id="rate" v-model.number="rate" min="0.5" max="2" step="0.1" class="tts-range">
<label for="pitch">Pitch: </label>
<input type="range" id="pitch" v-model.number="pitch" min="0" max="2" step="0.1" class="tts-range">
<button @click="speak" :disabled="!textToSpeak" class="tts-button">Speak</button>
<button @click="stop" :disabled="!textToSpeak" class="tts-button">Stop</button>
</div>
</div>
</template>
<script>
export default {
name: 'TTSInput',
data() {
return {
textToSpeak: '',
voices: [],
selectedVoice: null,
rate: 1, // Default rate
pitch: 1, // Default pitch
};
},
mounted() {
this.getVoices();
},
methods: {
getVoices() {
this.voices = window.speechSynthesis.getVoices();
if (this.voices.length > 0) {
this.selectedVoice = this.voices[0];
}
},
speak() {
if (!this.textToSpeak) return;
const utterance = new SpeechSynthesisUtterance(this.textToSpeak);
if (this.selectedVoice) {
utterance.voice = this.selectedVoice;
}
utterance.rate = this.rate; // Set the rate
utterance.pitch = this.pitch; // Set the pitch
window.speechSynthesis.speak(utterance);
},
stop() {
window.speechSynthesis.cancel();
},
},
};
</script>
<style scoped>
/* Existing styles... */
.tts-range {
width: 100px;
margin: 0 10px;
}
</style>
Here’s what we added:
- Data properties: We added
rate(defaulting to 1) andpitch(defaulting to 1) to control the speech rate and pitch, respectively. - Template: We added range input elements for rate and pitch. The
v-model.numbermodifier ensures that the values are treated as numbers. - speak() method: We set the
rateandpitchproperties of theSpeechSynthesisUtteranceobject before speaking.
Now, you should see sliders for rate and pitch, allowing you to customize the speech output.
3. Error Handling
While the SpeechSynthesis API is generally reliable, errors can occur. Let’s add basic error handling to our app.
Modify the TTSInput.vue component as follows:
<template>
<div class="tts-input-container">
<textarea v-model="textToSpeak" placeholder="Enter text here..." class="tts-textarea"></textarea>
<div class="tts-controls">
<select v-model="selectedVoice" class="tts-select">
<option v-for="voice in voices" :key="voice.name" :value="voice">
{{ voice.name }} ({{ voice.lang }})
</option>
</select>
<label for="rate">Rate: </label>
<input type="range" id="rate" v-model.number="rate" min="0.5" max="2" step="0.1" class="tts-range">
<label for="pitch">Pitch: </label>
<input type="range" id="pitch" v-model.number="pitch" min="0" max="2" step="0.1" class="tts-range">
<button @click="speak" :disabled="!textToSpeak" class="tts-button">Speak</button>
<button @click="stop" :disabled="!textToSpeak" class="tts-button">Stop</button>
</div>
<div v-if="error" class="tts-error">{{ error }}</div>
</div>
</template>
<script>
export default {
name: 'TTSInput',
data() {
return {
textToSpeak: '',
voices: [],
selectedVoice: null,
rate: 1,
pitch: 1,
error: '', // Error message
};
},
mounted() {
this.getVoices();
},
methods: {
getVoices() {
this.voices = window.speechSynthesis.getVoices();
if (this.voices.length > 0) {
this.selectedVoice = this.voices[0];
}
},
speak() {
if (!this.textToSpeak) {
this.error = 'Please enter some text.';
return;
}
this.error = ''; // Clear any previous errors
const utterance = new SpeechSynthesisUtterance(this.textToSpeak);
if (this.selectedVoice) {
utterance.voice = this.selectedVoice;
}
utterance.rate = this.rate;
utterance.pitch = this.pitch;
utterance.onerror = (event) => {
this.error = `An error occurred: ${event.error}`; // Display the error message
};
window.speechSynthesis.speak(utterance);
},
stop() {
window.speechSynthesis.cancel();
},
},
};
</script>
<style scoped>
/* Existing styles... */
.tts-error {
color: red;
margin-top: 10px;
}
</style>
Here’s what we added:
- Data property: We added an
errorproperty to store error messages. - Template: We added a
divwith the classtts-errorthat displays the error message if theerrorproperty is not empty. - speak() method: We added error handling within the
speak()method. It now checks if the text field is empty and if so, sets the error message. It also clears any existing errors before attempting to speak. We added anonerrorevent handler to theSpeechSynthesisUtteranceobject. This handler will be called if an error occurs during speech synthesis, and it will set theerrorproperty with the error message.
4. UI Improvements
Consider adding the following UI enhancements:
- Loading indicator: Display a loading indicator while the speech is in progress.
- Clear button: Add a button to clear the text input.
- Responsive design: Ensure the app looks good on different screen sizes.
Common Mistakes and How to Fix Them
Here are some common mistakes and how to avoid them:
- Not waiting for voices to load: The
speechSynthesis.getVoices()method might not return voices immediately. Make sure to call it after the component is mounted or after the voices have loaded. Use themounted()lifecycle hook as shown above. - Incorrectly handling the voice selection: Ensure you are correctly assigning the selected voice to the
SpeechSynthesisUtteranceobject. Check that theselectedVoiceis not null before assigning it to the utterance. - Not handling errors: Always include error handling to gracefully handle unexpected issues, as demonstrated in the error handling section above.
- Ignoring browser compatibility: While the SpeechSynthesis API is widely supported, there might be slight differences in behavior across different browsers. Test your application in multiple browsers.
- Forgetting to clear previous errors: When a new speaking attempt is made, make sure to clear any previous error messages.
Key Takeaways
- You’ve learned the basics of building a Text-to-Speech application with Vue.js.
- You’ve gained experience with the SpeechSynthesis API.
- You’ve learned how to create reusable Vue.js components.
- You’ve seen how to enhance the user experience with features like voice selection, rate, and pitch control, and error handling.
FAQ
Here are some frequently asked questions:
- Why are there no voices available? The available voices depend on your operating system and browser. Ensure you have voices installed in your system settings.
- How can I change the language? The
SpeechSynthesisUtteranceobject has alangproperty that you can set to specify the language. However, the voice must support that language. - Why is the speech not working? Double-check that you’ve entered text, and that the “Speak” button is not disabled. Also, check the browser console for any errors.
- Can I use this app offline? Yes, the SpeechSynthesis API works offline, as long as the voices are installed on your system.
By following this tutorial, you’ve built a functional Text-to-Speech app and gained valuable skills in Vue.js and browser APIs. This project is a great starting point for exploring more advanced features and building more complex web applications. You can extend this app further by integrating it with other features, such as reading articles from a URL, and improving the user interface and overall user experience.
