序文#
このプロジェクトは、ある日寮で音楽を聴いているときに思いついたものです。ネットイースクラウドに登録してから 5 年が経ちましたが、このプラットフォームはますます私を失望させるようになっていますが、5 年間聴いてきたので感情が出てきました。だから、この休暇中にほぼ 1 ヶ月かけてこのプロジェクトを書きました。なぜモバイルページを作ったのかというと、私が音楽を聴いていたのは携帯電話だったからです... だから、UI も Android 版のネットイースクラウドミュージックを模倣しました。すべての機能を実装することはできませんでしたが、比較的中核となる再生ページを作成しました。このブログでは、このプロジェクトの実装プロセスといくつかのトラブルについて記録しています。
このプロジェクトは、Vue + Typescript + Vuetify UI を使用して実装されています。
プロジェクトのアドレス:CloudMusic
実装された機能:
- ログイン
- プレイリストの取得
- プレイリストの作成
- プレイリストの削除 / お気に入り解除
- 曲の再生
- ランキング
- 毎日のおすすめ
- おすすめのプレイリスト
プロジェクトのスクリーンショット:
再生#
これは核心機能と言えるでしょう。音楽を聴くプラットフォームは、音楽を聴かないと通過できません。まず、曲の選択はプレイリストから、ディスカバリーページのスライドショーから、再生ページの選択から取得できます。そして、曲の再生 / 一時停止を制御するために、他のページの下部の再生タブで制御することもできますし、再生ページで制御することもできます。Prop と Emit を使用すると、非常に多くのものが混乱するため、ここでは Vuex を使用しました。
Vuex は、Vue.js アプリケーションのための状態管理パターンです。アプリケーションのすべてのコンポーネントの状態を集中的に管理し、予測可能な方法で状態を変化させるための対応するルールを提供します。
実際には、必要なコンポーネントの共有状態を抽出し、グローバルなシングルトンモデルとして管理し、任意のコンポーネントでその状態や値を変更できるようにします。したがって、選択した曲の ID と現在の再生リスト情報、ID を Vuex に渡し、再生ページでは単に Vuex で曲の ID の変化を監視し、新しい曲を再生できます。Vuex の一時停止状態を監視して、オーディオコンポーネントの一時停止または再生を制御します。
再生進捗#
進捗バーには、Vuetify UI のスライダーコンポーネントを使用し、v-model を使用して値を設定します。
オーディオコンポーネントでは、@timeupdateを使用してオーディオの現在の再生タイムスタンプを監視し、再生の総時間で割り、100 を乗算して現在の再生進捗のパーセンテージを取得し、スライダーコンポーネントに割り当てます。進捗バーの左側の時間も、現在の再生タイムスタンプを時間に変換して取得します。
しかし、ここで問題が発生します。スライダーを移動して再生の進捗を変更する必要がありますが、ちょうど @timeupdate にバインドされたメソッドでは、進捗をスライダーに単方向に割り当てるだけであり、音楽が再生されているときにこのメソッドは常に実行されるため、どのようにスライダーを移動しても、スライダーはすぐに現在の再生位置に瞬時に移動します。したがって、スライダーが移動中かどうかを監視するメソッドを追加する必要があります。スライダーが移動中の場合は、現在の再生進捗をスライダーに割り当てないようにし、手を離すとスライダーの現在の進捗をタイムスタンプに変換してオーディオの再生時間に再割り当てします。
私の解決策は、スライダーコンポーネントに @mousedown と @mouseup を導入することでした。マウスが押されたときに変数に true を割り当て、離されたときに false を割り当て、@timeupdate にバインドされたメソッドを変更し、その変数が false の場合にのみスライダーコンポーネントに値を割り当てるように設定しました。ただし、スライダーをスライドするときにも現在の再生時間を変更する必要があるため、スライダーコンポーネントに @change を導入し、スライダーの値が手動で変更された場合にこのメソッドがトリガーされるようにし、このメソッドでオーディオの再生進捗を変更します。
歌詞の実装#
バックエンドから取得した歌詞は文字列ですが、各行の歌詞は \n で区切られているため、文字列を \n で分割すると各行の歌詞が得られます。各行の歌詞は、角括弧で囲まれた時間で分割する必要があります。最後に、時間をタイムスタンプに変換し、歌詞と一緒にクラスとして配列に入れます。
インデックスを設定し、何行目の歌詞かを記録するために使用します。歌詞コンポーネントの高さを offsetHeight で取得し、歌詞の中央線の位置を確認するためにインデックスに行ごとの歌詞の高さを乗算してスクロールするかどうかを判断します。
実際には、外部の div にオーバーフローを隠し、margin-top を変更してスクロール効果を実現します。margin-top の値は、中央線の高さからインデックスに行ごとの歌詞の高さを乗算した値によって決まります。同時に、変更の時間を指定するために transition 属性を使用できます。
例:transition: margin-top 1s;
これは、margin-top プロパティが 1 秒で変更されることを意味します。
Vue のカスタムディレクティブについて#
制作中に、特定の機能を実現したいと思いました。それは、特定のコンポーネント以外をクリックした場合にそのコンポーネントを非表示にする機能です。
調査中に、Vue にはそのようなディレクティブはないことがわかりましたが、自分でカスタムディレクティブを作成してこの機能を実現することができます。
まず、カスタムディレクティブオブジェクトのフック関数を見てみましょう:
- bind:要素に最初にバインドされたときに呼び出される関数です。ここでは、一度だけの初期化設定を行うことができます。
- inserted:バインドされた要素が親ノードに挿入されたときに呼び出されます(親ノードが存在することは保証されますが、ドキュメントに挿入されているわけではありません)。
- update:ディレクティブが含まれるコンポーネントの VNode が更新されたときに呼び出されますが、子の VNode の更新よりも前に発生する可能性があります。ディレクティブの値が変更されたかどうかは関係ありませんが、更新前後の値を比較して不要なテンプレートの更新を無視することができます(詳細なフック関数のパラメータについては以下を参照)。
- componentUpdated:ディレクティブが含まれるコンポーネントの VNode およびそのすべての子 VNode が更新された後に呼び出されます。
- unbind:要素とディレクティブのバインドが解除されたときに呼び出される関数です。
上記はすべて Vue のドキュメントからの情報ですが、それぞれのフック関数と変数はオプションであり、必要なものだけを選択すればよいです。この機能を実装する際には、bind と unbind のみを使用し、変数も el と binding のみを使用しました。
import {DirectiveOptions} from "vue";
// カスタムディレクティブclickoutside、クリックされた場所が現在の要素ではない場合にバインドされたメソッドを実行します
const clickoutside: DirectiveOptions = {
// ディレクティブの初期化
bind (el: any, binding: any, vnode) {
function documentHandler (e: any) {
// クリックされた要素が自身であるかどうかを判断し、自身であれば戻る
if (el.contains(e.target)) {
return false
}
// ディレクティブに関数がバインドされているかどうかを判断します
if (binding.expression) {
// バインドされた関数を呼び出します
binding.value(e)
}
}
// 現在の要素にプライベート変数をバインドし、unbindでイベントリスナーを解除できるようにします
el.vueClickOutside = documentHandler
document.addEventListener('click', documentHandler)
},
unbind (el: any, binding) {
// イベントリスナーを解除します
document.removeEventListener('click', el.vueClickOutside)
delete el.vueClickOutside
}
}
export default clickoutside;
必要な場所でインポートします:
@Component({
directives:{
clickoutside,
}
})
使用方法(div 以外をクリックした場合に outside メソッドを呼び出します):
<div v-clickoutside="outside"></div>
この機能は実装した後で、Vuetify が既にこのようなディレクティブを統合していることに気づきましたので、直接使用できます。これは、ドキュメントをよく読まないという結果です。
最後に⭐️#
🙏 NeteaseCloudMusicApi が提供する API とドキュメントに感謝します。
また、このプロジェクトは個人の学習のために作成されたものであり、正常に使用するにはNetEase Cloud Musicにアクセスしてください👈