Trong kỷ nguyên nhạc số, một ứng dụng nghe nhạc đẹp mắt và tiện dụng là điều không thể thiếu. Bài viết này sẽ hướng dẫn bạn tạo một ứng dụng nghe nhạc Harmonic Beats bằng Angular và Tailwind CSS, với các tính năng như điều hướng bài hát, thanh trượt tiến độ tùy chỉnh và danh sách phát được tuyển chọn, mang đến trải nghiệm vừa bắt mắt vừa tương tác cao.
Các tính năng chính của Harmonic Beats
- Điều hướng bài hát: Tua tới hoặc tua lại giữa các bài hát.
- Thanh trượt tiến độ tùy chỉnh: Được xây dựng bằng CSS để kiểm soát phát lại chính xác.
- Quản lý danh sách phát: Hiển thị và chọn bài hát từ danh sách có thể cuộn.
- Xử lý lỗi: Cảnh báo các sự cố phát lại.
Hướng dẫn từng bước để xây dựng ứng dụng Harmonic Beats
1. Thiết lập dự án Angular
Bắt đầu bằng cách thiết lập một dự án Angular. Nếu bạn chưa cài đặt Angular CLI, hãy cài đặt bằng lệnh:
npm install -g @angular/cli
Sau đó, tạo một dự án mới:
ng new music-player
cd music-player
Cài đặt Tailwind CSS:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
Định cấu hình tailwind.config.js để bao gồm các thành phần Angular:
module.exports = { content: [ "./src/**/*.{html,ts}", ], theme: { extend: {}, }, plugins: [],
}
Thêm các chỉ thị Tailwind vào src/styles.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
2. Thiết kế giao diện người dùng với Tailwind CSS
Sử dụng Tailwind CSS để tạo giao diện người dùng hiện đại, đáp ứng. Dưới đây là cấu trúc HTML cho thành phần:
<div class="w-full h-screen flex flex-col items-center justify-center p-6 bg-gray-800 text-white"> <h1 class="text-3xl font-bold mb-8">Music Player</h1> @if (error()) { <div class="bg-red-500 p-4 mb-4 rounded w-full max-w-md"> <div class="flex items-center"> <span class="material-icons mr-2">error_outline</span> <div> <h4 class="font-bold">Error</h4> <p>{{ error() }}</p> </div> </div> </div> } <!-- Current Track --> <div class="text-center mb-4 w-full max-w-md"> <h2 class="text-2xl font-bold">{{ tracks[currentTrackIndex()].title }}</h2> <p class="text-gray-400">{{ tracks[currentTrackIndex()].artist }}</p> </div> <!-- Custom Slider --> <div class="relative mb-4 w-full max-w-md"> <input type="range" min="0" max="100" [value]="progress()" (input)="handleSeek($event)" class="w-full h-2 bg-gray-600 rounded-lg appearance-none cursor-pointer slider-thumb" /> </div> <!-- Player Controls --> <div class="flex justify-center space-x-4 mb-6 w-full max-w-md"> <button (click)="handlePrevious()" class="btn"> <i class="fas fa-step-backward"></i> </button> <button (click)="handlePlayPause()" class="btn"> @if (isPlaying()) { <i class="fas fa-pause"></i> } @else { <i class="fas fa-play"></i> } </button> <button (click)="handleNext()" class="btn"> <i class="fas fa-step-forward"></i> </button> </div> <!-- Track List --> <h3 class="text-xl font-semibold mb-2">Track List</h3> <div class="h-[200px] w-full max-w-md rounded-md border border-gray-700 p-4 overflow-y-auto custom-scrollbar" #trackListContainer> @for (track of tracks; track $index) { <div class="p-2 cursor-pointer hover:bg-gray-700 rounded-md" [ngClass]="{ 'bg-gray-700': $index === currentTrackIndex() }" (click)="handleTrackSelect($index)"> <p class="font-medium">{{ track.title }}</p> <p class="text-sm text-gray-400">{{ track.artist }}</p> </div> } @empty { <p class="text-gray-400">No tracks found</p> } </div>
</div>
3. Thêm tính tương tác
Định nghĩa logic trong music-player.component.ts:
- Xử lý phát lại: Phát, tạm dừng và điều hướng bài hát.
- Cập nhật tiến độ: Theo dõi và hiển thị tiến độ phát lại.
- Xử lý lỗi: Cung cấp phản hồi cho người dùng về các sự cố âm thanh.
import { NgClass } from '@angular/common';
import { Component, OnInit, signal, computed, ViewChild, ElementRef,
} from '@angular/core'; interface Track { title: string; artist: string; url: string;
} @Component({ selector: 'app-root', imports: [NgClass], templateUrl: './app.component.html', styleUrl: './app.component.scss',
})
export class AppComponent implements OnInit { @ViewChild('trackListContainer') trackListContainer!: ElementRef; tracks: Track[] = [ { title: 'Serenity', artist: 'Piano and Strings', url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3', }, { title: 'Energetic Beats', artist: 'Drum and Bass Collective', url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3', }, { title: 'Smooth Jazz', artist: 'Sax and Keys', url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3', }, { title: 'Classical Symphony', artist: 'Orchestra Ensemble', url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-4.mp3', }, { title: 'Electronic Dreams', artist: 'Synthwave Collective', url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-5.mp3', }, { title: 'Ambient Relaxation', artist: 'Chillout Lounge', url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-6.mp3', }, { title: 'Country Folk', artist: 'Acoustic Guitar Trio', url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-7.mp3', }, { title: 'Rocking Blues', artist: 'Electric Guitar Band', url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-8.mp3', }, { title: 'Hip Hop Beats', artist: 'Rap Collective', url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-9.mp3', }, { title: 'Reggae Vibes', artist: 'Island Rhythms', url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-10.mp3', } ]; currentTrackIndex = signal(0); isPlaying = signal(false); progress = signal(0); error = signal<string | null>(null); private audio: HTMLAudioElement | null = null; ngOnInit() { this.loadTrack(); } loadTrack() { this.audio?.pause(); this.audio = new Audio(this.tracks[this.currentTrackIndex()].url); this.audio.addEventListener('timeupdate', this.updateProgress.bind(this)); this.audio.addEventListener('ended', this.handleNext.bind(this)); this.audio.addEventListener('canplay', () => this.error.set(null)); this.audio.addEventListener('error', () => { this.error.set('Unable to load audio. Please check the audio source.'); this.isPlaying.set(false); }); } handlePlayPause() { if (this.audio) { if (this.isPlaying()) { this.audio.pause(); } else { this.audio.play().catch(() => { this.error.set('Playback failed. Please try again.'); }); } this.isPlaying.set(!this.isPlaying()); } } scrollToCurrentTrack() { const container = this.trackListContainer.nativeElement; const selectedTrack = container.children[this.currentTrackIndex()]; if (selectedTrack) { selectedTrack.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } } handleNext() { this.currentTrackIndex.set( (this.currentTrackIndex() + 1) % this.tracks.length ); this.loadTrack(); this.isPlaying.set(true); this.audio?.play(); this.scrollToCurrentTrack(); } handlePrevious() { this.currentTrackIndex.set( (this.currentTrackIndex() - 1 + this.tracks.length) % this.tracks.length ); this.loadTrack(); this.isPlaying.set(true); this.audio?.play(); this.scrollToCurrentTrack(); } handleTrackSelect(index: number) { this.currentTrackIndex.set(index); this.loadTrack(); this.isPlaying.set(true); this.audio?.play(); this.scrollToCurrentTrack(); } handleSeek(event: Event) { const input = event.target as HTMLInputElement; const value = parseFloat(input.value); this.progress.set(value); if (this.audio) { const newTime = (value / 100) * this.audio.duration; this.audio.currentTime = newTime; } } updateProgress() { if (this.audio) { const duration = this.audio.duration || 1; const currentTime = this.audio.currentTime; this.progress.set((currentTime / duration) * 100); } }
}
5. Triển khai và kiểm tra
Sử dụng Angular CLI để chạy ứng dụng cục bộ:
ng serve
Truy cập http://localhost:4200 để xem Harmonic Beats hoạt động!
Kết luận
Trong hướng dẫn này, bạn đã học cách xây dựng một ứng dụng nghe nhạc hiện đại bằng Angular và Tailwind CSS. Bằng cách kết hợp kiến trúc dựa trên thành phần của Angular với framework CSS Tailwind, bạn đã tạo ra một trình phát nhạc hấp dẫn về mặt hình ảnh và tương tác. Bạn có thể tùy chỉnh ứng dụng thêm bằng cách bổ sung các tính năng như điều khiển âm lượng, chế độ phát ngẫu nhiên hoặc chủ đề chế độ tối.
Cảm ơn các bạn đã theo dõi!